<commit_before>"use strict";

var Promise = require('bluebird');
var extend = require('extend');
var request = Promise.promisify(require('request'));
var tokenUpdateTime = 12 * 60 * 1000;

/**
 * SignIn to Nfield API
 */
function signIn (defOptions, credentials, callback) {
  var options = {
    method : 'POST',
    uri : 'v1/SignIn',
    json : credentials
  };

  extend(true, options, defOptions);
  return request(options).nodeify(callback);
}

/**
 * Wrapper function for all Nfield API requests that checks if API token is not outdated, and refreshes it otherwise
 */
function requestWithTokenCheck (defOptions, credentials, token, options, callback) {
  var returnedPromise;

  options.headers = options.headers || {};
  extend(true, options, defOptions);

  if (Date.now() - token.Timestamp > tokenUpdateTime) {
    returnedPromise = signIn(defOptions, credentials).then(function (data) {
      if (data[0].statusCode !== 200) {
        throw new Error(`${data[0].statusCode}: ${data[0].body.Message}`);
      } else {
        token.AuthenticationToken = data[0].body.AuthenticationToken;
        token.Timestamp = Date.now();
        options.headers.Authorization = `Basic ${token.AuthenticationToken}`;
        return request(options);
      }
    });
  } else {
    options.headers.Authorization = `Basic ${token.AuthenticationToken}`;
    returnedPromise = request(options);
  }

  returnedPromise.nodeify(callback);
  return returnedPromise;
}

/**
 * Returns Nfield survey status
 *
 * {@link https://api.nfieldmr.com/help/api/get-v1-surveys-surveyid-fieldwork-status}
 */
function getSurveyStatus (defOptions, credentials, token, surveyId, callback) {
  var options = {
    method : 'GET',
    uri : `v1/Surveys/${surveyId}/Fieldwork/Status`
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Starts Nfield survey
 *
 * {@link https://api.nfieldmr.com/help/api/put-v1-surveys-surveyid-fieldwork-start}
 */
function startSurvey (defOptions, credentials, token, surveyId, callback) {
  var options = {
    method : 'PUT',
    uri : `v1/Surveys/${surveyId}/Fieldwork/Start`
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Stops Nfield survey
 *
 * Has a 'magical' parameter called TerminateRunningInterviews, that never explained in any part of Nfield API documentation
 * and doesn't seem to affect anything when changed, but present here for the sake of making requests as close to API docs as possible (defaults to 'false')
 *
 * {@link https://api.nfieldmr.com/help/api/put-v1-surveys-surveyid-fieldwork-stop}
 */
function stopSurvey (defOptions, credentials, token, requestParams, callback) {
  var options = {
    method : 'PUT',
    uri : `v1/Surveys/${requestParams.SurveyId}/Fieldwork/Stop`,
    json : {
      'TerminateRunningInterviews' : requestParams.TerminateRunningInterviews || false
    }
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Retrieves an array of Default Texts (or a specific Default Text) for domain
 *
 * {@link https://api.nfieldmr.com/help/api/get-v1-defaulttexts}
 * {@link https://api.nfieldmr.com/help/api/get-v1-defaulttexts-translationkey}
 */
function getDefaultTexts (defOptions, credentials, token, translationKey, callback) {
  var options;

  translationKey = translationKey || '';

  if (typeof translationKey === 'function') {
    callback = translationKey;
    translationKey = '';
  }

  options = {
    method : 'GET',
    uri : `v1/DefaultTexts/${translationKey}`,
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Retrieves an array of Survey Translations (or a specific Survey Translation) for specific language in survey
 *
 * {@link https://api.nfieldmr.com/help/api/get-v1-surveys-surveyid-languages-languageid-translations}
 * {@link https://api.nfieldmr.com/help/api/get-v1-surveys-surveyid-languages-languageid-translations-translationkey}
 */
function getSurveyTranslations (defOptions, credentials, token, requestParams, callback) {
  var options;

  requestParams.TranslationKey = requestParams.TranslationKey || '';

  options = {
    method : 'GET',
    uri : `v1/Surveys/${requestParams.SurveyId}/Languages/${requestParams.LanguageId}/Translations/${requestParams.TranslationKey}`,
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

module.exports = {
  signIn : signIn,
  getSurveyStatus : getSurveyStatus,
  startSurvey : startSurvey,
  stopSurvey : stopSurvey,
  getDefaultTexts : getDefaultTexts,
  getSurveyTranslations : getSurveyTranslations
};<commit_msg>Add SurveyTranslations and SurveyLanguages api methods<commit_after>"use strict";

var Promise = require('bluebird');
var extend = require('extend');
var request = Promise.promisify(require('request'));
var tokenUpdateTime = 12 * 60 * 1000;

/**
 * SignIn to Nfield API
 */
function signIn (defOptions, credentials, callback) {
  var options = {
    method : 'POST',
    uri : 'v1/SignIn',
    json : credentials
  };

  extend(true, options, defOptions);
  return request(options).nodeify(callback);
}

/**
 * Wrapper function for all Nfield API requests that checks if API token is not outdated, and refreshes it otherwise
 */
function requestWithTokenCheck (defOptions, credentials, token, options, callback) {
  var returnedPromise;

  options.headers = options.headers || {};
  extend(true, options, defOptions);

  if (Date.now() - token.Timestamp > tokenUpdateTime) {
    returnedPromise = signIn(defOptions, credentials).then(function (data) {
      if (data[0].statusCode !== 200) {
        throw new Error(`${data[0].statusCode}: ${data[0].body.Message}`);
      } else {
        token.AuthenticationToken = data[0].body.AuthenticationToken;
        token.Timestamp = Date.now();
        options.headers.Authorization = `Basic ${token.AuthenticationToken}`;
        return request(options);
      }
    });
  } else {
    options.headers.Authorization = `Basic ${token.AuthenticationToken}`;
    returnedPromise = request(options);
  }

  returnedPromise.nodeify(callback);
  return returnedPromise;
}

/**
 * Return Nfield survey status
 *
 * {@link https://api.nfieldmr.com/help/api/get-v1-surveys-surveyid-fieldwork-status}
 */
function getSurveyStatus (defOptions, credentials, token, surveyId, callback) {
  var options = {
    method : 'GET',
    uri : `v1/Surveys/${surveyId}/Fieldwork/Status`
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Start Nfield survey
 *
 * {@link https://api.nfieldmr.com/help/api/put-v1-surveys-surveyid-fieldwork-start}
 */
function startSurvey (defOptions, credentials, token, surveyId, callback) {
  var options = {
    method : 'PUT',
    uri : `v1/Surveys/${surveyId}/Fieldwork/Start`
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Stop Nfield survey
 *
 * Has a 'magical' parameter called TerminateRunningInterviews, that never explained in any part of Nfield API documentation
 * and doesn't seem to affect anything when changed, but present here for the sake of making requests as close to API docs as possible (defaults to 'false')
 *
 * {@link https://api.nfieldmr.com/help/api/put-v1-surveys-surveyid-fieldwork-stop}
 */
function stopSurvey (defOptions, credentials, token, requestParams, callback) {
  var options = {
    method : 'PUT',
    uri : `v1/Surveys/${requestParams.SurveyId}/Fieldwork/Stop`,
    json : {
      'TerminateRunningInterviews' : requestParams.TerminateRunningInterviews || false
    }
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Retrieve an array of Default Texts (or a specific Default Text) for domain
 *
 * {@link https://api.nfieldmr.com/help/api/get-v1-defaulttexts}
 * {@link https://api.nfieldmr.com/help/api/get-v1-defaulttexts-translationkey}
 */
function getDefaultTexts (defOptions, credentials, token, translationKey, callback) {
  var options;

  translationKey = translationKey || '';

  if (typeof translationKey === 'function') {
    callback = translationKey;
    translationKey = '';
  }

  options = {
    method : 'GET',
    uri : `v1/DefaultTexts/${translationKey}`,
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Retrieve an array of Survey Translations (or a specific Survey Translation) for specific language in survey
 *
 * {@link https://api.nfieldmr.com/help/api/get-v1-surveys-surveyid-languages-languageid-translations}
 * {@link https://api.nfieldmr.com/help/api/get-v1-surveys-surveyid-languages-languageid-translations-translationkey}
 */
function getSurveyTranslations (defOptions, credentials, token, requestParams, callback) {
  var options;

  requestParams.TranslationKey = requestParams.TranslationKey || '';

  options = {
    method : 'GET',
    uri : `v1/Surveys/${requestParams.SurveyId}/Languages/${requestParams.LanguageId}/Translations/${requestParams.TranslationKey}`,
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Add new translation
 *
 * {@link https://api.nfieldmr.com/help/api/post-v1-surveys-surveyid-languages-languageid-translations}
 */
function addSurveyTranslations (defOptions, credentials, token, requestParams, callback) {
  var options = {
    method : 'POST',
    uri : `v1/Surveys/${requestParams.SurveyId}/Languages/${requestParams.LanguageId}/Translations`,
    json : {
      'Name' : requestParams.Name,
      'Text' : requestParams.Text
    }
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Update existing translation
 *
 * {@link https://api.nfieldmr.com/help/api/put-v1-surveys-surveyid-languages-languageid-translations}
 */
function updateSurveyTranslations (defOptions, credentials, token, requestParams, callback) {
  var options = {
    method : 'PUT',
    uri : `v1/Surveys/${requestParams.SurveyId}/Languages/${requestParams.LanguageId}/Translations`,
    json : {
      'Name' : requestParams.Name,
      'Text' : requestParams.Text
    }
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Remove existing translation
 *
 * {@link https://api.nfieldmr.com/help/api/delete-v1-surveys-surveyid-languages-languageid-translations-translationkey}
 */
function removeSurveyTranslations (defOptions, credentials, token, requestParams, callback) {
  var options = {
    method : 'DELETE',
    uri : `v1/Surveys/${requestParams.SurveyId}/Languages/${requestParams.LanguageId}/Translations/${requestParams.TranslationKey}`,
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Retrieve an array of languages (or a specific language) for survey
 *
 * {@link https://api.nfieldmr.com/help/api/get-v1-surveys-surveyid-languages}
 * {@link https://api.nfieldmr.com/help/api/get-v1-surveys-surveyid-languages-languageid}
 */
function getSurveyLanguages (defOptions, credentials, token, requestParams, callback) {
  var options;

  requestParams.LanguageId = requestParams.LanguageId || '';

  options = {
    method : 'GET',
    uri : `v1/Surveys/${requestParams.SurveyId}/Languages/${requestParams.LanguageId}`,
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Add new language to survey
 *
 * {@link https://api.nfieldmr.com/help/api/post-v1-surveys-surveyid-languages}
 */
function addSurveyLanguages (defOptions, credentials, token, requestParams, callback) {
  var options = {
    method : 'POST',
    uri : `v1/Surveys/${requestParams.SurveyId}/Languages`,
    json : {
      'Name' : requestParams.Name
    }
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Update existing language
 *
 * {@link https://api.nfieldmr.com/help/api/put-v1-surveys-surveyid-languages}
 */
function updateSurveyLanguages (defOptions, credentials, token, requestParams, callback) {
  var options = {
    method : 'PUT',
    uri : `v1/Surveys/${requestParams.SurveyId}/Languages`,
    json : {
      'Id' : requestParams.Id,
      'Name' : requestParams.Name
    }
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Remove existing language
 *
 * {@link https://api.nfieldmr.com/help/api/delete-v1-surveys-surveyid-languages-languageid}
 */
function removeSurveyLanguages (defOptions, credentials, token, requestParams, callback) {
  var options = {
    method : 'DELETE',
    uri : `v1/Surveys/${requestParams.SurveyId}/Languages/${requestParams.LanguageId}`,
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

module.exports = {
  signIn : signIn,
  getSurveyStatus : getSurveyStatus,
  startSurvey : startSurvey,
  stopSurvey : stopSurvey,
  getDefaultTexts : getDefaultTexts,
  getSurveyTranslations : getSurveyTranslations,
  addSurveyTranslations : addSurveyTranslations,
  updateSurveyTranslations : updateSurveyTranslations,
  removeSurveyTranslations : removeSurveyTranslations,
  getSurveyLanguages : getSurveyLanguages,
  addSurveyLanguages : addSurveyLanguages,
  updateSurveyLanguages : updateSurveyLanguages,
  removeSurveyLanguages : removeSurveyLanguages
};
<|endoftext|>


<commit_before>'use strict';

import React, {
  AsyncStorage,
  Component,
  Dimensions,
  RefreshControl,
  View,
} from 'react-native';

import {SegmentedControls} from 'react-native-radio-buttons';
import Firebase from 'firebase';

import GridView from '../components/grid-view';
import Post from '../components/post';
import Profile from "../scenes/profile";
import SceneStyles from '../styles/scene-styles';
import SearchBar from '../components/search-bar';
import SmallPost from '../components/small-post';
import TitleBar from '../components/title-bar';

let database = new Firebase("poopapp1.firebaseio.com");

class Home extends Component {
  constructor(props) {
    super(props);
    this.state = {
      dataSourceAll: [],
      dataSourceFollowing: [],
      open: false,
      selectedOption: 'All',
    };
  }

  componentDidMount() {
    AsyncStorage.getItem('user_data', (error, result) => {
      this.setState({
        userId: JSON.parse(result).uid,
      });
    });
    this.queryDataAll();
    this.queryDataFollowing();
  }

  renderRowAll(post) {
    return (
      <SmallPost
        id = {post}
        navigator = {this.props.navigator}
      />
    );
  }

  renderRowFollowing(post) {
    return (
      <Post
        id = {post}
        navigator = {this.props.navigator}
      />
    );
  }

  queryDataAll() {
    var myBlob = [];
    var self = this;

    // this section loads the postIDs into myBlob and pushes them to dataSourceAll
    database.child("posts").once("value", function(snapshot) {
      snapshot.forEach(function(postSnapshot) {
        myBlob.push(postSnapshot);
      });
      myBlob.sort((a, b) => {
        return b.val().date - a.val().date;
      });
      self.setState({dataSourceAll: myBlob});
    });
  }

  queryDataFollowing() {
    var myBlob = [];
    var self = this;

    // this section loads the postIDs into myBlob and pushes them to dataSourceFollowing
    database.once("value", function(snapshot) {
      var followingList = snapshot.child("users/" + self.state.userId + "/followingList");
      followingList.forEach(function(followingSnapshot) {
        var follower = snapshot.child("users/" + followingSnapshot.val().userId);
        if(follower.hasChild("postList")) {
          var postList = follower.child("postList");
          postList.forEach(function(postSnapshot) {
            var postId = postSnapshot.val().postId;
            var postData = snapshot.child("posts/" + postId);
            myBlob.push(postData);
          });
        }
      });
      myBlob.sort((a, b) => {
        return b.val().date - a.val().date;
      });
      self.setState({dataSourceFollowing: myBlob});
    });
  }

  renderFeed() {
    if(this.state.selectedOption === "All") {
      return (
        <GridView
          dataSource = {this.state.dataSourceAll}
          onRefresh = {this.queryDataAll.bind(this)}
          renderRow = {this.renderRowAll.bind(this)}
        />
      );
    } else {
      return (
        <GridView
          dataSource = {this.state.dataSourceFollowing}
          onRefresh = {this.queryDataFollowing.bind(this)}
          renderRow = {this.renderRowFollowing.bind(this)}
        />
      );
    }
  }

  setSelectedOption(selectedOption) {
    this.setState({
      selectedOption
    })
  }

  render() {
    const options = [
      "All",
      "Following"
    ];

    return (
      <View style = {{flex: 1, backgroundColor: '#f3f3f3'}}>
        <TitleBar
          navigator = {this.props.navigator}
          text = "Home"
        />
        <SegmentedControls
          backTint = {'#F26D6A'}
          onSelection = {this.setSelectedOption.bind(this)}
          options = {options}
          selectedOption = {this.state.selectedOption}
          selectedTint = {'#F26D6A'}
          tint = {'white'}
        />
        <SearchBar/>
        {this.renderFeed()}
      </View>
    );
  }
}

module.exports = Home;
<commit_msg>renamed home to fapp<commit_after>'use strict';

import React, {
  AsyncStorage,
  Component,
  Dimensions,
  RefreshControl,
  View,
} from 'react-native';

import {SegmentedControls} from 'react-native-radio-buttons';
import Firebase from 'firebase';

import GridView from '../components/grid-view';
import Post from '../components/post';
import Profile from "../scenes/profile";
import SceneStyles from '../styles/scene-styles';
import SearchBar from '../components/search-bar';
import SmallPost from '../components/small-post';
import TitleBar from '../components/title-bar';

let database = new Firebase("poopapp1.firebaseio.com");

class Home extends Component {
  constructor(props) {
    super(props);
    this.state = {
      dataSourceAll: [],
      dataSourceFollowing: [],
      open: false,
      selectedOption: 'All',
    };
  }

  componentDidMount() {
    AsyncStorage.getItem('user_data', (error, result) => {
      this.setState({
        userId: JSON.parse(result).uid,
      });
    });
    this.queryDataAll();
    this.queryDataFollowing();
  }

  renderRowAll(post) {
    return (
      <SmallPost
        id = {post}
        navigator = {this.props.navigator}
      />
    );
  }

  renderRowFollowing(post) {
    return (
      <Post
        id = {post}
        navigator = {this.props.navigator}
      />
    );
  }

  queryDataAll() {
    var myBlob = [];
    var self = this;

    // this section loads the postIDs into myBlob and pushes them to dataSourceAll
    database.child("posts").once("value", function(snapshot) {
      snapshot.forEach(function(postSnapshot) {
        myBlob.push(postSnapshot);
      });
      myBlob.sort((a, b) => {
        return b.val().date - a.val().date;
      });
      self.setState({dataSourceAll: myBlob});
    });
  }

  queryDataFollowing() {
    var myBlob = [];
    var self = this;

    // this section loads the postIDs into myBlob and pushes them to dataSourceFollowing
    database.once("value", function(snapshot) {
      var followingList = snapshot.child("users/" + self.state.userId + "/followingList");
      followingList.forEach(function(followingSnapshot) {
        var follower = snapshot.child("users/" + followingSnapshot.val().userId);
        if(follower.hasChild("postList")) {
          var postList = follower.child("postList");
          postList.forEach(function(postSnapshot) {
            var postId = postSnapshot.val().postId;
            var postData = snapshot.child("posts/" + postId);
            myBlob.push(postData);
          });
        }
      });
      myBlob.sort((a, b) => {
        return b.val().date - a.val().date;
      });
      self.setState({dataSourceFollowing: myBlob});
    });
  }

  renderFeed() {
    if(this.state.selectedOption === "All") {
      return (
        <GridView
          dataSource = {this.state.dataSourceAll}
          onRefresh = {this.queryDataAll.bind(this)}
          renderRow = {this.renderRowAll.bind(this)}
        />
      );
    } else {
      return (
        <GridView
          dataSource = {this.state.dataSourceFollowing}
          onRefresh = {this.queryDataFollowing.bind(this)}
          renderRow = {this.renderRowFollowing.bind(this)}
        />
      );
    }
  }

  setSelectedOption(selectedOption) {
    this.setState({
      selectedOption
    })
  }

  render() {
    const options = [
      "All",
      "Following"
    ];

    return (
      <View style = {{flex: 1, backgroundColor: '#f3f3f3'}}>
        <TitleBar
          navigator = {this.props.navigator}
          text = "Fapp"
        />
        <SegmentedControls
          backTint = {'#F26D6A'}
          onSelection = {this.setSelectedOption.bind(this)}
          options = {options}
          selectedOption = {this.state.selectedOption}
          selectedTint = {'#F26D6A'}
          tint = {'white'}
        />
        <SearchBar/>
        {this.renderFeed()}
      </View>
    );
  }
}

module.exports = Home;
<|endoftext|>


<commit_before>/* eslint-disable no-console */
import chai from 'chai';
import dirty from 'dirty-chai';
import sinonChai from 'sinon-chai';
import sinon from 'sinon';
import fs from 'fs';
import path from 'path';
import shell from 'shelljs';
import electron from 'electron';
import { Application } from 'spectron';
import mockery from 'mockery';

import paths from '../helpers/paths';
import meteorDesktop from '../../lib/index';

shell.config.fatal = true;

chai.use(sinonChai);
chai.use(dirty);
const { describe, it } = global;
const { expect } = chai;

/**
 * This is first experimental integration test.
 */

let appDir = '';

/**
 * Waits until a promise from a function finally returns true.
 * @param {Function} functionReturningPromise - function to test
 * @param {number}   ms                       - expiration timeout in milliseconds
 * @returns {Promise}
 */
function waitFor(functionReturningPromise, ms = 10000) {
    return new Promise((resolve, reject) => {
        let invokerTimeout;
        let timeout;
        const invokeFunction = () =>
            functionReturningPromise()
                .then((result) => {
                    console.log(result);
                    if (result) {
                        clearTimeout(invokerTimeout);
                        clearTimeout(timeout);
                        resolve();
                    } else {
                        invokerTimeout = setTimeout(invokeFunction, 500);
                    }
                })
                .catch(() => {
                    invokerTimeout = setTimeout(invokeFunction, 500);
                });
        invokeFunction();
        timeout = setTimeout(() => {
            clearTimeout(invokerTimeout);
            reject('timeout expired on waitFor');
        }, ms);
    });
}

/**
 * Waits for the app to load and appear.
 * @param {Object} app - test app
 * @returns {{app: (Application|*), window: *}}
 */
async function waitForApp(app) {
    await app.client.waitUntilWindowLoaded();
    const window = app.browserWindow;
    // Wait for the main window for max 30seconds. Adjust to your app.
    await waitFor(window.isVisible, 30000);
    expect(await app.client.getWindowCount()).to.equal(1);
    await app.client.waitUntil(
        async () => await app.client.execute(
            () => document.readyState === 'complete'
        )
    );
    return { app, window };
}

describe('desktop', () => {
    let MeteorDesktop;

    before(() => {
        appDir = path.join(paths.testsIntegrationTmpPath, 'test-desktop');
    });

    beforeEach(() => {
        try {
            fs.unlinkSync('meteor.log');
        } catch (e) {
            // No worries...
        }
    });

    describe('add to scripts', () => {
        let exitStub;

        before(() => {
            mockery.registerMock('path', {
                resolve: dir => dir,
                join: () => `${appDir}${path.sep}package.json`
            });
            mockery.enable({
                warnOnReplace: false,
                warnOnUnregistered: false
            });
        });
        after(() => {
            mockery.deregisterMock('path');
            mockery.disable();
            exitStub.restore();
        });

        it('should add a `desktop` entry in package.json', () => {
            exitStub = sinon.stub(process, 'exit');
            require('../../lib/scripts/addToScripts'); // eslint-disable-line
            const packageJson = JSON.parse(fs.readFileSync(path.join(appDir, 'package.json'), 'utf8'));
            expect(exitStub).to.have.callCount(0);
            expect(packageJson.scripts.desktop).to.be.equal('meteor-desktop');
        });
    });

    describe('build with params --scaffold -b', () => {
        let exitStub;
        let app;

        after(async () => {
            exitStub.restore();
            if (app && app.isRunning()) {
                await app.stop();
            }
        });

        it('should create a build', async () => {
            exitStub = sinon.stub(process, 'exit', () => {
                try {
                    console.log(fs.readFileSync('meteor.log', 'utf8'));
                } catch (e) {
                    // Nothing to do.
                }
                process.exit(1);
            });
            // Load plugins directly from the package instead of those published to atmosphere.
            process.env.METEOR_PACKAGE_DIRS = path.resolve(path.join(__dirname, '..', '..', 'plugins'));
            process.env.MONGO_URL = 'none';
            MeteorDesktop = meteorDesktop(
                appDir,
                appDir,
                {
                    ddpUrl: 'http://127.0.0.1:3080',
                    scaffold: true,
                    build: true,
                    skipMobileBuild: !!process.env.TRAVIS,
                    forceCordovaBuild: !!process.env.TRAVIS
                }
            );
            // Build the app.
            await MeteorDesktop.build();

            // Run it.
            app = new Application({
                path: electron,
                args: [path.join(appDir, '.meteor', 'desktop-build')],
                requireName: 'electronRequire',
                env: {
                    NODE_ENV: 'test',
                    ELECTRON_ENV: 'test',
                    METEOR_DESKTOP_NO_SPLASH_SCREEN: 1
                }
            });
            await app.start();
            await waitForApp(app);

            const title = await app.client.getTitle();
            expect(title).to.equal('test-desktop');
            const text = await app.client.getText('h1');
            expect(text).to.equal('Welcome to Meteor!');
        }).timeout(10 * 60000);
    });
});
<commit_msg>Increase timeout for ChromeDriver<commit_after>/* eslint-disable no-console */
import chai from 'chai';
import dirty from 'dirty-chai';
import sinonChai from 'sinon-chai';
import sinon from 'sinon';
import fs from 'fs';
import path from 'path';
import shell from 'shelljs';
import electron from 'electron';
import { Application } from 'spectron';
import mockery from 'mockery';

import paths from '../helpers/paths';
import meteorDesktop from '../../lib/index';

shell.config.fatal = true;

chai.use(sinonChai);
chai.use(dirty);
const { describe, it } = global;
const { expect } = chai;

/**
 * This is first experimental integration test.
 */

let appDir = '';

/**
 * Waits until a promise from a function finally returns true.
 * @param {Function} functionReturningPromise - function to test
 * @param {number}   ms                       - expiration timeout in milliseconds
 * @returns {Promise}
 */
function waitFor(functionReturningPromise, ms = 10000) {
    return new Promise((resolve, reject) => {
        let invokerTimeout;
        let timeout;
        const invokeFunction = () =>
            functionReturningPromise()
                .then((result) => {
                    console.log(result);
                    if (result) {
                        clearTimeout(invokerTimeout);
                        clearTimeout(timeout);
                        resolve();
                    } else {
                        invokerTimeout = setTimeout(invokeFunction, 500);
                    }
                })
                .catch(() => {
                    invokerTimeout = setTimeout(invokeFunction, 500);
                });
        invokeFunction();
        timeout = setTimeout(() => {
            clearTimeout(invokerTimeout);
            reject('timeout expired on waitFor');
        }, ms);
    });
}

/**
 * Waits for the app to load and appear.
 * @param {Object} app - test app
 * @returns {{app: (Application|*), window: *}}
 */
async function waitForApp(app) {
    await app.client.waitUntilWindowLoaded();
    const window = app.browserWindow;
    // Wait for the main window for max 30seconds. Adjust to your app.
    await waitFor(window.isVisible, 30000);
    expect(await app.client.getWindowCount()).to.equal(1);
    await app.client.waitUntil(
        async () => await app.client.execute(
            () => document.readyState === 'complete'
        )
    );
    return { app, window };
}

describe('desktop', () => {
    let MeteorDesktop;

    before(() => {
        appDir = path.join(paths.testsIntegrationTmpPath, 'test-desktop');
    });

    beforeEach(() => {
        try {
            fs.unlinkSync('meteor.log');
        } catch (e) {
            // No worries...
        }
    });

    describe('add to scripts', () => {
        let exitStub;

        before(() => {
            mockery.registerMock('path', {
                resolve: dir => dir,
                join: () => `${appDir}${path.sep}package.json`
            });
            mockery.enable({
                warnOnReplace: false,
                warnOnUnregistered: false
            });
        });
        after(() => {
            mockery.deregisterMock('path');
            mockery.disable();
            exitStub.restore();
        });

        it('should add a `desktop` entry in package.json', () => {
            exitStub = sinon.stub(process, 'exit');
            require('../../lib/scripts/addToScripts'); // eslint-disable-line
            const packageJson = JSON.parse(fs.readFileSync(path.join(appDir, 'package.json'), 'utf8'));
            expect(exitStub).to.have.callCount(0);
            expect(packageJson.scripts.desktop).to.be.equal('meteor-desktop');
        });
    });

    describe('build with params --scaffold -b', () => {
        let exitStub;
        let app;

        after(async () => {
            exitStub.restore();
            if (app && app.isRunning()) {
                await app.stop();
            }
        });

        it('should create a build', async () => {
            exitStub = sinon.stub(process, 'exit', () => {
                try {
                    console.log(fs.readFileSync('meteor.log', 'utf8'));
                } catch (e) {
                    // Nothing to do.
                }
                process.exit(1);
            });
            // Load plugins directly from the package instead of those published to atmosphere.
            process.env.METEOR_PACKAGE_DIRS = path.resolve(path.join(__dirname, '..', '..', 'plugins'));
            process.env.MONGO_URL = 'none';
            MeteorDesktop = meteorDesktop(
                appDir,
                appDir,
                {
                    ddpUrl: 'http://127.0.0.1:3080',
                    scaffold: true,
                    build: true,
                    skipMobileBuild: !!process.env.TRAVIS,
                    forceCordovaBuild: !!process.env.TRAVIS
                }
            );
            // Build the app.
            await MeteorDesktop.build();

            // Run it.
            app = new Application({
                path: electron,
                args: [path.join(appDir, '.meteor', 'desktop-build')],
                requireName: 'electronRequire',
                startTimeout: 30000,
                env: {
                    NODE_ENV: 'test',
                    ELECTRON_ENV: 'test',
                    METEOR_DESKTOP_NO_SPLASH_SCREEN: 1
                }
            });
            await app.start();
            await waitForApp(app);

            const title = await app.client.getTitle();
            expect(title).to.equal('test-desktop');
            const text = await app.client.getText('h1');
            expect(text).to.equal('Welcome to Meteor!');
        }).timeout(10 * 60000);
    });
});
<|endoftext|>


<commit_before>var gameModule = angular.module('app', []).
	controller("gameTheoryControl", function gameTheoryControl($scope){

	    	function number(num){
	    		this.name = num;
	    		this.selected = false;
	    		return this;
	    	}

	    	$scope.numbers=[];
	    	$scope.solutions=[];

	    	function solution(x, parts){
	    		this.number = x;
	    		this.parts = parts;
	    	}

	    	$scope.amount = 10;

	    	$scope.newNumbers = function(){
			$scope.numbers = [];

			intAmount = parseInt($scope.amount);

			for(var i = 1; i < intAmount + 1; i++){
				$scope.numbers.push(new number(i));
			}
		}

		$scope.deleteSelected = function(){
			for(var num in $scope.numbers){
				if($scope.numbers[num].selected){
					$scope.numbers.splice(num, 1);
				}
			}
		}

		function sumOf(parts){
			var num = 0;
			for(var i = 0; i < parts.length; i++){
				num += parts[i];
			}
			return num;
		}

		function getSmallerNumbers(number){
			var newArray = [];
			for(var i = 0; i < $scope.numbers.length; i++){
				if($scope.numbers[i].name <= number){
					newArray.push($scope.numbers[i].name);
				}
				else{
					break;
				}
			}
			return newArray;
		}

		function getCombinations(parts) {
		  var result = [];
		  var f = function(firstParts, parts) {
		    for (var i = 0; i < parts.length; i++) {

		      result.push(firstParts.concat(parts[i]));
		      console.log(parts[i+1]);
		      f(firstParts.concat(parts[i]), parts.splice(i + 1));

		    }
		  }
		  f([], parts);
		  return result;
		}

		$scope.getSolutions = function(){
			$scope.solutions = [];
			var calculable = true;
			var currentNumber = 1;

			while(calculable){
				parts = [];

				var solved = false;

				var smallerNums = getSmallerNumbers(currentNumber);

				if(smallerNums.length == 0){
					calculable = false; break;
				}

				var combinations = getCombinations(smallerNums);

					for(var i = 0; i < combinations.length; i++){
						if(sumOf(combinations[i]) == currentNumber){
							$scope.solutions.push(new solution(currentNumber, combinations[i]));
							solved = true;
						}
					}

				if(!solved){
					calculable = false; break;
				}

				currentNumber++;
			}
		}

		$scope.newNumbers();

	});
<commit_msg>Update gameTheoryController.js<commit_after>var gameModule = angular.module('app', []).
	controller("gameTheoryControl", function gameTheoryControl($scope){

	    	function number(num){
	    		this.name = num;
	    		this.selected = false;
	    		return this;
	    	}

	    	$scope.numbers=[];
	    	$scope.solutions=[];

	    	function solution(x, parts){
	    		this.number = x;
	    		this.parts = parts;
	    	}

	    	$scope.amount = 10;

	    	$scope.newNumbers = function(){
			$scope.numbers = [];

			intAmount = parseInt($scope.amount);

			for(var i = 1; i < intAmount + 1; i++){
				$scope.numbers.push(new number(i));
			}
		}

		$scope.deleteSelected = function(){
			for(var num in $scope.numbers){
				if($scope.numbers[num].selected){
					$scope.numbers.splice(num, 1);
				}
			}
		}

		function sumOf(parts){
			var num = 0;
			for(var i = 0; i < parts.length; i++){
				num += parts[i];
			}
			return num;
		}

		function getSmallerNumbers(number){
			var newArray = [];
			for(var i = 0; i < $scope.numbers.length; i++){
				if($scope.numbers[i].name <= number){
					newArray.push($scope.numbers[i].name);
				}
				else{
					break;
				}
			}
			return newArray;
		}

		var getCombinations = function(firstParts, parts, solutions) {
		    for (var i = 0; i < parts.length; i++) {
		      solutions.push(firstParts.concat(parts[i]));
		      solutions.concat(getCombinations(firstParts.concat(parts[i]), parts.splice(i + 1)));
		    }
		   return solutions;
		}

		$scope.getSolutions = function(){
			$scope.solutions = [];
			var calculable = true;
			var currentNumber = 1;

			while(calculable){
				parts = [];

				var solved = false;

				var smallerNums = getSmallerNumbers(currentNumber);

				if(smallerNums.length == 0){
					calculable = false; break;
				}

				var combinations = getCombinations([], smallerNums, []);

					for(var i = 0; i < combinations.length; i++){
						if(sumOf(combinations[i]) == currentNumber){
							$scope.solutions.push(new solution(currentNumber, combinations[i]));
							solved = true;
						}
					}

				if(!solved){
					calculable = false; break;
				}

				currentNumber++;
			}
		}

		$scope.newNumbers();

	});
<|endoftext|>


<commit_before>/**
 * BikeshedViewer
 * @flow
 */
// import cn from './BikeshedViewer.css'
import type { ReactElement } from 'react'
// import { Card, CardActions, CardBody } from '../Card'
import React, { Component, PropTypes } from 'react'
// import { Icon, ICONS } from '@components/Icon'
// import { Button } from '@components/Button'
import { Subhead } from '@components/Text'
import Relay from 'react-relay'

/**
 * BikeshedViewer page
 */
export class BikeshedViewer extends Component {
  constructor (props: Object) {
    super(props)
    this.state = {}
  }

  render (): ReactElement {
    console.log('this.props', this.props)
    console.log('bikeshed', this.props.bikeshed)
    return (

        <Subhead>
          BikeshedViewer
        </Subhead>

          bikeshed~

    )
  }
}
BikeshedViewer.propTypes = {
  bikeshed: PropTypes.object.isRequired
}

/**
 * Connect BikeshedViewer with Relay
 */
export const BikeshedViewerContainer = Relay.createContainer(BikeshedViewer, {
  fragments: {
    bikeshed () {
      return Relay.QL`
        fragment on Bikeshed {
          description,
          voteCount,
          hasVoted,
          bikes {
            score,
            rating,
            fullUrl,
          }
        }
      `
    }
  }
})
<commit_msg>Add simple bikeshed viewer image grid<commit_after>/**
 * BikeshedViewer
 * @flow
 */
// import { Card, CardActions, CardBody } from '../Card'
// import { Icon, ICONS } from '@components/Icon'
// import { Button } from '@components/Button'
import { Page, PageSubhead } from '@components/Page'
import React, { Component, PropTypes } from 'react'
import { Motion, spring } from 'react-motion'
import partition from 'linear-partitioning'
import type { ReactElement } from 'react'
import debounce from 'lodash.debounce'
import cn from './BikeshedViewer.css'
import Relay from 'react-relay'

const LINE_HEIGHT = 220
const IMAGE_PADDING = 2

function getRatioSum (list) {
  return list.reduce((ratioSum, { ratio }) => ratioSum + ratio, 0)
}

function calculateRowHeight (list, maxWidth) {
  const ratioSum = getRatioSum(list)
  return Math.ceil((maxWidth - (IMAGE_PADDING * (list.length - 1))) / ratioSum)
}

function calculatePositions (list, maxWidth) {
  const ratioSum = getRatioSum(list)
  const rowCount = Math.max(
    Math.min(
      ((ratioSum * LINE_HEIGHT) / maxWidth) | 0,
      list.length
    ),
    list.length > 4 ? 2 : 1
  )

  const ratioList = list.map(item => item.ratio)
  return partition(ratioList, rowCount)
  .reduce((result, items) => {
    const slice = list.slice(result.length, items.length)
    const height = calculateRowHeight(slice, maxWidth)

    // This will get us the last result from the previous row
    const lastResult = result[result.length - 1]
    const top = lastResult
      ? (lastResult.top + lastResult.height + IMAGE_PADDING)
      : 0
    const bottom = top + height

    // Get the styles for each item in this row
    const row = slice.reduce((row, item, idx) => {
      const width = (height * item.ratio) | 0
      const lastItem = row[row.length - 1]
      const left = lastItem
        ? lastItem.right + IMAGE_PADDING
        : 0
      const right = left + width
      return [...row, { top, right, bottom, left, width, height }]
    }, [])

    return [...result, ...row]
  }, [])
}

function springifyStyles (styleList) {
  return styleList.map(style =>
    Object.entries(style).reduce((style, [name, value]) =>
      ({ ...style, [name]: spring(value) })
    , {})
  )
}

/**
 * BikeshedViewer page
 */
export class BikeshedViewer extends Component {
  constructor (props: Object) {
    super(props)
    this.state = {
      bikePositions: []
    }
    this.refreshData = this.refreshData.bind(this)
    this.getBikeStyles = this.getBikeStyles.bind(this)
    this.debouncedRefresh = debounce(this.refreshData, 100)
  }

  componentDidMount () {
    this.refreshData()
    window.addEventListener('resize', this.debouncedRefresh)
  }

  componentWillUnmount () {
    window.removeEventListener('resize', this.debouncedRefresh)
  }

  refreshData () {
    clearTimeout(this.refreshDataTimeout)
    const { bikeshed, relay } = this.props
    if (bikeshed.status === 'READY') {
      this.getBikeStyles()
    } else if (bikeshed.status !== 'ERROR') {
      this.refreshDataTimeout = setTimeout(() => {
        relay.forceFetch({}, readyState => {
          if (readyState.ready && readyState.done) {
            this.refreshData()
          }
        })
      }, 200)
    }
  }

  getBikeStyles () {
    const { bikeshed } = this.props
    const maxWidth = this.refs.bikesContainer.offsetWidth
    const bikePositions = calculatePositions(bikeshed.bikes, maxWidth)
    this.setState({
      bikePositions: bikePositions
    })
  }

  render (): ReactElement {
    const { bikeshed } = this.props
    const { bikePositions } = this.state
    const styleList = springifyStyles(bikePositions)

    return (
      <Page>
        <PageSubhead>
          BikeshedViewer
        </PageSubhead>

            Status is: {bikeshed.status}

          {bikeshed.status === 'READY' && (
            styleList
            .map((style, idx) => {
              const bike = bikeshed.bikes[idx]
              return (
                <Motion
                  key={idx}
                  style={style}
                >
                  {interpolatedStyle => (

                  )}
                </Motion>
              )
            })
          )}

      </Page>
    )
  }
}
BikeshedViewer.propTypes = {
  bikeshed: PropTypes.object.isRequired,
  relay: PropTypes.object.isRequired
}

/**
 * Connect BikeshedViewer with Relay
 */
export const BikeshedViewerContainer = Relay.createContainer(BikeshedViewer, {
  fragments: {
    bikeshed () {
      return Relay.QL`
        fragment on Bikeshed {
          description,
          voteCount,
          hasVoted,
          status,
          bikes {
            score,
            rating,
            fullUrl,
            height,
            width,
            ratio,
          }
        }
      `
    }
  }
})
<|endoftext|>


<commit_before>import * as d3 from 'd3';
import Timer from './timer';

export default class {
	constructor(id, svg, duration, width, thickness){
		this.id = id;
		this.svg = svg;
		this.width = width;
		this.radius = this.width / 2;
		this.thickness = thickness;

		const pie = d3.pie().sort(null);

		const arc = d3.arc()
		.innerRadius(this.radius - thickness)
		.outerRadius(this.radius);

		this.draw = function(){
			g.datum(this.timerData);
			path.data(pie)
				.attr("d", arcEased);
		}
		this.start = function(){
			this.timer.start();
		}

		this.timer = new Timer(duration, ::this.draw, ::this.draw);

		const arcEased = function(d, i){
			var interpolate = d3.interpolate(this._current, d);
			return arc(interpolate(d3.easeCubic(oneSecondScaleIn(d.value))));
		}

		const data = this.timerData;
		const oneSecondScaleIn = d3.scaleLinear().domain([0, 1000]).range([0, 1]);
		const colorScale = d3.scaleLinear().domain([0, data.length - 1]).range([.8, .2]);
		const color = function(index){
			return d3.interpolateInferno(colorScale(index));
		};

		const g = this.svg
		.append("g")
		.attr("transform", "translate(" + this.width / 2 + "," + this.width / 2 + ")");

		const path = g.datum(data).selectAll("path")
		.data(pie)
		.enter().append("path")
		.attr("fill", function(d, i) { return color(i); })
		.attr("d", arc)
	    .each(function(d) { this._current = d; }); // store the initial angles
	}

	get elapsed(){
		if (!this.running){
			return 0;
		}
		return Date.now() - this.startedAt;
	}

	get timerData(){
		return this.constructor.generateTimerData(this.timer.duration, this.timer.elapsed);
	}

	/**
	 * For a duration of n seconds, generates an array with n+1 numbers initialized to 0.
	 * Number of elapsed milliseconds will be reflected with appropriate number of array indices
	 * set to 1000, with last item in array holding remaining duration.
	 */
	static generateTimerData(duration, elapsed){
		console.log('duration, elapsed', duration, elapsed);
		const numIndices = Math.floor(duration / 1000);
		const numIndicesCompleted = Math.floor(elapsed / 1000);
		let result = Array(numIndices + 1).fill(0);
		for(let i = 0; i < numIndicesCompleted; i++){
			result[i] = 1000;
		}
		result[numIndicesCompleted] = elapsed % 1000;
		result[result.length - 1] = duration - elapsed; //(numIndices - numIndicesElapsed) * 1000;
		console.log('numIndices', numIndices, 'numIndicesCompleted', numIndicesCompleted);
		console.log(result);
		return result;
	}
}<commit_msg>preventing donut graph from drawing extra space if tab is inactive for a period longer than timer duration<commit_after>import * as d3 from 'd3';
import Timer from './timer';

export default class {
	constructor(id, svg, duration, width, thickness){
		this.id = id;
		this.svg = svg;
		this.width = width;
		this.radius = this.width / 2;
		this.thickness = thickness;

		const pie = d3.pie().sort(null);

		const arc = d3.arc()
		.innerRadius(this.radius - thickness)
		.outerRadius(this.radius);

		this.draw = function(){
			g.datum(this.timerData);
			path.data(pie)
				.attr("d", arcEased);
		}
		this.start = function(){
			this.timer.start();
		}

		this.timer = new Timer(duration, ::this.draw, ::this.draw);

		const arcEased = function(d, i){
			var interpolate = d3.interpolate(this._current, d);
			return arc(interpolate(d3.easeCubic(oneSecondScaleIn(d.value))));
		}

		const data = this.timerData;
		const oneSecondScaleIn = d3.scaleLinear().domain([0, 1000]).range([0, 1]);
		const colorScale = d3.scaleLinear().domain([0, data.length - 1]).range([.8, .2]);
		const color = function(index){
			return d3.interpolateInferno(colorScale(index));
		};

		const g = this.svg
		.append("g")
		.attr("transform", "translate(" + this.width / 2 + "," + this.width / 2 + ")");

		const path = g.datum(data).selectAll("path")
		.data(pie)
		.enter().append("path")
		.attr("fill", function(d, i) { return color(i); })
		.attr("d", arc)
	    .each(function(d) { this._current = d; }); // store the initial angles
	}

	get elapsed(){
		if (!this.running){
			return 0;
		}
		return Date.now() - this.startedAt;
	}

	get timerData(){
		return this.constructor.generateTimerData(this.timer.duration, this.timer.elapsed);
	}

	/**
	 * For a duration of n seconds, generates an array with n+1 numbers initialized to 0.
	 * Number of elapsed milliseconds will be reflected with appropriate number of array indices
	 * set to 1000, with last item in array holding remaining duration.
	 */
	static generateTimerData(duration, elapsed){
		console.log('duration, elapsed', duration, elapsed);
		const numIndices = Math.floor(duration / 1000);
		let numIndicesCompleted = Math.floor(elapsed / 1000);
		if (numIndicesCompleted > numIndices){
			numIndicesCompleted = numIndices;
		}
		let result = Array(numIndices + 1).fill(0);
		for(let i = 0; i < numIndicesCompleted; i++){
			result[i] = 1000;
		}
		result[numIndicesCompleted] = elapsed % 1000;

		result[result.length - 1] = duration - elapsed; //(numIndices - numIndicesElapsed) * 1000;
		console.log('numIndices', numIndices, 'numIndicesCompleted', numIndicesCompleted);
		console.log(result);
		return result;
	}
}<|endoftext|>


<commit_before>const CommandHandler = require('../util/msgUtil');
const Discordie = require('discordie');
const Events = Discordie.Events;

const util = require('../util/botUtil');

const config = require('../config');

/**
 *
 *
 * @param {IMessage} msg
 * @param {Discordie} client
 * @returns
 */
function respond (msg, client) {
	const msgText = msg.content;
	if (!msgText.startsWith(config.prefix)) return;
	// const sender = msg.member || msg.author; // IUser as a substitute for DMs
	const msgChannel = msg.channel;
	// const msgGuild = msg.guild;

	// const botUser = msg.isPrivate ? client.User : client.User.memberOf(msgGuild);

	const log = (s) => util.log(s);

	/**
	 * Sends a message
	 * @param {string} s Text
	 * @param {object} e Embed object
	 * @returns {Promise<{}>}
	 */
	const sendMessage = (s, e) => msgChannel.sendMessage(s, false, e);
	// const sendEmbed = (e) => sendMessage('', e);

	const command = msgText.slice(config.prefix.length);
	const handler = new CommandHandler(command);

	const addCommand = (c, f) => handler.addCommand(c, f);
	// const addCommandResponse = (c, r) => addCommand(c, () => sendMessage(r));
	/**
	 *
	 * @param {string} c
	 * @param {function(string)} f
	 */
	const addCommandSentence = (c, f) => handler.addCommandSentence(c, f);
	// const addCommandArgs = (c, f) => handler.addCommandArgs(c, f);

	const Emojis = {
		ONE: [0x31, 0x20E3],
		TWO: [0x32, 0x20E3],
		THREE: [0x33, 0x20E3],
		FOUR: [0x34, 0x20E3],
		FIVE: [0x35, 0x20E3]
	};

	addCommand('panel', () => {
		sendMessage([
			'1. -',
			'2. -',
			'3. -',
			'4. -',
			'5. -'
		].join('\n')).then((msg, err) => {
			if (err) return log(err);

			addSelectors(msg).then(() => {
				log('done');
				function respondToReact (e) {
					if (e.message.id !== msg.id) return client.Dispatcher.once(Events.MESSAGE_REACTION_ADD, respondToReact);
					log(e.emoji.id);
				}
				client.Dispatcher.once(Events.MESSAGE_REACTION_ADD, respondToReact);
			}, () => {
				log('\u{1F914}');
			});
		});
	});

	async function addSelectors (msg) {
		// let p =
		msg.addReaction(String.fromCodePoint(...Emojis.ONE)).catch(() => log('What?'));
		for (let i = 1; i < Object.keys(Emojis).length; i++) {
			const ind = Object.keys(Emojis)[i];
			log(ind);
			// p =
			await msg.addReaction(String.fromCodePoint(...Emojis[ind])).catch(() => log('What..?'));
		}
	}

	/* addCommandSentence('emoji', a => {
		if (msgText.includes('<')) return;
		if (/^<:.+?:\d+>$/.test(msgText)) return;
		a = a.replace(/\ufe0f|\u200d/gm, ''); // strips unicode variation selector and zero-width joiner
		let i = 0, c = 0, p = 0;
		const r = [];
		while (i < a.length) {
			c = a.charCodeAt(i++);
			if (p) {
				r.push((65536 + (p - 55296 << 10) + (c - 56320)).toString(16));
				p = 0;
			} else if (55296 <= c && c <= 56319) p = c;
			else r.push(c.toString(16));
		}

		sendMessage(r.join('-'));
	}); */
}

exports.respond = respond;
<commit_msg>Reinstate old emoji command as 'unicode'<commit_after>const CommandHandler = require('../util/msgUtil');
const Discordie = require('discordie');
const Events = Discordie.Events;

const util = require('../util/botUtil');

const config = require('../config');

/**
 *
 *
 * @param {IMessage} msg
 * @param {Discordie} client
 * @returns
 */
function respond (msg, client) {
	const msgText = msg.content;
	if (!msgText.startsWith(config.prefix)) return;
	// const sender = msg.member || msg.author; // IUser as a substitute for DMs
	const msgChannel = msg.channel;
	// const msgGuild = msg.guild;

	// const botUser = msg.isPrivate ? client.User : client.User.memberOf(msgGuild);

	const log = (s) => util.log(s);

	/**
	 * Sends a message
	 * @param {string} s Text
	 * @param {object} e Embed object
	 * @returns {Promise<{}>}
	 */
	const sendMessage = (s, e) => msgChannel.sendMessage(s, false, e);
	// const sendEmbed = (e) => sendMessage('', e);

	const command = msgText.slice(config.prefix.length);
	const handler = new CommandHandler(command);

	const addCommand = (c, f) => handler.addCommand(c, f);
	// const addCommandResponse = (c, r) => addCommand(c, () => sendMessage(r));
	/**
	 *
	 * @param {string} c
	 * @param {function(string)} f
	 */
	const addCommandSentence = (c, f) => handler.addCommandSentence(c, f);
	// const addCommandArgs = (c, f) => handler.addCommandArgs(c, f);

	const Emojis = {
		ONE: [0x31, 0x20E3],
		TWO: [0x32, 0x20E3],
		THREE: [0x33, 0x20E3],
		FOUR: [0x34, 0x20E3],
		FIVE: [0x35, 0x20E3]
	};

	addCommand('panel', () => {
		sendMessage([
			'1. -',
			'2. -',
			'3. -',
			'4. -',
			'5. -'
		].join('\n')).then((msg, err) => {
			if (err) return log(err);

			addSelectors(msg).then(() => {
				log('done');
				function respondToReact (e) {
					if (e.message.id !== msg.id) return client.Dispatcher.once(Events.MESSAGE_REACTION_ADD, respondToReact);
					log(e.emoji.id);
				}
				client.Dispatcher.once(Events.MESSAGE_REACTION_ADD, respondToReact);
			}, () => {
				log('\u{1F914}');
			});
		});
	});

	async function addSelectors (msg) {
		// let p =
		msg.addReaction(String.fromCodePoint(...Emojis.ONE)).catch(() => log('What?'));
		for (let i = 1; i < Object.keys(Emojis).length; i++) {
			const ind = Object.keys(Emojis)[i];
			log(ind);
			// p =
			await msg.addReaction(String.fromCodePoint(...Emojis[ind])).catch(() => log('What..?'));
		}
	}

	addCommandSentence('unicode', a => {
		if (msgText.includes('<')) return;
		if (/^<:.+?:\d+>$/.test(msgText)) return;
		a = a.replace(/\ufe0f|\u200d/gm, ''); // strips unicode variation selector and zero-width joiner
		let i = 0, c = 0, p = 0;
		const r = [];
		while (i < a.length) {
			c = a.charCodeAt(i++);
			if (p) {
				r.push((65536 + (p - 55296 << 10) + (c - 56320)).toString(16));
				p = 0;
			} else if (55296 <= c && c <= 56319) p = c;
			else r.push(c.toString(16));
		}

		sendMessage(r.join('-'));
	});
}

exports.respond = respond;
<|endoftext|>


<commit_before>/**
 * @description This module communicates with Lychee's API
 * @copyright   2015 by Tobias Reich
 */

api = {

	path    : 'php/index.php',
	onError : null

}

api.post = function(fn, params, callback) {

	loadingBar.show()

	params = $.extend({ function: fn }, params)

	const success = (data) => {

		setTimeout(() => loadingBar.hide(), 100)

		// Catch errors
		if (typeof data==='string' && data.substring(0, 7)==='Error: ') {
			api.onError(data.substring(7, data.length), params, data)
			return false
		}

		// Convert 1 to true and an empty string to false
		if (data==='1')     data = true
		else if (data==='') data = false

		// Convert to JSON if string start with '{' and ends with '}'
		if (typeof data==='string' && data.substring(0, 1)==='{' && data.substring(data.length - 1, data.length)==='}') {
			data = $.parseJSON(data)
		}

		callback(data)

	}

	const error = (jqXHR, textStatus, errorThrown) => {

		api.onError('Server error or API not found.', params, errorThrown)

	}

	$.ajax({
		type: 'POST',
		url: api.path,
		data: params,
		dataType: 'text',
		success,
		error
	})

}<commit_msg>Removed unnecessary anonymous function<commit_after>/**
 * @description This module communicates with Lychee's API
 * @copyright   2015 by Tobias Reich
 */

api = {

	path    : 'php/index.php',
	onError : null

}

api.post = function(fn, params, callback) {

	loadingBar.show()

	params = $.extend({ function: fn }, params)

	const success = (data) => {

		setTimeout(loadingBar.hide, 100)

		// Catch errors
		if (typeof data==='string' && data.substring(0, 7)==='Error: ') {
			api.onError(data.substring(7, data.length), params, data)
			return false
		}

		// Convert 1 to true and an empty string to false
		if (data==='1')     data = true
		else if (data==='') data = false

		// Convert to JSON if string start with '{' and ends with '}'
		if (typeof data==='string' && data.substring(0, 1)==='{' && data.substring(data.length - 1, data.length)==='}') {
			data = $.parseJSON(data)
		}

		callback(data)

	}

	const error = (jqXHR, textStatus, errorThrown) => {

		api.onError('Server error or API not found.', params, errorThrown)

	}

	$.ajax({
		type: 'POST',
		url: api.path,
		data: params,
		dataType: 'text',
		success,
		error
	})

}<|endoftext|>


<commit_before>var React = require('react-native');

var {
  View,
  Text,
  StyleSheet,
  TouchableHighlight,
  Modal,
  Dimensions
} = React;

class TeacherQCModal extends React.Component {
  constructor(props) {
    super(props);
    var {height, width} = Dimensions.get('window');

    this.state = {
      height: height,
      width: width,
    };
  }

  handleEnter() {
    this.props.onEnter.call(null)
  }

  render() {
    return (
      <Modal visible={this.props.visible} transparent={true} animated={true}>
        <View style={styles.modal}>
          <View style={{height:this.state.height * 0.6, width:this.state.width * 0.8}}>
            <View style={styles.modalBox}>
              <Text> Your secret code is: </Text>
              <Text> {this.props.randomId} </Text>
              <TouchableHighlight onPress={this.handleEnter.bind(this)}>
                <Text> Okay </Text>
              </TouchableHighlight>
            </View>
          </View>
        </View>
      </Modal>
    );
  }
}

const styles = StyleSheet.create({
  modal: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.8)',
    justifyContent: 'center',
    alignItems: 'center'
  },
  modalBox: {
    flex: 1,
    borderRadius: 10,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'white'
  },
  userInput: {
    height: 50,
    padding: 4,
    fontSize: 18,
    borderWidth: 1,
    borderRadius: 4,
  }
});

module.exports = TeacherQCModal;

<commit_msg>fixed teacherQCModal style<commit_after>var React = require('react-native');
var Button = require('./../shared/button');

var {
  View,
  Text,
  StyleSheet,
  TouchableHighlight,
  Modal,
  Dimensions
} = React;

class TeacherQCModal extends React.Component {
  constructor(props) {
    super(props);
    var {height, width} = Dimensions.get('window');

    this.state = {
      height: height,
      width: width,
    };
  }

  handleEnter() {
    this.props.onEnter.call(null)
  }

  render() {
    return (
      <Modal visible={this.props.visible} transparent={true} animated={true}>
        <View style={styles.modal}>
          <View style={{height:this.state.height * 0.5, width:this.state.width * 0.85}}>
            <View style={styles.modalBox}>
              <Text style={styles.modalTextSize}> Your secret code is: </Text>
              <Text style={styles.modalTextSize}> {this.props.randomId} </Text>
            </View>
          </View>
          <Button onPress={this.handleEnter.bind(this)} text={'Okay'} />
        </View>
      </Modal>
    );
  }
}

const styles = StyleSheet.create({
  modal: {
      flex: 1,
      backgroundColor: 'rgba(0,0,0,0.8)',
      justifyContent: 'center',
      alignItems: 'center',
    },
    modalBox: {
      flex: 1,
      borderRadius: 10,
      justifyContent: 'center',
      alignItems: 'center',
      backgroundColor: '#e3f2fd'
    },
    modalTextSize: {
      fontSize: 25,
      fontWeight: 'bold',
      marginBottom: 20,
    },
});

module.exports = TeacherQCModal;

<|endoftext|>


<commit_before>// ==UserScript==
// @name            TW Best Friends
// @description     Sending tamboola currency made easier
// @author          xShteff, Diggo11
// @match           https://*.the-west.net/game.php*
// @match           https://*.the-west.de/game.php*
// @match           https://*.the-west.pl/game.php*
// @match           https://*.the-west.nl/game.php*
// @match           https://*.the-west.se/game.php*
// @match           https://*.the-west.ro/game.php*
// @match           https://*.the-west.com.pt/game.php*
// @match           https://*.the-west.cz/game.php*
// @match           https://*.the-west.es/game.php*
// @match           https://*.the-west.ru/game.php*
// @match           https://*.the-west.com.br/game.php*
// @match           https://*.the-west.org/game.php*
// @match           https://*.the-west.hu/game.php*
// @match           https://*.the-west.gr/game.php*
// @match           https://*.the-west.dk/game.php*
// @match           https://*.the-west.sk/game.php*
// @match           https://*.the-west.fr/game.php*
// @match           https://*.the-west.it/game.php*
// @grant           none
// @run-at          document-end
// ==/UserScript==

/**
 * A map of player ids to plain objects describing the character
 * @type {Object}
 */
var friends = {};

/**
 * A map of player ids to unix timestamps
 * @type {Object}
 */
var lastSent = {};

/**
 * Records whether an ses:*_received event has been signalled since last downloading logs, assuming true upon login
 * @type {Boolean}
 */
var newLogs = true;

/**
 * A map containing information such as the most recent log processed, etc
 * @type {Object}
 */
var logsMetadata = null;

/**
 * A map of player ids to eg {total: Number, frequency: [js timestamp, ...]}
 * @type {Object}
 */
var playerLogs = null;

/**
 * A map of ses drop types to ses currency received from this drop type
 * @type {Object}
 */
var dropTypeLogs = null;

/**
 * We don't want a race between two log processing functions
 * @type {Boolean}
 */
var logsLocked = false;

/**
 * Returns a list of keys for active events, eg Hearts. Practically guaranteed to have a length of 0 if no events are
 * running and a length of 1 otherwise (2 or more = internal beta only).
 * @returns {Array}
 */
function getActiveSesKeys() {
	return Object.keys(Game.sesData);
}

/**
 * Returns the number of seconds until you can send ses currency to a friend, or 0 if you can send it immediately.
 * Friends list must be initiated first.
 * @param {Number} friendId
 * @returns {Number}
 */
function timeUntilSesReady(friendId) {
	if (!lastSent.hasOwnProperty(friendId)) {
		return 0;
	}
	var yesterday = Date.now()/1000 - 3600*24;
	return Math.max(0, Math.floor(lastSent[friendId] - yesterday));
}

/**
 * Returns the number of friends you can currently send ses currency to. Friends list must be initiated first.
 * @returns {Number}
 */
function getSesReadyCount() {
	var count = 0;
	$.each(friends, function (playerId, client) {
		if (timeUntilSesReady(playerId) === 0) {
			count++;
		}
	});
	return count;
}

/**
 * Returns the total number of friends you have. Friends list must be initiated first.
 * @returns {Number}
 */
function getFriendCount() {
	return Object.keys(friends).length;
}

/**
 * Initiates the friend list and its twin, the last sent list. Do NOT run before establishing an event is ongoing.
 * Returns a promise with the message from the server, if any, but consider it void if resolved successfully.
 * @returns {Promise}
 */
function getFriendsList() {
	return new Promise(function (resolve, reject) {
		Ajax.remoteCallMode('friendsbar', 'search', {search_type: 'friends'}, function (data) {
			if (data.error) {
				return reject(data.msg);
			}

			$.each(data.players, function (i, client) {
				if (client.player_id !== Character.playerId) {
					friends[client.player_id] = west.storage.FriendsBar.prototype.normalizeAvatars_(client, i);
					delete friends[client.player_id].experience;
					delete friends[client.player_id].x;
					delete friends[client.player_id].y;
				}
			});

			var sesKey = getActiveSesKeys()[0];
			$.each(data.eventActivations, function (i, eventActivation) {
				if (eventActivation.event_name === sesKey) {
					lastSent[eventActivation.friend_id] = eventActivation.activation_time;
				}
			});

			return resolve(data.msg);
		});
	});
}

/**
 * Sends ses currency to a friend. Do NOT run before establishing an event is ongoing, even if you somehow obtain a
 * friend id without initiating the friend list first (congratulations). Returns a promise with the response message.
 * @param {Number} friendId
 * @returns {Promise}
 */
function sendSesCurrency(friendId) {
	return new Promise(function (resolve, reject) {
		Ajax.remoteCall('friendsbar', 'event', {player_id: friendId, event: getActiveSesKeys()[0]}, function (data) {
			if (data.error) {
				return reject(data.msg);
			}
			lastSent[friendId] = data.activationTime;
			return resolve(data.msg);
		});
	});
}

/**
 * Downloads any new ses currency logs and processes them. If the last seen ses key does not match then it deletes the
 * currently held logs. Optionally adds a delay between log fetches to avoid streaks of bad luck. If you want more info
 * see the functions below.
 * @param {Boolean} background
 * @returns {Promise}
 */
function processLogs(background) {
	if (logsLocked) throw new Error("Please don't try and process the logs twice at the same time.");
	if (!newLogs) return Promise.resolve();
	logsLocked = true;

	loadLogs();
	var sesKey = getActiveSesKeys()[0];
	if (sesKey !== logsMetadata.sesKey) {
		playerLogs = {};
		dropTypeLogs = {};
		logsMetadata.sesKey = sesKey;
	}

	return new Promise(function (resolve, reject) {
		var generator = processLogsBatches(sesKey, background, resolve, reject);
		generator.next();
		generator.next(generator.next);
	});
}

/**
 * This whole function is a big hack to emulate ES7 async/await. If it were supported by current browsers, we could
 * simply make processLogs async and await each processLogBatch there in a loop, then resolve the promise. We can't use
 * promises' .then either because we don't know how many pages there are in advance; we would need something like .while
 * and severe mental backflips. The next best thing is the yield keyword provided by generators, which allows us to
 * "pause" execution in a similar way. We obviously can't stick this in the promise directly so it exists here instead,
 * and processLogs just calls it and hands along its resolve and reject functions.
 *
 * @see https://davidwalsh.name/async-generators
 * @see https://esdiscuss.org/topic/retrieving-generator-references
 *
 * The core idea here is that once a batch of logs is done processing asynchronously, it resumes the generator, which
 * starts the next batch. To do that, we need to pass a reference to this generator onto processLogBatch. Generators are
 * not initialised with the new keyword, so annoyingly `this` does not point to the generator object. However, it is
 * possible to resume suspended generators with an overwritten value. This is why we yield immediately -- so processLogs
 * can resume the execution with a reference to the generator object.
 *
 * After that it is relatively straightforward. The generator is suspended and resumed via the callback until there are
 * no more pages of logs to process. At that point we update the newestSeen data, save everything to local storage and
 * resolve the promise satisfied everything is ready to open the window.
 * @param {String} sesKey
 * @param {Boolean} background
 * @param {Function} resolve
 * @param {Function} reject
 */
function* processLogsBatches(sesKey, background, resolve, reject) {
	var callback = yield;
	var stats = {newest: logsMetadata.newestSeen, hasNext: true};
	var page = 1;
	do {
		yield processLogBatch(sesKey, page++, stats, callback, background);
	} while (stats.hasNext);
	logsMetadata.newestSeen = stats.newest;
	saveLogs();
	newLogs = false;
	logsLocked = false;
	return resolve();
}

/**
 * Processes a given page of logs and updates playerLogs and dropTypeLogs. Stats object is used like pass-by-reference
 * to return both the newest log date seen and whether more new log pages are available.
 * @param {String} sesKey
 * @param {Number} page
 * @param {Object} stats
 * @param {Function} callback
 * @param {Boolean} background
 */
function processLogBatch(sesKey, page, stats, callback, background) {
	Ajax.remoteCallMode('ses', 'log', {ses_id: sesKey, page: page, limit: 100}, function (data) {
		if (data.error) {
			logsLocked = false;
			return reject(data.msg);
		}

		stats.hasNext = !data.entries.some(function (entry, i) {
			if (entry.date <= logsMetadata.newestSeen) {
				return true; // short circuit
			} else if (i === 0 && entry.date > stats.newest) {
				stats.newest = entry.date;
			}

			dropTypeLogs[entry.type] = (dropTypeLogs[entry.type] || 0) + entry.value;
			if (entry.type === 'friendDrop') {
				var senderId = JSON.parse(entry.details).player_id;
				if (playerLogs.hasOwnProperty(senderId)) {
					playerLogs[senderId].total += entry.value;
					playerLogs[senderId].frequency.push(entry.date);
				} else {
					playerLogs[senderId] = {total: entry.value, frequency: [entry.date]};
				}
			}
		}) && data.hasNext;

		if (background) {
			setTimeout(callback, 1000);
		} else {
			callback();
		}
	});
}

/**
 * Load playerLogs and dropTypeLogs from local storage.
 */
function loadLogs() {
	logsMetadata = JSON.parse(localStorage.getItem('xshteff.betterfriends.logsMetadata')) || {};
	playerLogs = JSON.parse(localStorage.getItem('xshteff.betterfriends.playerLogs')) || {};
	dropTypeLogs = JSON.parse(localStorage.getItem('xshteff.betterfriends.dropTypeLogs')) || {};
}

/**
 * Save playerLogs and dropTypeLogs into local storage.
 */
function saveLogs() {
	var prefix = 'xshteff.betterfriends.';
	localStorage.setItem(prefix + 'logsMetadata', JSON.stringify(logsMetadata));
	localStorage.setItem(prefix + 'playerLogs', JSON.stringify(playerLogs));
	localStorage.setItem(prefix + 'dropTypeLogs', JSON.stringify(dropTypeLogs));
}

// getFriendsList()
// .then(getSesReadyCount)
// .then(x => console.log(x));
//
// sendSesCurrency(1337)
// .then(msg => MessageSuccess(msg).show())
// .catch(msg => MessageError(msg).show());

function initialiseScript() {
	var sesKeys = getActiveSesKeys();
	if (sesKeys.length === 0) return;

	getFriendsList().then(function () {
		getSesReadyCount(); // display it pls Allen
		getFriendCount(); // display it pls Allen
		return processLogs(true)
	}).then(
		// add/enable button to open window, no earlier than here pls
	);

	EventHandler.listen('friend_added', function (client) {
		friends[client.playerId] = {
			avatar: client.avatar,
			class: client.charClass,
			level: client.level,
			name: client.pname,
			player_id: client.playerId,
			profession_id: client.professionId,
			subclass: client.subClass
		};
	});

	EventHandler.listen('friend_removed', function (friendId) {
		delete friends[friendId];
	});

	EventHandler.listen(Game.sesData[sesKeys[0]].counter.key, function (amount) {
		newLogs = true;
	});
}

function openWindow() {
	processLogs(false).then(function () {
		// open it pls Allen
	});
}

// Right, here's the fun part.

//Manually adding some styling for the table
var styling = $('').text('.send-links { float:right; margin-right:5px; } ');
$('head').append(styling);

//Generating an icon so you can open the window
var icon = $('

').attr({
    'title': 'TW Best Friends',
    'class': 'menulink'
}).css({
    'background': 'url(https://puu.sh/nkN3l/aba1b474e5.png)',
    'background-position': '0px 0px'
}).mouseleave(function() {
    $(this).css("background-position", "0px 0px");
}).mouseenter(function(e) {
    $(this).css("background-position", "25px 0px");
}).click(function() {
	getFriendsList().then(function() {
		openFriendsWindow();
	});
});

//Generating the end of the button
var fix = $('

').attr({
    'class': 'menucontainer_bottom'
});

//Adding it
$("#ui_menubar .ui_menucontainer :last").after($('

').attr({
    'class': 'ui_menucontainer',
    'id': 'twbf'
}).append(icon).append(fix));

var generateSendLink = function(pid) {
	var link = $('').text('Send').click(function() {
		sendSesCurrency(pid);
		$(this).parent().parent().fadeOut();
		new UserMessage("Thingies sent", UserMessage.TYPE_SUCCESS).show();
	});
    return link;
}

var appendPlayerToTable = function(table, pid) {
    table.appendRow().appendToCell(-1, 'player-names', friends[pid].name).appendToCell(-1, 'send-links', generateSendLink(pid));
}

var openFriendsWindow = function() {
    var windowContent = new west.gui.Scrollpane;
    var friendsTable = new west.gui.Table;
    friendsTable.addColumn('player-names').appendToCell('head', 'player-names', ' ' + 'Name');
    friendsTable.addColumn('send-links');
    $.each(friends, function(pid) {
        if(!timeUntilSesReady(pid)){
            appendPlayerToTable(friendsTable, pid);
        }
    });
    windowContent.appendContent(friendsTable.divMain);
    wman.open('twbf').setTitle('TW Best Friends').appendToContentPane(windowContent.divMain).setMiniTitle('TW Best Friends - Sending currency made easier!').setSize('400', '400');
}<commit_msg>Attempt numero uno<commit_after>// ==UserScript==
// @name            TW Best Friends
// @description     Sending tamboola currency made easier
// @author          xShteff, Diggo11
// @match           https://*.the-west.net/game.php*
// @match           https://*.the-west.de/game.php*
// @match           https://*.the-west.pl/game.php*
// @match           https://*.the-west.nl/game.php*
// @match           https://*.the-west.se/game.php*
// @match           https://*.the-west.ro/game.php*
// @match           https://*.the-west.com.pt/game.php*
// @match           https://*.the-west.cz/game.php*
// @match           https://*.the-west.es/game.php*
// @match           https://*.the-west.ru/game.php*
// @match           https://*.the-west.com.br/game.php*
// @match           https://*.the-west.org/game.php*
// @match           https://*.the-west.hu/game.php*
// @match           https://*.the-west.gr/game.php*
// @match           https://*.the-west.dk/game.php*
// @match           https://*.the-west.sk/game.php*
// @match           https://*.the-west.fr/game.php*
// @match           https://*.the-west.it/game.php*
// @grant           none
// @run-at          document-end
// ==/UserScript==

/**
 * A map of player ids to plain objects describing the character
 * @type {Object}
 */
var friends = {};

/**
 * A map of player ids to unix timestamps
 * @type {Object}
 */
var lastSent = {};

/**
 * Records whether an ses:*_received event has been signalled since last downloading logs, assuming true upon login
 * @type {Boolean}
 */
var newLogs = true;

/**
 * A map containing information such as the most recent log processed, etc
 * @type {Object}
 */
var logsMetadata = null;

/**
 * A map of player ids to eg {total: Number, frequency: [js timestamp, ...]}
 * @type {Object}
 */
var playerLogs = null;

/**
 * A map of ses drop types to ses currency received from this drop type
 * @type {Object}
 */
var dropTypeLogs = null;

/**
 * We don't want a race between two log processing functions
 * @type {Boolean}
 */
var logsLocked = false;

/**
 * Returns a list of keys for active events, eg Hearts. Practically guaranteed to have a length of 0 if no events are
 * running and a length of 1 otherwise (2 or more = internal beta only).
 * @returns {Array}
 */
function getActiveSesKeys() {
    return Object.keys(Game.sesData);
}

/**
 * Returns the number of seconds until you can send ses currency to a friend, or 0 if you can send it immediately.
 * Friends list must be initiated first.
 * @param {Number} friendId
 * @returns {Number}
 */
function timeUntilSesReady(friendId) {
    if (!lastSent.hasOwnProperty(friendId)) {
        return 0;
    }
    var yesterday = Date.now() / 1000 - 3600 * 24;
    return Math.max(0, Math.floor(lastSent[friendId] - yesterday));
}

/**
 * Returns the number of friends you can currently send ses currency to. Friends list must be initiated first.
 * @returns {Number}
 */
function getSesReadyCount() {
    var count = 0;
    $.each(friends, function(playerId, client) {
        if (timeUntilSesReady(playerId) === 0) {
            count++;
        }
    });
    return count;
}

/**
 * Returns the total number of friends you have. Friends list must be initiated first.
 * @returns {Number}
 */
function getFriendCount() {
    return Object.keys(friends).length;
}

/**
 * Initiates the friend list and its twin, the last sent list. Do NOT run before establishing an event is ongoing.
 * Returns a promise with the message from the server, if any, but consider it void if resolved successfully.
 * @returns {Promise}
 */
function getFriendsList() {
    return new Promise(function(resolve, reject) {
        Ajax.remoteCallMode('friendsbar', 'search', {
            search_type: 'friends'
        }, function(data) {
            if (data.error) {
                return reject(data.msg);
            }

            $.each(data.players, function(i, client) {
                if (client.player_id !== Character.playerId) {
                    friends[client.player_id] = west.storage.FriendsBar.prototype.normalizeAvatars_(client, i);
                    delete friends[client.player_id].experience;
                    delete friends[client.player_id].x;
                    delete friends[client.player_id].y;
                }
            });

            var sesKey = getActiveSesKeys()[0];
            $.each(data.eventActivations, function(i, eventActivation) {
                if (eventActivation.event_name === sesKey) {
                    lastSent[eventActivation.friend_id] = eventActivation.activation_time;
                }
            });

            return resolve(data.msg);
        });
    });
}

/**
 * Sends ses currency to a friend. Do NOT run before establishing an event is ongoing, even if you somehow obtain a
 * friend id without initiating the friend list first (congratulations). Returns a promise with the response message.
 * @param {Number} friendId
 * @returns {Promise}
 */
function sendSesCurrency(friendId) {
    return new Promise(function(resolve, reject) {
        Ajax.remoteCall('friendsbar', 'event', {
            player_id: friendId,
            event: getActiveSesKeys()[0]
        }, function(data) {
            if (data.error) {
                return reject(data.msg);
            }
            lastSent[friendId] = data.activationTime;
            return resolve(data.msg);
        });
    });
}

/**
 * Downloads any new ses currency logs and processes them. If the last seen ses key does not match then it deletes the
 * currently held logs. Optionally adds a delay between log fetches to avoid streaks of bad luck. If you want more info
 * see the functions below.
 * @param {Boolean} background
 * @returns {Promise}
 */
function processLogs(background) {
    if (logsLocked) throw new Error("Please don't try and process the logs twice at the same time.");
    if (!newLogs) return Promise.resolve();
    logsLocked = true;

    loadLogs();
    var sesKey = getActiveSesKeys()[0];
    if (sesKey !== logsMetadata.sesKey) {
        playerLogs = {};
        dropTypeLogs = {};
        logsMetadata.sesKey = sesKey;
    }

    return new Promise(function(resolve, reject) {
        var generator = processLogsBatches(sesKey, background, resolve, reject);
        generator.next();
        generator.next(generator.next);
    });
}

/**
 * This whole function is a big hack to emulate ES7 async/await. If it were supported by current browsers, we could
 * simply make processLogs async and await each processLogBatch there in a loop, then resolve the promise. We can't use
 * promises' .then either because we don't know how many pages there are in advance; we would need something like .while
 * and severe mental backflips. The next best thing is the yield keyword provided by generators, which allows us to
 * "pause" execution in a similar way. We obviously can't stick this in the promise directly so it exists here instead,
 * and processLogs just calls it and hands along its resolve and reject functions.
 *
 * @see https://davidwalsh.name/async-generators
 * @see https://esdiscuss.org/topic/retrieving-generator-references
 *
 * The core idea here is that once a batch of logs is done processing asynchronously, it resumes the generator, which
 * starts the next batch. To do that, we need to pass a reference to this generator onto processLogBatch. Generators are
 * not initialised with the new keyword, so annoyingly `this` does not point to the generator object. However, it is
 * possible to resume suspended generators with an overwritten value. This is why we yield immediately -- so processLogs
 * can resume the execution with a reference to the generator object.
 *
 * After that it is relatively straightforward. The generator is suspended and resumed via the callback until there are
 * no more pages of logs to process. At that point we update the newestSeen data, save everything to local storage and
 * resolve the promise satisfied everything is ready to open the window.
 * @param {String} sesKey
 * @param {Boolean} background
 * @param {Function} resolve
 * @param {Function} reject
 */
function * processLogsBatches(sesKey, background, resolve, reject) {
    var callback = yield;
    var stats = {
        newest: logsMetadata.newestSeen,
        hasNext: true
    };
    var page = 1;
    do {
        yield processLogBatch(sesKey, page++, stats, callback, background);
    } while (stats.hasNext);
    logsMetadata.newestSeen = stats.newest;
    saveLogs();
    newLogs = false;
    logsLocked = false;
    return resolve();
}

/**
 * Processes a given page of logs and updates playerLogs and dropTypeLogs. Stats object is used like pass-by-reference
 * to return both the newest log date seen and whether more new log pages are available.
 * @param {String} sesKey
 * @param {Number} page
 * @param {Object} stats
 * @param {Function} callback
 * @param {Boolean} background
 */
function processLogBatch(sesKey, page, stats, callback, background) {
    Ajax.remoteCallMode('ses', 'log', {
        ses_id: sesKey,
        page: page,
        limit: 100
    }, function(data) {
        if (data.error) {
            logsLocked = false;
            return reject(data.msg);
        }

        stats.hasNext = !data.entries.some(function(entry, i) {
            if (entry.date <= logsMetadata.newestSeen) {
                return true; // short circuit
            } else if (i === 0 && entry.date > stats.newest) {
                stats.newest = entry.date;
            }

            dropTypeLogs[entry.type] = (dropTypeLogs[entry.type] || 0) + entry.value;
            if (entry.type === 'friendDrop') {
                var senderId = JSON.parse(entry.details).player_id;
                if (playerLogs.hasOwnProperty(senderId)) {
                    playerLogs[senderId].total += entry.value;
                    playerLogs[senderId].frequency.push(entry.date);
                } else {
                    playerLogs[senderId] = {
                        total: entry.value,
                        frequency: [entry.date]
                    };
                }
            }
        }) && data.hasNext;

        if (background) {
            setTimeout(callback, 1000);
        } else {
            callback();
        }
    });
}

/**
 * Load playerLogs and dropTypeLogs from local storage.
 */
function loadLogs() {
    logsMetadata = JSON.parse(localStorage.getItem('xshteff.betterfriends.logsMetadata')) || {};
    playerLogs = JSON.parse(localStorage.getItem('xshteff.betterfriends.playerLogs')) || {};
    dropTypeLogs = JSON.parse(localStorage.getItem('xshteff.betterfriends.dropTypeLogs')) || {};
}

/**
 * Save playerLogs and dropTypeLogs into local storage.
 */
function saveLogs() {
    var prefix = 'xshteff.betterfriends.';
    localStorage.setItem(prefix + 'logsMetadata', JSON.stringify(logsMetadata));
    localStorage.setItem(prefix + 'playerLogs', JSON.stringify(playerLogs));
    localStorage.setItem(prefix + 'dropTypeLogs', JSON.stringify(dropTypeLogs));
}

// getFriendsList()
// .then(getSesReadyCount)
// .then(x => console.log(x));
//
// sendSesCurrency(1337)
// .then(msg => MessageSuccess(msg).show())
// .catch(msg => MessageError(msg).show());

function initialiseScript() {
    var sesKeys = getActiveSesKeys();
    if (sesKeys.length === 0) return;

    getFriendsList().then(function() {
        getSesReadyCount(); // display it pls Allen
        getFriendCount(); // display it pls Allen
        return processLogs(true)
    }).then(
        // add/enable button to open window, no earlier than here pls
        initialiseButton();
    );

    EventHandler.listen('friend_added', function(client) {
        friends[client.playerId] = {
            avatar: client.avatar,
            class: client.charClass,
            level: client.level,
            name: client.pname,
            player_id: client.playerId,
            profession_id: client.professionId,
            subclass: client.subClass
        };
    });

    EventHandler.listen('friend_removed', function(friendId) {
        delete friends[friendId];
    });

    EventHandler.listen(Game.sesData[sesKeys[0]].counter.key, function(amount) {
        newLogs = true;
    });
}

function openWindow() {
    processLogs(false).then(openFriendsWindow);
}

// Right, here's the fun part.

//Manually adding some styling for the table
var styling = $('').text('.send-links { float:right; margin-right:5px; } ');
$('head').append(styling);

//Generating an icon so you can open the window

var initialiseButton = function() {
    var icon = $('

').attr({
        'title': 'TW Best Friends',
        'class': 'menulink'
    }).css({
        'background': 'url(https://puu.sh/nkN3l/aba1b474e5.png)',
        'background-position': '0px 0px'
    }).mouseleave(function() {
        $(this).css("background-position", "0px 0px");
    }).mouseenter(function(e) {
        $(this).css("background-position", "25px 0px");
    }).click(function() {
        getFriendsList().then(function() {
            openFriendsWindow();
        });
    });

    //Generating the end of the button
    var fix = $('

').attr({
        'class': 'menucontainer_bottom'
    });

    //Adding it
    $("#ui_menubar .ui_menucontainer :last").after($('

').attr({
        'class': 'ui_menucontainer',
        'id': 'twbf'
    }).append(icon).append(fix));

}

var generateSendLink = function(pid) {
    var link = $('').text('Send').click(function() {
        sendSesCurrency(pid);
        $(this).parent().parent().fadeOut();
        new UserMessage("Thingies sent", UserMessage.TYPE_SUCCESS).show();
    });
    return link;
}

var appendPlayerToTable = function(table, pid) {
    table.appendRow().appendToCell(-1, 'player-names', friends[pid].name).appendToCell(-1, 'send-links', generateSendLink(pid));
}

var openFriendsWindow = function() {
    var windowContent = new west.gui.Scrollpane;
    var friendsTable = new west.gui.Table;
    friendsTable.addColumn('player-names').appendToCell('head', 'player-names', ' ' + 'Name');
    friendsTable.addColumn('send-links');
    $.each(friends, function(pid) {
        if (!timeUntilSesReady(pid)) {
            appendPlayerToTable(friendsTable, pid);
        }
    });
    windowContent.appendContent(friendsTable.divMain);
    wman.open('twbf').setTitle('TW Best Friends').appendToContentPane(windowContent.divMain).setMiniTitle('TW Best Friends - Sending currency made easier!').setSize('400', '400');
}<|endoftext|>


<commit_before>class Linear{
	// TODO : bmoor
	static isNumeric( v ){
		return v || v === 0;
	}

	constructor(){
		var i, c,
			t = [];

		for( i = 0, c = arguments.length; i < c; i++ ){
			if ( arguments[i] ){
				t.push( arguments[i] );
			}
		}

		this.references = t;
	}

	getReferences(){
		return this.references;
	}

	// allows for very complex checks of if the value is defined, allows checking previous and next value
	makeSet(){
		return [];
	}

	// DrawLinear.prototype.getPoint

	// merging set, returning true means to end the set, returning false means to continue it
	mergePoint( parsed, set ){
		if ( parsed ){
			set.push( set );
			return false;
		}else{
			return true;
		}
	}

	isValidSet( set ){
		return set.length !== 0;
	}

	parse( keys ){
		var i, c,
			raw,
			parsed,
			state,
			dis = this,
			set = this.makeSet(),
			sets = [];

		function mergePoint(){
			state = dis.mergePoint(
				parsed,
				set
			);

			if ( state !== 1 && parsed.$classify ){
				if ( !set.$classify ){
					set.$classify = {};
				}

				// TODO : this should be made so you can turn it on or off
				Object.keys(parsed.$classify).forEach(function( c ){
					if ( parsed.$classify[c] ){
						set.$classify[c] = true;
					}
				});
			}
		}

		// I need to start on the end, and find the last valid point.  Go until there
		for( i = 0, c = keys.length; i < c; i++ ){
			raw = keys[i];
			parsed = this.getPoint(raw);

			if ( parsed ){
				// -1 : added to old set, continue set
				// 0 : create new set
				// 1 : create new set, add parsed to that
				mergePoint();
			} else {
				state = 0;
			}

			if ( state > -1 ){
				if ( this.isValidSet(set) ){
					sets.push( set );
				}

				set = this.makeSet();

				if ( state ){ // state === 1
					mergePoint();
				}
			}
		}

		if ( this.isValidSet(set) ){
			sets.push( set );
		}

		this.dataSets = sets;
	}

	getLimits(){
		var min,
			max;

		this.references.forEach(function( ref ){
			if ( ref.$ops.getValue ){
				ref.$ops.$eachNode(function(node){
					var v = +ref.$ops.getValue(node);
					if ( v || v === 0 ){
						if ( min === undefined ){
							min = v;
							max = v;
						}else if ( min > v ){
							min = v;
						}else if ( max < v ){
							max = v;
						}
					}
				});
			}
		});

		return {
			min: min,
			max: max
		};
	}

	closeSet( set ){
		return set;
	}

	makeElement( convertedSet ){
		console.log( 'makeElement', convertedSet );
		return '<text>Element not overriden</text>';
	}

	makePath( convertedSet ){
		console.log( 'makePath', convertedSet );
		return 'M0,0Z';
	}
}

module.exports = Linear;
<commit_msg>fixing casting bug and state check<commit_after>class Linear{
	// TODO : bmoor
	static isNumeric( v ){
		return v || v === 0;
	}

	constructor(){
		var i, c,
			t = [];

		for( i = 0, c = arguments.length; i < c; i++ ){
			if ( arguments[i] ){
				t.push( arguments[i] );
			}
		}

		this.references = t;
	}

	getReferences(){
		return this.references;
	}

	// allows for very complex checks of if the value is defined, allows checking previous and next value
	makeSet(){
		return [];
	}

	// DrawLinear.prototype.getPoint

	// merging set, returning true means to end the set, returning false means to continue it
	mergePoint( parsed, set ){
		if ( parsed ){
			set.push( set );
			return false;
		}else{
			return true;
		}
	}

	isValidSet( set ){
		return set.length !== 0;
	}

	parse( keys ){
		var i, c,
			raw,
			parsed,
			state,
			dis = this,
			set = this.makeSet(),
			sets = [];

		function mergePoint(){
			state = dis.mergePoint(
				parsed,
				set
			);

			if ( state === -1 && parsed.$classify ){
				if ( !set.$classify ){
					set.$classify = {};
				}

				// TODO : this should be made so you can turn it on or off
				Object.keys(parsed.$classify).forEach(function( c ){
					if ( parsed.$classify[c] ){
						set.$classify[c] = true;
					}
				});
			}
		}

		// I need to start on the end, and find the last valid point.  Go until there
		for( i = 0, c = keys.length; i < c; i++ ){
			raw = keys[i];
			parsed = this.getPoint(raw);

			if ( parsed ){
				// -1 : added to old set, continue set
				// 0 : create new set
				// 1 : create new set, add parsed to that
				mergePoint();
			} else {
				state = 0;
			}

			if ( state > -1 ){
				if ( this.isValidSet(set) ){
					sets.push( set );
				}

				set = this.makeSet();

				if ( state ){ // state === 1, so merge it with the new set
					mergePoint();
				}
			}
		}

		if ( this.isValidSet(set) ){
			sets.push( set );
		}

		this.dataSets = sets;
	}

	getLimits(){
		var min,
			max;

		this.references.forEach(function( ref ){
			if ( ref.$ops.getValue ){
				ref.$ops.$eachNode(function(node){
					var v = ref.$ops.getValue(node);
					if ( v || v === 0 ){
						if ( min === undefined ){
							min = v;
							max = v;
						}else if ( min > v ){
							min = v;
						}else if ( max < v ){
							max = v;
						}
					}
				});
			}
		});

		return {
			min: min,
			max: max
		};
	}

	closeSet( set ){
		return set;
	}

	makeElement( convertedSet ){
		console.log( 'makeElement', convertedSet );
		return '<text>Element not overriden</text>';
	}

	makePath( convertedSet ){
		console.log( 'makePath', convertedSet );
		return 'M0,0Z';
	}
}

module.exports = Linear;
<|endoftext|>


<commit_before>var request = require('request');

var apiUrl = process.env.API_URL+"/v1";

class API {
  constructor() {
    this.url = apiUrl;
  }
  createLog() {
    return new Promise((resolve, reject) => {
      request.post({url:this.url}, function(e, r, b) {
        console.log(b);
        if (e) reject(e);
        else resolve(JSON.parse(b));
      });
    });
  }
  showLog(logid) {
    return new Promise((resolve, reject) => {
      request.get(this.url+"/"+logid, (e, r, b) => {
        if (e) reject(e);
        else resolve(JSON.parse(b));
      });
    });
  }
  createEntry(logid, type, data) {
    return new Promise((resolve, reject) => {
      request.post({url: this.url+"/"+logid, form: {"type":type, "data":data}}, (e, r, b) => {
        if (e) reject(e);
        else resolve(JSON.parse(b));
      });
    });
  }
  editEntry(logid, entryid, type, data) {
   return new Promise((resolve, reject) => {
    request.put({url: this.url+"/"+logid+"/"+entryid, form: {"type":type, "data":data}}, (e, r, b) => {
      if (e) reject(e);
      else resolve(JSON.parse(b));
    });
   });
  }
  deleteEntry(logid, entryid) {
    return new Promise((resolve, reject) => {
      request.del(this.url+"/"+logid+"/"+entryid, (e, r, b) => {
        if (e) reject(e);
        else resolve(JSON.parse(b));
      });
    });
  }
}

module.exports = API;
<commit_msg>log error and response on create log<commit_after>var request = require('request');

var apiUrl = process.env.API_URL+"/v1";

class API {
  constructor() {
    this.url = apiUrl;
  }
  createLog() {
    return new Promise((resolve, reject) => {
      request.post({url:this.url}, function(e, r, b) {
        console.log(e);
        console.log(r);
        if (e) reject(e);
        else resolve(JSON.parse(b));
      });
    });
  }
  showLog(logid) {
    return new Promise((resolve, reject) => {
      request.get(this.url+"/"+logid, (e, r, b) => {
        if (e) reject(e);
        else resolve(JSON.parse(b));
      });
    });
  }
  createEntry(logid, type, data) {
    return new Promise((resolve, reject) => {
      request.post({url: this.url+"/"+logid, form: {"type":type, "data":data}}, (e, r, b) => {
        if (e) reject(e);
        else resolve(JSON.parse(b));
      });
    });
  }
  editEntry(logid, entryid, type, data) {
   return new Promise((resolve, reject) => {
    request.put({url: this.url+"/"+logid+"/"+entryid, form: {"type":type, "data":data}}, (e, r, b) => {
      if (e) reject(e);
      else resolve(JSON.parse(b));
    });
   });
  }
  deleteEntry(logid, entryid) {
    return new Promise((resolve, reject) => {
      request.del(this.url+"/"+logid+"/"+entryid, (e, r, b) => {
        if (e) reject(e);
        else resolve(JSON.parse(b));
      });
    });
  }
}

module.exports = API;
<|endoftext|>