<commit_before>/* global SETTINGS: false */
import React from 'react';
import type { Dispatch } from 'redux';
import { connect } from 'react-redux';
import Loader from 'react-loader';
import { Card, CardTitle } from 'react-mdl/lib/Card';
import _ from 'lodash';

import {
  FETCH_SUCCESS,
  FETCH_PROCESSING,
  checkout,
} from '../actions';
import {
  TOAST_SUCCESS,
  TOAST_FAILURE,
} from '../constants';
import { addCourseEnrollment } from '../actions/enrollments';
import {
  setToastMessage,
  setConfirmSkipDialogVisibility,
} from '../actions/ui';
import { createForm } from '../util/util';
import CourseListCard from '../components/dashboard/CourseListCard';
import DashboardUserCard from '../components/dashboard/DashboardUserCard';
import FinancialAidCard from '../components/dashboard/FinancialAidCard';
import ErrorMessage from '../components/ErrorMessage';
import ProgressWidget from '../components/ProgressWidget';
import { setCalculatorDialogVisibility } from '../actions/ui';
import {
  setDocumentSentDate,
  updateDocumentSentDate,
} from '../actions/documents';
import {
  startCalculatorEdit,
  updateCalculatorEdit,
} from '../actions/financial_aid';
import type { UIState } from '../reducers/ui';
import type {
  DocumentsState,
} from '../reducers/documents';
import type { CoursePricesState, DashboardState } from '../flow/dashboardTypes';
import type { ProgramEnrollment } from '../flow/enrollmentTypes';
import type { ProfileGetResult } from '../flow/profileTypes';
import { skipFinancialAid } from '../actions/financial_aid';
import { currencyForCountry } from '../util/currency';

class DashboardPage extends React.Component {
  static contextTypes = {
    router:   React.PropTypes.object.isRequired
  };

  props: {
    profile:                  ProfileGetResult,
    currentProgramEnrollment: ProgramEnrollment,
    dashboard:                DashboardState,
    prices:                   CoursePricesState,
    dispatch:                 Dispatch,
    setCalculatorVisibility:  (b: boolean) => void,
    ui:                       UIState,
    documents:                DocumentsState,
  };

  componentDidMount() {
    this.handleOrderStatus();
  }

  componentDidUpdate() {
    this.handleOrderStatus();
  }

  handleOrderStatus = () => {
    const { dispatch, dashboard, location: { query } } = this.props;

    if (dashboard.fetchStatus !== FETCH_SUCCESS) {
      // wait until we have access to the dashboard
      return;
    }

    let courseKey = query.course_key;
    if (query.status === 'receipt') {
      let course = {};
      for (let program of dashboard.programs) {
        for (let courseInProgram of program.courses) {
          for (let courseRun of courseInProgram.runs) {
            if (courseRun.course_id === courseKey) {
              course = courseInProgram;
            }
          }
        }
      }

      dispatch(setToastMessage({
        title: 'Order Complete!',
        message: `You are now enrolled in ${course.title}`,
        icon: TOAST_SUCCESS,
      }));
      this.context.router.push('/dashboard/');
    } else if (query.status === 'cancel') {
      dispatch(setToastMessage({
        message: 'Order was cancelled',
        icon: TOAST_FAILURE,
      }));
      this.context.router.push('/dashboard/');
    }
  };

  dispatchCheckout = (courseId: string) => {
    const { dispatch } = this.props;

    return dispatch(checkout(courseId)).then(result => {
      const { payload, url, method } = result;

      if (method === 'POST') {
        const form = createForm(url, payload);
        const body = document.querySelector("body");
        body.appendChild(form);
        form.submit();
      } else {
        window.location = url;
      }
    });
  };

  openFinancialAidCalculator = () => {
    const {
      dispatch,
      currentProgramEnrollment,
      profile: { profile: { country } }
    } = this.props;
    dispatch(startCalculatorEdit(currentProgramEnrollment.id));
    if ( country ) {
      let currencyPrediction = currencyForCountry(country);
      dispatch(updateCalculatorEdit({ currency: currencyPrediction }));
    }
    dispatch(setCalculatorDialogVisibility(true));
  };

  setDocumentSentDate = (newDate: string): void => {
    const { dispatch } = this.props;
    dispatch(setDocumentSentDate(newDate));
  };

  skipFinancialAid = programId => {
    const { dispatch } = this.props;
    dispatch(skipFinancialAid(programId)).then(() => {
      this.setConfirmSkipDialogVisibility(false);
    });
  };

  setConfirmSkipDialogVisibility = bool => {
    const { dispatch } = this.props;
    dispatch(setConfirmSkipDialogVisibility(bool));
  };

  updateDocumentSentDate = (financialAidId: number, sentDate: string): Promise<*> => {
    const { dispatch } = this.props;
    return dispatch(updateDocumentSentDate(financialAidId, sentDate));
  };

  addCourseEnrollment = (courseId: string): void => {
    const { dispatch } = this.props;
    return dispatch(addCourseEnrollment(courseId));
  };

  render() {
    const {
      dashboard,
      prices,
      profile: { profile },
      documents,
      currentProgramEnrollment,
      ui,
    } = this.props;
    const loaded = dashboard.fetchStatus !== FETCH_PROCESSING && prices.fetchStatus !== FETCH_PROCESSING;
    let errorMessage;
    let dashboardContent;
    // if there are no errors coming from the backend, simply show the dashboard
    let program, coursePrice;
    if (!_.isNil(currentProgramEnrollment)) {
      program = dashboard.programs.find(program => program.id === currentProgramEnrollment.id);
      coursePrice = prices.coursePrices.find(coursePrice => coursePrice.program_id === currentProgramEnrollment.id);
    }
    if (dashboard.errorInfo !== undefined) {
      errorMessage = <ErrorMessage errorInfo={dashboard.errorInfo}/>;
    } else if (prices.errorInfo !== undefined) {
      errorMessage = <ErrorMessage errorInfo={prices.errorInfo} />;
    } else if (_.isNil(program) || _.isNil(coursePrice)) {
      errorMessage = <ErrorMessage errorInfo={{user_message: "No program enrollment is available."}} />;
    } else {
      let financialAidCard;
      if (program.financial_aid_availability) {
        financialAidCard = <FinancialAidCard
          program={program}
          coursePrice={coursePrice}
          openFinancialAidCalculator={this.openFinancialAidCalculator}
          documents={documents}
          setDocumentSentDate={this.setDocumentSentDate}
          skipFinancialAid={this.skipFinancialAid}
          updateDocumentSentDate={this.updateDocumentSentDate}
          setConfirmSkipDialogVisibility={this.setConfirmSkipDialogVisibility}
          ui={ui}
        />;
      }

      dashboardContent = (

            <DashboardUserCard profile={profile} program={program}/>
            {financialAidCard}
            <CourseListCard
              program={program}
              coursePrice={coursePrice}
              key={program.id}
              checkout={this.dispatchCheckout}
              openFinancialAidCalculator={this.openFinancialAidCalculator}
              addCourseEnrollment={this.addCourseEnrollment}
            />

            <ProgressWidget program={program} />
            <Card shadow={0}>
              <CardTitle>Learners Near Me</CardTitle>
            </Card>

      );
    }
    return (

        <Loader loaded={loaded}>
          {errorMessage}
          {dashboardContent}
        </Loader>

    );
  }
}

const mapStateToProps = (state) => {
  let profile = {
    profile: {}
  };
  if (state.profiles[SETTINGS.username] !== undefined) {
    profile = state.profiles[SETTINGS.username];
  }

  return {
    profile: profile,
    dashboard: state.dashboard,
    prices: state.prices,
    currentProgramEnrollment: state.currentProgramEnrollment,
    ui: state.ui,
    documents: state.documents,
  };
};

export default connect(mapStateToProps)(DashboardPage);
<commit_msg>Removed learners near me card (#1372)<commit_after>/* global SETTINGS: false */
import React from 'react';
import type { Dispatch } from 'redux';
import { connect } from 'react-redux';
import Loader from 'react-loader';
import _ from 'lodash';

import {
  FETCH_SUCCESS,
  FETCH_PROCESSING,
  checkout,
} from '../actions';
import {
  TOAST_SUCCESS,
  TOAST_FAILURE,
} from '../constants';
import { addCourseEnrollment } from '../actions/enrollments';
import {
  setToastMessage,
  setConfirmSkipDialogVisibility,
} from '../actions/ui';
import { createForm } from '../util/util';
import CourseListCard from '../components/dashboard/CourseListCard';
import DashboardUserCard from '../components/dashboard/DashboardUserCard';
import FinancialAidCard from '../components/dashboard/FinancialAidCard';
import ErrorMessage from '../components/ErrorMessage';
import ProgressWidget from '../components/ProgressWidget';
import { setCalculatorDialogVisibility } from '../actions/ui';
import {
  setDocumentSentDate,
  updateDocumentSentDate,
} from '../actions/documents';
import {
  startCalculatorEdit,
  updateCalculatorEdit,
} from '../actions/financial_aid';
import type { UIState } from '../reducers/ui';
import type {
  DocumentsState,
} from '../reducers/documents';
import type { CoursePricesState, DashboardState } from '../flow/dashboardTypes';
import type { ProgramEnrollment } from '../flow/enrollmentTypes';
import type { ProfileGetResult } from '../flow/profileTypes';
import { skipFinancialAid } from '../actions/financial_aid';
import { currencyForCountry } from '../util/currency';

class DashboardPage extends React.Component {
  static contextTypes = {
    router:   React.PropTypes.object.isRequired
  };

  props: {
    profile:                  ProfileGetResult,
    currentProgramEnrollment: ProgramEnrollment,
    dashboard:                DashboardState,
    prices:                   CoursePricesState,
    dispatch:                 Dispatch,
    setCalculatorVisibility:  (b: boolean) => void,
    ui:                       UIState,
    documents:                DocumentsState,
  };

  componentDidMount() {
    this.handleOrderStatus();
  }

  componentDidUpdate() {
    this.handleOrderStatus();
  }

  handleOrderStatus = () => {
    const { dispatch, dashboard, location: { query } } = this.props;

    if (dashboard.fetchStatus !== FETCH_SUCCESS) {
      // wait until we have access to the dashboard
      return;
    }

    let courseKey = query.course_key;
    if (query.status === 'receipt') {
      let course = {};
      for (let program of dashboard.programs) {
        for (let courseInProgram of program.courses) {
          for (let courseRun of courseInProgram.runs) {
            if (courseRun.course_id === courseKey) {
              course = courseInProgram;
            }
          }
        }
      }

      dispatch(setToastMessage({
        title: 'Order Complete!',
        message: `You are now enrolled in ${course.title}`,
        icon: TOAST_SUCCESS,
      }));
      this.context.router.push('/dashboard/');
    } else if (query.status === 'cancel') {
      dispatch(setToastMessage({
        message: 'Order was cancelled',
        icon: TOAST_FAILURE,
      }));
      this.context.router.push('/dashboard/');
    }
  };

  dispatchCheckout = (courseId: string) => {
    const { dispatch } = this.props;

    return dispatch(checkout(courseId)).then(result => {
      const { payload, url, method } = result;

      if (method === 'POST') {
        const form = createForm(url, payload);
        const body = document.querySelector("body");
        body.appendChild(form);
        form.submit();
      } else {
        window.location = url;
      }
    });
  };

  openFinancialAidCalculator = () => {
    const {
      dispatch,
      currentProgramEnrollment,
      profile: { profile: { country } }
    } = this.props;
    dispatch(startCalculatorEdit(currentProgramEnrollment.id));
    if ( country ) {
      let currencyPrediction = currencyForCountry(country);
      dispatch(updateCalculatorEdit({ currency: currencyPrediction }));
    }
    dispatch(setCalculatorDialogVisibility(true));
  };

  setDocumentSentDate = (newDate: string): void => {
    const { dispatch } = this.props;
    dispatch(setDocumentSentDate(newDate));
  };

  skipFinancialAid = programId => {
    const { dispatch } = this.props;
    dispatch(skipFinancialAid(programId)).then(() => {
      this.setConfirmSkipDialogVisibility(false);
    });
  };

  setConfirmSkipDialogVisibility = bool => {
    const { dispatch } = this.props;
    dispatch(setConfirmSkipDialogVisibility(bool));
  };

  updateDocumentSentDate = (financialAidId: number, sentDate: string): Promise<*> => {
    const { dispatch } = this.props;
    return dispatch(updateDocumentSentDate(financialAidId, sentDate));
  };

  addCourseEnrollment = (courseId: string): void => {
    const { dispatch } = this.props;
    return dispatch(addCourseEnrollment(courseId));
  };

  render() {
    const {
      dashboard,
      prices,
      profile: { profile },
      documents,
      currentProgramEnrollment,
      ui,
    } = this.props;
    const loaded = dashboard.fetchStatus !== FETCH_PROCESSING && prices.fetchStatus !== FETCH_PROCESSING;
    let errorMessage;
    let dashboardContent;
    // if there are no errors coming from the backend, simply show the dashboard
    let program, coursePrice;
    if (!_.isNil(currentProgramEnrollment)) {
      program = dashboard.programs.find(program => program.id === currentProgramEnrollment.id);
      coursePrice = prices.coursePrices.find(coursePrice => coursePrice.program_id === currentProgramEnrollment.id);
    }
    if (dashboard.errorInfo !== undefined) {
      errorMessage = <ErrorMessage errorInfo={dashboard.errorInfo}/>;
    } else if (prices.errorInfo !== undefined) {
      errorMessage = <ErrorMessage errorInfo={prices.errorInfo} />;
    } else if (_.isNil(program) || _.isNil(coursePrice)) {
      errorMessage = <ErrorMessage errorInfo={{user_message: "No program enrollment is available."}} />;
    } else {
      let financialAidCard;
      if (program.financial_aid_availability) {
        financialAidCard = <FinancialAidCard
          program={program}
          coursePrice={coursePrice}
          openFinancialAidCalculator={this.openFinancialAidCalculator}
          documents={documents}
          setDocumentSentDate={this.setDocumentSentDate}
          skipFinancialAid={this.skipFinancialAid}
          updateDocumentSentDate={this.updateDocumentSentDate}
          setConfirmSkipDialogVisibility={this.setConfirmSkipDialogVisibility}
          ui={ui}
        />;
      }

      dashboardContent = (

            <DashboardUserCard profile={profile} program={program}/>
            {financialAidCard}
            <CourseListCard
              program={program}
              coursePrice={coursePrice}
              key={program.id}
              checkout={this.dispatchCheckout}
              openFinancialAidCalculator={this.openFinancialAidCalculator}
              addCourseEnrollment={this.addCourseEnrollment}
            />

            <ProgressWidget program={program} />

      );
    }
    return (

        <Loader loaded={loaded}>
          {errorMessage}
          {dashboardContent}
        </Loader>

    );
  }
}

const mapStateToProps = (state) => {
  let profile = {
    profile: {}
  };
  if (state.profiles[SETTINGS.username] !== undefined) {
    profile = state.profiles[SETTINGS.username];
  }

  return {
    profile: profile,
    dashboard: state.dashboard,
    prices: state.prices,
    currentProgramEnrollment: state.currentProgramEnrollment,
    ui: state.ui,
    documents: state.documents,
  };
};

export default connect(mapStateToProps)(DashboardPage);
<|endoftext|>


<commit_before>(function ($) {

  $( document ).ready( function() {

    /* API CALLS />
      https://phiffer.org/youarehere/yah.php?get=stories
      https://phiffer.org/youarehere/yah.php?get=responses&story=[story ID]
      https://phiffer.org/youarehere/yah.php?get=mp3s
      https://phiffer.org/youarehere/yah.php?twilio=1 (Twilio POST endpoint)
    */

    /* HANDLEBARS HELPERS */
    Handlebars.registerHelper('getPark', function() {
      return PARK;
    });

    Handlebars.registerHelper('getRandomNumber', function() {
      var min = 0
          ,max = 14
          ,num = Math.floor(Math.random() * (max - min + 1)) + min
          ,padded;

      num < 10 ? padded = "0"+num : padded = String(num);

      return padded;
    });

    Handlebars.getTemplate = function(name) {
      if (Handlebars.templates === undefined || Handlebars.templates[name] === undefined) {
          $.ajax({
              url : 'tmpl/' + name + '.handlebars',
              success : function(data) {
                  if (Handlebars.templates === undefined) {
                      Handlebars.templates = {};
                  }
                  Handlebars.templates[name] = Handlebars.compile(data);
              },
              async : false
          });
      }
      return Handlebars.templates[name];
    };
    /* * * * */

    /* APP FUNCTIONS, UTILITIES */
    // vars, cached els
    var PH_NUM
        ,PARK="ts" // default to tompkins sq (not good, i know.. just for now)
        ,currentAudio = {}
        ,$viewButton = $('#viewButton');

    // callbacks
    // buttons
    $viewButton.on('click', function() {
      var href = $(this).data('href');
      triggerView( href );

      window.location.hash = href;

    });

    // story audio + call button assigned in renderStory function

    // window
    $(window).on('hashchange', function(){

      show(decodeURI(window.location.hash));
    });

    // Methods in order of operation...
    // load config file
    function loadConfig() {
      $.ajax({
          type: "GET",
          dataType: "json",
          url: "/config.json"
      })
      .done(function( data ) {

        PH_NUM = data["number"];
        PARK = data["park"];

        // now fetch the correct story
        fetchStory();

      })
      .fail( function(xhr, textStatus, errorThrown) {
        console.log("config xhr responseText: " + xhr.responseText);
        console.log("config textStatus: " + textStatus);
      });
    }

    function fetchStory() {
      $.ajax({
          type: "GET",
          dataType: "json",
          url: "/server/yah.php?get=stories"
      })
      .done(function( data ) {

        story = data.stories[PH_NUM];
        renderStory(story);

        // now get people's responses
        fetchResponses(story);

      })
      .fail( function(xhr, textStatus, errorThrown) {
        console.log("get stories textStatus: " + textStatus);
        console.log("get stories errorThrown: " + errorThrown);
      });
    }

    function fetchResponses(story) {
      $.ajax({
          type: "GET",
          dataType: "json",
          url: "/server/yah.php?get=responses&story="+story.id
      })
      .done(function( data ) {

        // Call a function that will turn that data into HTML.
        renderResponses(data.responses);

        // Trigger a hashchange depending on where we are in the app
        triggerView(window.location.hash);

        // Manually trigger a hashchange to start the app.
        $(window).trigger('hashchange');
      })
      .fail( function(xhr, textStatus, errorThrown) {
        console.log("get responses xhr responseText: " + xhr.responseText);
        console.log("get responses textStatus: " + textStatus);
      });
    }

    function renderStory(data){

      var $story = $('.story');
      var tmpl = Handlebars.getTemplate('story');
      HandlebarsIntl.registerWith(Handlebars);
      $story.append( tmpl(data).replace(/\u200b/g, '') );

      $story.find('.btn-audio').on('click', function (e) {
        e.preventDefault();

        toggleAudio( $story.find('audio').get(0), $(this) );

      });

      $story.find('.btn-call').on('click', function(e) {
        e.preventDefault();

        window.location.href="tel://" + $(this).data('tel');

      });

    }

    function renderResponses(data){
      // Uses Handlebars to create a list of responses using the provided data.
      // This function is called only once on page load.
      var list = $('.responses .responses-list');
      var tmpl = Handlebars.getTemplate('responses');
      HandlebarsIntl.registerWith(Handlebars);
      //list.append( tmpl(data).replace(/\u200b/g, '') );

      // click to toggle audio
      list.find('.tn').on('click', function (e) {

        e.preventDefault();

        toggleAudio( $(this).find('audio').get(0), $(this).find('.btn-audio') );

      });

      // removes the  being inserted by handlebars rendering
      //removeInvisibleChars();

      // hide loading message
      $('.loading').removeClass('visible');
      $('.responses').addClass('current visible');
    }

    function triggerView(href) {
      //strip out hash, if it's there
      href = href.replace(/^.*#/, '');
      switch(href) {
        case 'about':
          $viewButton.data('href', '');
          $viewButton.html('HOME');
          break;
        case '':
          $viewButton.data('href', 'about');
          $viewButton.html('ABOUT');
          break;
      }
    }

    function show(url) {

      var view = url.split('/')[0];

      // Hide current page
      $('.current').removeClass('current visible');

      switch( view ) {
        // Homepage.
        case '':
          $('.story').addClass('current visible');
          $('.responses').addClass('current visible');
          break;

        // About
        case '#about':
          $('.about').addClass('current visible');
          break;

        // 404 Not Found / Error
        default:
          $('.error').addClass('current visible');
      }

    }

    function removeInvisibleChars() {
      $('body').contents().eq(0).each(function(){
        if(this.nodeName.toString()=='#text' && this.data.trim().charCodeAt(0)==8203){
            $(this).remove();
        }
      });
    }

    // audio controls
    function toggleAudio(audio, btn) {

      // first, stop any currently playing audio
      if( currentAudio.audio && currentAudio.audio != audio) {
        onAudioEnd(currentAudio);
      }

      // update currentAudio
      currentAudio.audio = audio;
      currentAudio.btn = btn;

      // then toggle audio state
      if (currentAudio.audio.paused) {

        currentAudio.audio.addEventListener('ended', function() {
          onAudioEnd(currentAudio);
        });

        currentAudio.audio.play();

        $(currentAudio.btn).removeClass('play')
              .addClass('pause');

      }
      else {

        onAudioEnd(currentAudio);
        currentAudio = {};
      }
    }

    function onAudioEnd(obj) {

      obj.audio.removeEventListener('ended', onAudioEnd);

      obj.audio.pause();

      obj.audio.currentTime = 0;

      $(obj.btn).removeClass('pause')
                .addClass('play');
    }

    // Ready to go, load the config
    loadConfig();

  } );

}($));<commit_msg>debugging black boxes<commit_after>(function ($) {

  $( document ).ready( function() {

    /* API CALLS />
      https://phiffer.org/youarehere/yah.php?get=stories
      https://phiffer.org/youarehere/yah.php?get=responses&story=[story ID]
      https://phiffer.org/youarehere/yah.php?get=mp3s
      https://phiffer.org/youarehere/yah.php?twilio=1 (Twilio POST endpoint)
    */

    /* HANDLEBARS HELPERS */
    Handlebars.registerHelper('getPark', function() {
      return PARK;
    });

    Handlebars.registerHelper('getRandomNumber', function() {
      var min = 0
          ,max = 14
          ,num = Math.floor(Math.random() * (max - min + 1)) + min
          ,padded;

      num < 10 ? padded = "0"+num : padded = String(num);

      return padded;
    });

    Handlebars.getTemplate = function(name) {
      if (Handlebars.templates === undefined || Handlebars.templates[name] === undefined) {
          $.ajax({
              url : 'tmpl/' + name + '.handlebars',
              success : function(data) {
                  if (Handlebars.templates === undefined) {
                      Handlebars.templates = {};
                  }
                  Handlebars.templates[name] = Handlebars.compile(data);
              },
              async : false
          });
      }
      return Handlebars.templates[name];
    };
    /* * * * */

    /* APP FUNCTIONS, UTILITIES */
    // vars, cached els
    var PH_NUM
        ,PARK="ts" // default to tompkins sq (not good, i know.. just for now)
        ,currentAudio = {}
        ,$viewButton = $('#viewButton');

    // callbacks
    // buttons
    $viewButton.on('click', function() {
      var href = $(this).data('href');
      triggerView( href );

      window.location.hash = href;

    });

    // story audio + call button assigned in renderStory function

    // window
    $(window).on('hashchange', function(){

      show(decodeURI(window.location.hash));
    });

    // Methods in order of operation...
    // load config file
    function loadConfig() {
      $.ajax({
          type: "GET",
          dataType: "json",
          url: "/config.json"
      })
      .done(function( data ) {

        PH_NUM = data["number"];
        PARK = data["park"];

        // now fetch the correct story
        fetchStory();

      })
      .fail( function(xhr, textStatus, errorThrown) {
        console.log("config xhr responseText: " + xhr.responseText);
        console.log("config textStatus: " + textStatus);
      });
    }

    function fetchStory() {
      $.ajax({
          type: "GET",
          dataType: "json",
          url: "/server/yah.php?get=stories"
      })
      .done(function( data ) {

        story = data.stories[PH_NUM];
        renderStory(story);

        // now get people's responses
        fetchResponses(story);

      })
      .fail( function(xhr, textStatus, errorThrown) {
        console.log("get stories textStatus: " + textStatus);
        console.log("get stories errorThrown: " + errorThrown);
      });
    }

    function fetchResponses(story) {
      $.ajax({
          type: "GET",
          dataType: "json",
          url: "/server/yah.php?get=responses&story="+story.id
      })
      .done(function( data ) {

        // Call a function that will turn that data into HTML.
        renderResponses(data.responses);

        // Trigger a hashchange depending on where we are in the app
        triggerView(window.location.hash);

        // Manually trigger a hashchange to start the app.
        $(window).trigger('hashchange');
      })
      .fail( function(xhr, textStatus, errorThrown) {
        console.log("get responses xhr responseText: " + xhr.responseText);
        console.log("get responses textStatus: " + textStatus);
      });
    }

    function renderStory(data){

      var $story = $('.story');
      var tmpl = Handlebars.getTemplate('story');
      HandlebarsIntl.registerWith(Handlebars);
      $story.append( tmpl(data).replace(/\u200b/g, '') );

      $story.find('.btn-audio').on('click', function (e) {
        e.preventDefault();

        toggleAudio( $story.find('audio').get(0), $(this) );

      });

      $story.find('.btn-call').on('click', function(e) {
        e.preventDefault();

        window.location.href="tel://" + $(this).data('tel');

      });

    }

    function renderResponses(data){
      // Uses Handlebars to create a list of responses using the provided data.
      // This function is called only once on page load.
      var list = $('.responses .responses-list');
      var tmpl = Handlebars.getTemplate('responses');
      HandlebarsIntl.registerWith(Handlebars);
      list.append( tmpl(data).replace(/\u200b/g, '') );

      // click to toggle audio
      list.find('.tn').on('click', function (e) {

        e.preventDefault();

        toggleAudio( $(this).find('audio').get(0), $(this).find('.btn-audio') );

      });

      // removes the  being inserted by handlebars rendering
      //removeInvisibleChars();

      // hide loading message
      $('.loading').removeClass('visible');
      $('.responses').addClass('current visible');
    }

    function triggerView(href) {
      //strip out hash, if it's there
      href = href.replace(/^.*#/, '');
      switch(href) {
        case 'about':
          $viewButton.data('href', '');
          $viewButton.html('HOME');
          break;
        case '':
          $viewButton.data('href', 'about');
          $viewButton.html('ABOUT');
          break;
      }
    }

    function show(url) {

      var view = url.split('/')[0];

      // Hide current page
      $('.current').removeClass('current visible');

      switch( view ) {
        // Homepage.
        case '':
          $('.story').addClass('current visible');
          $('.responses').addClass('current visible');
          break;

        // About
        case '#about':
          $('.about').addClass('current visible');
          break;

        // 404 Not Found / Error
        default:
          $('.error').addClass('current visible');
      }

    }

    function removeInvisibleChars() {
      $('body').contents().eq(0).each(function(){
        if(this.nodeName.toString()=='#text' && this.data.trim().charCodeAt(0)==8203){
            $(this).remove();
        }
      });
    }

    // audio controls
    function toggleAudio(audio, btn) {

      // first, stop any currently playing audio
      if( currentAudio.audio && currentAudio.audio != audio) {
        onAudioEnd(currentAudio);
      }

      // update currentAudio
      currentAudio.audio = audio;
      currentAudio.btn = btn;

      // then toggle audio state
      if (currentAudio.audio.paused) {

        currentAudio.audio.addEventListener('ended', function() {
          onAudioEnd(currentAudio);
        });

        currentAudio.audio.play();

        $(currentAudio.btn).removeClass('play')
              .addClass('pause');

      }
      else {

        onAudioEnd(currentAudio);
        currentAudio = {};
      }
    }

    function onAudioEnd(obj) {

      obj.audio.removeEventListener('ended', onAudioEnd);

      obj.audio.pause();

      obj.audio.currentTime = 0;

      $(obj.btn).removeClass('pause')
                .addClass('play');
    }

    // Ready to go, load the config
    loadConfig();

  } );

}($));<|endoftext|>


<commit_before>/**
 * Created by Praveen on 11/09/2015.
 */
/* system imports */
require("babel/polyfill");

/* third party lib imports */
import React, { Component } from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import moment from 'moment';

/* internal imports*/
import App from './containers/AxsysApp';
import { addDevice, removeDevice } from './actions/actionCreators';
import AXApi from './ax-client';
import axsysApp from './reducers/reducers';

let store = createStore(axsysApp);

const WRITTEN = Symbol();
const WRITTEN_AND_READ = Symbol();

const TIMEOUT_IN_SECONDS = 10;
const CHECK_FREQUENCY_IN_SECONDS = 1;

let connections = {};
let commandQeueue = [];

let api = new AXApi(
    onDeviceAdded(store),
    onDeviceRemoved(store),
    onConnected(store),
    onDisconnected(store),
    onDataReceived
);

console.log(store.getState());

function onDeviceAdded(store) {
    return (device) => {
        store.dispatch(addDevice(device));
    }
}

function onDeviceRemoved(store) {
    return (device) => {
        store.dispatch(removeDevice(device));
    }
}

function onDisconnected(store) {
    return () => {
        let devices = store.getState().devices;
        devices.map((device) => {
            store.dispatch(removeDevice(device));
        });
    }
}

function onDataReceived(data) {
    console.log('Data is in the app');
    console.log(data);
    connections[data.path] = WRITTEN_AND_READ;
}

function getHardwareAndSoftwareVersion(device) {
    let options = {};
    let path = device._id;
    options.path = path;
    console.log('Getting h/w and s/w version for device: ' + path);

    api.connect(options, (response) => {
        if (response) { // void method
            let writeOptions = {};
            writeOptions.path = path;
            writeOptions.command = 'ID\r\n';
            api.write(writeOptions, (writeResponse) => {
                console.log(writeResponse);
                connections[path] = WRITTEN;
            });
            checkIfDataReceivedAndCloseConnection(path);
        }
    });
}

function checkIfDataReceivedAndCloseConnection(path) {
    let endTime = moment().add(TIMEOUT_IN_SECONDS, 'second');
    var checker = setInterval(() => {
        if (connections[path] === WRITTEN_AND_READ || moment() >= endTime) {
            let options = {};
            options.path = path;
            api.disconnect(options, (response) => {
                console.log('Closed connection');
                console.log(response);
            });
            clearInterval(checker);
        }
    }, CHECK_FREQUENCY_IN_SECONDS);
}

function onConnected(store) {
    return () => {
        api.getDevices((allDevices) => {
            console.log(err);
            console.log(allDevices);

            let err = null;

            if(err) {
                // Need to handle error
                console.error(err);

            } else {
                var devices = allDevices.devices;

                (function loop(i) {

                    setTimeout(() => {
                        console.log(i);
                        console.log(devices);
                        let dev = devices[i];
                        console.log(dev);
                        getHardwareAndSoftwareVersion(dev);
                        --i;

                        if (i >= 0) loop(i);
                    }, 3000);

                })(allDevices.devices.length - 1);

                allDevices.devices.forEach((aDevice) => {
                    store.dispatch(addDevice(aDevice));
                });

            }

        });
    }
}

// Entry point to the views
React.render(
    <Provider store={store}>
        {() => <App />}
    </Provider>,
    document.getElementById('devicesList')
);
<commit_msg>Update to accommodate API changes<commit_after>/**
 * Created by Praveen on 11/09/2015.
 */
/* system imports */
require("babel/polyfill");

/* third party lib imports */
import React, { Component } from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import moment from 'moment';

/* internal imports*/
import App from './containers/AxsysApp';
import { addDevice, removeDevice } from './actions/actionCreators';
import AXApi from './ax-client';
import axsysApp from './reducers/reducers';

let store = createStore(axsysApp);

const WRITTEN = Symbol();
const WRITTEN_AND_READ = Symbol();

const TIMEOUT_IN_SECONDS = 10;
const CHECK_FREQUENCY_IN_SECONDS = 1;

let connections = {};
let attributeCommands = [
    {
        'command': 'BATTERY\r\n',
        'frequency_in_seconds': 60
    },
    {
        'command': 'ID\r\n',
        'frequency_in_seconds': 0
    }

];

let api = new AXApi(
    onDeviceAdded(store),
    onDeviceRemoved(store),
    onConnected(store),
    onDisconnected(store),
    onDataReceived
);

console.log(store.getState());

function onDeviceAdded(store) {
    return (device) => {
        store.dispatch(addDevice(device));
    }
}

function onDeviceRemoved(store) {
    return (device) => {
        store.dispatch(removeDevice(device));
    }
}

function onDisconnected(store) {
    return () => {
        let devices = store.getState().devices;
        devices.map((device) => {
            store.dispatch(removeDevice(device));
        });
    }
}

function onDataReceived(data) {
    console.log('Data is in the app');
    console.log(data);
    connections[data.path] = WRITTEN_AND_READ;
}

function getHardwareAndSoftwareVersion(device) {
    let options = {};
    let path = device._id;
    options.path = path;
    console.log('Getting h/w and s/w version for device: ' + path);

    api.connect(options, (response) => {
        if (response) { // void method
            let writeOptions = {};
            writeOptions.path = path;
            writeOptions.command = 'ID\r\n';
            api.write(writeOptions, (writeResponse) => {
                console.log(writeResponse);
                connections[path] = WRITTEN;
            });
            checkIfDataReceivedAndCloseConnection(path);
        }
    });
}

function checkIfDataReceivedAndCloseConnection(path) {
    let endTime = moment().add(TIMEOUT_IN_SECONDS, 'second');
    var checker = setInterval(() => {
        if (connections[path] === WRITTEN_AND_READ || moment() >= endTime) {
            let options = {};
            options.path = path;
            api.disconnect(options, (response) => {
                console.log('Closed connection');
                console.log(response);
            });
            clearInterval(checker);
        }
    }, CHECK_FREQUENCY_IN_SECONDS);
}

function onConnected(store) {
    return () => {
        api.getDevices((allDevices) => {
            console.log(err);
            console.log(allDevices);

            let err = null;

            if(err) {
                // Need to handle error
                console.error(err);

            } else {
                var devices = allDevices.devices;

                (function loop(i) {

                    setTimeout(() => {
                        console.log(i);
                        console.log(devices);
                        let dev = devices[i];
                        console.log(dev);
                        getHardwareAndSoftwareVersion(dev);
                        --i;

                        if (i >= 0) loop(i);
                    }, 3000);

                })(allDevices.devices.length - 1);

                allDevices.devices.forEach((aDevice) => {
                    store.dispatch(addDevice(aDevice));
                });

            }

        });
    }
}

// Entry point to the views
React.render(
    <Provider store={store}>
        {() => <App />}
    </Provider>,
    document.getElementById('devicesList')
);
<|endoftext|>


<commit_before>'use strict'

const fs = require('fs')
const crypto = require('crypto')
const moment = require('moment')
const handlebars = require('handlebars')

const tpl = handlebars.compile(fs.readFileSync(__dirname + '/schedule.tpl').toString('utf-8'))
const note_to_contributors = "Note to contributors: Do not submit any changes to this page, as it is generated automatically from https://github.com/fossasia/open-event-scraper."
const sessionsData = require('../out/sessions.json')
const speakersData = require('../out/speakers.json')

function slugify(str) {
  return str.replace(/[^\w]/g, '-').replace(/-+/g, '-').toLowerCase()
}

function speakerNameWithOrg(speaker) {
  return speaker.organisation ?
    `${speaker.name} (${speaker.organisation})` :
    speaker.name
}

function foldByDate(tracks) {
  let dateMap = new Map()

  tracks.forEach(track => {
    if (!dateMap.has(track.date)) dateMap.set(track.date, {
      caption: track.date,
      tracks: []
    })
    dateMap.get(track.date).tracks.push(track)
  })

  let dates = Array.from(dateMap.values())
  dates.forEach(date => date.tracks.sort(byProperty('sortKey')))

  return dates;
}

function byProperty(key) {
  return (a, b) => {
    if (a[key] > b[key]) return 1
    if (a[key] < b[key]) return -1
    else return 0
  }
}

function sessionId(track, start) {
  let data = track + "," + start
  return crypto.createHash('md5').update(data).digest('hex');
}

function zeroFill(num) {
  if (num >= 10) return num.toString()
  else return '0' + num.toString()
}

function foldByTrack(sessions, speakers) {
  let trackData = new Map()
  let speakersMap = new Map(speakers.map(s => [s.id, s]));

  sessions.forEach(session => {
    if (!session.start_time)
      return

    // generate slug/key for session
    let date = moment(session.start_time).format('YYYY-MM-DD')
    let slug = date + '-' + slugify(session.track.name)
    let track = null

    // set up track if it does not exist
    if (!trackData.has(slug)) {
      track = {
        title: session.track.name,
        date: moment(session.start_time).format('ddd, MMM DD'),
        slug: slug,
        sortKey: date + '-' + zeroFill(session.track.order),
        sessions: []
      }
      trackData.set(slug, track)
    } else {
      track = trackData.get(slug)
    }

    let sessionData = {
      start: moment(session.start_time).utcOffset(8).format('hh:mm a'),
      title: session.title,
      type: session.type,
      location: session.location,
      speakers_list: session.speakers.map(speaker => speakersMap.get(speaker.id)),
      description: session.description
    }
    sessionData.uniqid = sessionId(session.track.name, sessionData.start)
    track.sessions.push(sessionData)
  })

  let tracks = Array.from(trackData.values())
  tracks.sort(byProperty('sortKey'))

  return tracks
}

function prepareSpeakers(speakers) {
  speakers.forEach(s => {
    s.nameWithOrg = speakerNameWithOrg(s)
  })
}

function transformData(sessions, speakers) {
  prepareSpeakers(speakers.speakers)
  let tracks = foldByTrack(sessions.sessions, speakers.speakers)
  let days = foldByDate(tracks)
  return {tracks, days}
}

const data = transformData(sessionsData, speakersData)
data.note_to_contributors = note_to_contributors
process.stdout.write(tpl(data))
<commit_msg>session id should use timestamp, not time alone<commit_after>'use strict'

const fs = require('fs')
const crypto = require('crypto')
const moment = require('moment')
const handlebars = require('handlebars')

const tpl = handlebars.compile(fs.readFileSync(__dirname + '/schedule.tpl').toString('utf-8'))
const note_to_contributors = "Note to contributors: Do not submit any changes to this page, as it is generated automatically from https://github.com/fossasia/open-event-scraper."
const sessionsData = require('../out/sessions.json')
const speakersData = require('../out/speakers.json')

function slugify(str) {
  return str.replace(/[^\w]/g, '-').replace(/-+/g, '-').toLowerCase()
}

function speakerNameWithOrg(speaker) {
  return speaker.organisation ?
    `${speaker.name} (${speaker.organisation})` :
    speaker.name
}

function foldByDate(tracks) {
  let dateMap = new Map()

  tracks.forEach(track => {
    if (!dateMap.has(track.date)) dateMap.set(track.date, {
      caption: track.date,
      tracks: []
    })
    dateMap.get(track.date).tracks.push(track)
  })

  let dates = Array.from(dateMap.values())
  dates.forEach(date => date.tracks.sort(byProperty('sortKey')))

  return dates;
}

function byProperty(key) {
  return (a, b) => {
    if (a[key] > b[key]) return 1
    if (a[key] < b[key]) return -1
    else return 0
  }
}

function sessionId(track, start) {
  let data = track + "," + start
  return crypto.createHash('md5').update(data).digest('hex');
}

function zeroFill(num) {
  if (num >= 10) return num.toString()
  else return '0' + num.toString()
}

function foldByTrack(sessions, speakers) {
  let trackData = new Map()
  let speakersMap = new Map(speakers.map(s => [s.id, s]));

  sessions.forEach(session => {
    if (!session.start_time)
      return

    // generate slug/key for session
    let date = moment(session.start_time).format('YYYY-MM-DD')
    let slug = date + '-' + slugify(session.track.name)
    let track = null

    // set up track if it does not exist
    if (!trackData.has(slug)) {
      track = {
        title: session.track.name,
        date: moment(session.start_time).format('ddd, MMM DD'),
        slug: slug,
        sortKey: date + '-' + zeroFill(session.track.order),
        sessions: []
      }
      trackData.set(slug, track)
    } else {
      track = trackData.get(slug)
    }

    track.sessions.push({
      start: moment(session.start_time).utcOffset(8).format('hh:mm a'),
      title: session.title,
      type: session.type,
      location: session.location,
      speakers_list: session.speakers.map(speaker => speakersMap.get(speaker.id)),
      description: session.description,
      uniqid: sessionId(session.track.name, session.start_time)
    })
  })

  let tracks = Array.from(trackData.values())
  tracks.sort(byProperty('sortKey'))

  return tracks
}

function prepareSpeakers(speakers) {
  speakers.forEach(s => {
    s.nameWithOrg = speakerNameWithOrg(s)
  })
}

function transformData(sessions, speakers) {
  prepareSpeakers(speakers.speakers)
  let tracks = foldByTrack(sessions.sessions, speakers.speakers)
  let days = foldByDate(tracks)
  return {tracks, days}
}

const data = transformData(sessionsData, speakersData)
data.note_to_contributors = note_to_contributors
process.stdout.write(tpl(data))
<|endoftext|>


<commit_before>// ==UserScript==
// @id             youdaodict-greasemonkey-reverland-2015-09-26
// @name           youdaodict
// @name:zh-CN     有道取词
// @version        1.3
// @namespace      youdao
// @author         Liu Yuyang(sa@linuxer.me)
// @description    Translate any text selected into a tooltip
// @description:zh-cn    一个可以在浏览器中自由使用的屏幕取词脚本
// @include        *
// @grant          GM_xmlhttpRequest
// ==/UserScript==

window.document.body.addEventListener("mouseup", translate, false);
window.document.body.addEventListener("keyup", toggleYoudao, false);
var toggle = true;

function toggleYoudao(e) {
  if (e.which == 81 && e.altKey && e.ctrlKey) {
    if (toggle) {
      window.document.body.removeEventListener("mouseup", translate, false);
      toggle = false;
    } else {
      window.document.body.addEventListener("mouseup", translate, false);
      toggle = true;
    }
  }
}

function translate(e) {
  // remove previous .youdaoPopup if exists
  var previous = document.querySelector(".youdaoPopup");
  if (previous) {
    document.body.removeChild(previous);
  }
  //console.log("translate start");
  var selectObj = document.getSelection();

  // if #text node
  if (selectObj.anchorNode.nodeType == 3) {
    //GM_log(selectObj.anchorNode.nodeType.toString());
    var word = selectObj.toString();
    if (word == "") {
      return;
    }
    // linebreak wordwrap, optimize for pdf.js
    word = word.replace('-\n','');
    // multiline selection, optimize for pdf.js
    word = word.replace('\n', ' ');
    //console.log("word:", word);
    var ts = new Date().getTime();
    //console.log("time: ", ts);
    var mx = e.clientX;
    var my = e.clientY;
    translate(word, ts);

  }

  function popup(mx, my, result) {
    //console.log(mx)
    //console.log(my)
    //console.log("popup window!")
    var youdaoWindow = document.createElement('div');
    youdaoWindow.classList.toggle("youdaoPopup");
    // parse
    var dictJSON = JSON.parse(result);
    console.log(dictJSON);
    var query = dictJSON['query'];
    var errorCode = dictJSON['errorCode'];
    if (dictJSON['basic']) {
      word();
    } else {
      sentence();
    }
    // main window
    // first insert into dom then there is offsetHeight！IMPORTANT！
    document.body.appendChild(youdaoWindow);
    youdaoWindow.style.color = "black";
    youdaoWindow.style.textAlign = "left";
    youdaoWindow.style.display = "block";
    youdaoWindow.style.position = "fixed";
    youdaoWindow.style.background = "lightblue";
    youdaoWindow.style.borderRadius = "5px";
    youdaoWindow.style.boxShadow = "0 0 5px 0";
    youdaoWindow.style.opacity = "0.9";
    youdaoWindow.style.width = "200px";
    youdaoWindow.style.wordWrap = "break-word";
    youdaoWindow.style.left = mx + 10 + "px";
    if (mx + 200 + 30 >= window.innerWidth) {
      youdaoWindow.style.left = parseInt(youdaoWindow.style.left) - 200 + "px";
    }
    if (my + youdaoWindow.offsetHeight + 30 >= window.innerHeight) {
      youdaoWindow.style.bottom = "20px";
    } else {
      youdaoWindow.style.top = my + 10 + "px";
    }
    youdaoWindow.style.padding = "5px";
    youdaoWindow.style.zIndex = '999999';

    function word() {

      function play(word) {
        //console.log("[DEBUG] PLAYOUND")

        function playSound(buffer) {
          var source = context.createBufferSource();
          source.buffer = buffer;
          source.connect(context.destination);
          source.start(0);
        }

        var context = new AudioContext();
        var soundUrl = `https://dict.youdao.com/dictvoice?type=2&audio=${word}`
        var p = new Promise(function(resolve, reject) {
          var ret = GM_xmlhttpRequest({
            method: "GET",
            url: soundUrl,
            responseType: 'arraybuffer',
            onload: function(res) {
              try {
                context.decodeAudioData(res.response, function(buffer) {
                  resolve(buffer);
                });
              } catch(e) {
                reject(e);
              }
            }
          });
        });
        p.then(playSound, function(e) {
          console.log(e);
        });
      }

      var basic = dictJSON['basic'];
      var header = document.createElement('p');
      // header
      var span = document.createElement('span');
      span.innerHTML = query;
      header.appendChild(span);
      // phonetic if there is
      var phonetic = basic['phonetic'];
      if (phonetic) {
        var phoneticNode = document.createElement('span');
        phoneticNode.innerHTML = '[' + phonetic + ']';
        phoneticNode.style.cursor = "pointer";
        header.appendChild(phoneticNode);
        var playLogo = document.createElement('span');
        header.appendChild(phoneticNode);
        phoneticNode.addEventListener('mouseup', function(e){
          if (e.target === phoneticNode) {
          }
          e.stopPropagation();
          play(query)}, false);
      }
      header.style.color = "darkBlue";
      header.style.margin = "0";
      header.style.padding = "0";
      span.style.fontweight = "900";
      span.style.color = "black";

      youdaoWindow.appendChild(header);
      var hr = document.createElement('hr');
      hr.style.margin = "0";
      hr.style.padding = "0";
      hr.style.height = "1px";
      hr.style.borderTop = "dashed 1px black";
      youdaoWindow.appendChild(hr);
      var ul = document.createElement('ul');
      // ul style
      ul.style.margin = "0";
      ul.style.padding = "0";
      basic['explains'].map(function(trans) {
        var li = document.createElement('li');
        li.style.listStyle = "none";
        li.style.margin = "0";
        li.style.padding = "0";
        li.style.background = "none";
        li.style.color = "inherit";
        li.appendChild(document.createTextNode(trans));
        ul.appendChild(li);
      });
      youdaoWindow.appendChild(ul);

    }

    function sentence() {
      var ul = document.createElement('ul');
      // ul style
      ul.style.margin = "0";
      ul.style.padding = "0";
      dictJSON['translation'].map(function(trans) {
        var li = document.createElement('li');
        li.style.listStyle = "none";
        li.style.margin = "0";
        li.style.padding = "0";
        li.style.background = "none";
        li.style.color = "inherit";
        li.appendChild(document.createTextNode(trans));
        ul.appendChild(li);
      });
      youdaoWindow.appendChild(ul);
    }
  }

  function translate(word, ts) {
    var reqUrl = `http://fanyi.youdao.com/openapi.do?type=data&doctype=json&version=1.1&relatedUrl=http%3A%2F%2Ffanyi.youdao.com%2F%23&keyfrom=fanyiweb&key=null&translate=on&q=${word}&ts=${ts}`;
    //console.log("request url: ", reqUrl);
    var ret = GM_xmlhttpRequest({
      method: "GET",
      url: reqUrl,
      headers: {"Accept": "application/json"},  // can be omitted...
      onreadystatechange: function(res) {
        //console.log("Request state changed to: " + res.readyState);
      },
      onload: function(res) {
        var retContent = res.response;
        //console.log(retContent)
        popup(mx, my, retContent);
      },
      onerror: function(res) {
        console.log("error");
      }
    });
  }
}
<commit_msg>update<commit_after>// ==UserScript==
// @id             youdaodict-greasemonkey-reverland-2015-09-26
// @name           youdaodict
// @name:zh-CN     有道取词
// @version        1.3
// @namespace      youdao
// @author         Liu Yuyang(sa@linuxer.me)
// @description    Translate any text selected into a tooltip
// @description:zh-cn    一个可以在浏览器中自由使用的屏幕取词脚本
// @include        *
// @grant          GM_xmlhttpRequest
// ==/UserScript==

window.document.body.addEventListener("mouseup", translate, false);
window.document.body.addEventListener("keyup", toggleYoudao, false);
var toggle = true;

function toggleYoudao(e) {
  if (e.which == 81 && e.altKey && e.ctrlKey) {
    if (toggle) {
      window.document.body.removeEventListener("mouseup", translate, false);
      toggle = false;
    } else {
      window.document.body.addEventListener("mouseup", translate, false);
      toggle = true;
    }
  }
}

function translate(e) {
  // remove previous .youdaoPopup if exists
  var previous = document.querySelector(".youdaoPopup");
  if (previous) {
    document.body.removeChild(previous);
  }
  //console.log("translate start");
  var selectObj = document.getSelection();

  // if #text node
  if (selectObj.anchorNode.nodeType == 3) {
    //GM_log(selectObj.anchorNode.nodeType.toString());
    var word = selectObj.toString();
    if (word == "") {
      return;
    }
    // linebreak wordwrap, optimize for pdf.js
    word = word.replace('-\n','');
    // multiline selection, optimize for pdf.js
    word = word.replace('\n', ' ');
    //console.log("word:", word);
    var ts = new Date().getTime();
    //console.log("time: ", ts);
    var mx = e.clientX;
    var my = e.clientY;
    translate(word, ts);

  }

  function popup(mx, my, result) {
    //console.log(mx)
    //console.log(my)
    //console.log("popup window!")
    var youdaoWindow = document.createElement('div');
    youdaoWindow.classList.toggle("youdaoPopup");
    // parse
    var dictJSON = JSON.parse(result);
    console.log(dictJSON);
    var query = dictJSON['query'];
    var errorCode = dictJSON['errorCode'];
    if (dictJSON['basic']) {
      word();
    } else {
      sentence();
    }
    // main window
    // first insert into dom then there is offsetHeight！IMPORTANT！
    document.body.appendChild(youdaoWindow);
    youdaoWindow.style.color = "black";
    youdaoWindow.style.textAlign = "left";
    youdaoWindow.style.display = "block";
    youdaoWindow.style.position = "fixed";
    youdaoWindow.style.background = "lightblue";
    youdaoWindow.style.borderRadius = "5px";
    youdaoWindow.style.boxShadow = "0 0 5px 0";
    youdaoWindow.style.opacity = "0.9";
    youdaoWindow.style.width = "200px";
    youdaoWindow.style.wordWrap = "break-word";
    youdaoWindow.style.left = mx + 10 + "px";
    if (mx + 200 + 30 >= window.innerWidth) {
      youdaoWindow.style.left = parseInt(youdaoWindow.style.left) - 200 + "px";
    }
    if (my + youdaoWindow.offsetHeight + 30 >= window.innerHeight) {
      youdaoWindow.style.bottom = "20px";
    } else {
      youdaoWindow.style.top = my + 10 + "px";
    }
    youdaoWindow.style.padding = "5px";
    youdaoWindow.style.zIndex = '999999';

    function word() {

      function play(word) {
        //console.log("[DEBUG] PLAYOUND")

        function playSound(buffer) {
          var source = context.createBufferSource();
          source.buffer = buffer;
          source.connect(context.destination);
          source.start(0);
        }

        var context = new AudioContext();
        var soundUrl = `https://dict.youdao.com/dictvoice?type=2&audio=${word}`;
        var p = new Promise(function(resolve, reject) {
          var ret = GM_xmlhttpRequest({
            method: "GET",
            url: soundUrl,
            responseType: 'arraybuffer',
            onload: function(res) {
              try {
                context.decodeAudioData(res.response, function(buffer) {
                  resolve(buffer);
                });
              } catch(e) {
                reject(e);
              }
            }
          });
        });
        p.then(playSound, function(e) {
          console.log(e);
        });
      }

      var basic = dictJSON['basic'];
      var header = document.createElement('p');
      // header
      var span = document.createElement('span');
      span.innerHTML = query;
      header.appendChild(span);
      // phonetic if there is
      var phonetic = basic['phonetic'];
      if (phonetic) {
        var phoneticNode = document.createElement('span');
        phoneticNode.innerHTML = '[' + phonetic + ']';
        phoneticNode.style.cursor = "pointer";
        header.appendChild(phoneticNode);
        var playLogo = document.createElement('span');
        header.appendChild(phoneticNode);
        phoneticNode.addEventListener('mouseup', function(e){
          if (e.target === phoneticNode) {
          }
          e.stopPropagation();
          play(query)}, false);
      }
      header.style.color = "darkBlue";
      header.style.margin = "0";
      header.style.padding = "0";
      span.style.fontweight = "900";
      span.style.color = "black";

      youdaoWindow.appendChild(header);
      var hr = document.createElement('hr');
      hr.style.margin = "0";
      hr.style.padding = "0";
      hr.style.height = "1px";
      hr.style.borderTop = "dashed 1px black";
      youdaoWindow.appendChild(hr);
      var ul = document.createElement('ul');
      // ul style
      ul.style.margin = "0";
      ul.style.padding = "0";
      basic['explains'].map(function(trans) {
        var li = document.createElement('li');
        li.style.listStyle = "none";
        li.style.margin = "0";
        li.style.padding = "0";
        li.style.background = "none";
        li.style.color = "inherit";
        li.appendChild(document.createTextNode(trans));
        ul.appendChild(li);
      });
      youdaoWindow.appendChild(ul);

    }

    function sentence() {
      var ul = document.createElement('ul');
      // ul style
      ul.style.margin = "0";
      ul.style.padding = "0";
      dictJSON['translation'].map(function(trans) {
        var li = document.createElement('li');
        li.style.listStyle = "none";
        li.style.margin = "0";
        li.style.padding = "0";
        li.style.background = "none";
        li.style.color = "inherit";
        li.appendChild(document.createTextNode(trans));
        ul.appendChild(li);
      });
      youdaoWindow.appendChild(ul);
    }
  }

  function translate(word, ts) {
    var reqUrl = `http://fanyi.youdao.com/openapi.do?type=data&doctype=json&version=1.1&relatedUrl=http%3A%2F%2Ffanyi.youdao.com%2F%23&keyfrom=fanyiweb&key=null&translate=on&q=${word}&ts=${ts}`;
    //console.log("request url: ", reqUrl);
    var ret = GM_xmlhttpRequest({
      method: "GET",
      url: reqUrl,
      headers: {"Accept": "application/json"},  // can be omitted...
      onreadystatechange: function(res) {
        //console.log("Request state changed to: " + res.readyState);
      },
      onload: function(res) {
        var retContent = res.response;
        //console.log(retContent)
        popup(mx, my, retContent);
      },
      onerror: function(res) {
        console.log("error");
      }
    });
  }
}
<|endoftext|>


<commit_before>
// 3

var update = function (env, v, val) {
    // Your code here
    if(env.name === v) {
        env.value = val;
        return env;
    }
    return update(env.outer, v, val);
};

// 2

var evalScheem = function (expr, env) {
    // Numbers evaluate to themselves
    if (typeof expr === 'number') {
        return expr;
    }
    // Strings are variable references
    if (typeof expr === 'string') {
        return lookup(env, expr);
    }
    // Look at head of list for operation
    switch (expr[0]) {
        case '+':
            return evalScheem(expr[1], env) +
                   evalScheem(expr[2], env);
        case 'let-one':
            return evalScheem(expr[3], {name: expr[1],
                                        value: evalScheem(expr[2], env),
                                        outer: env});
    }
};

// 1

var lookup = function (env, v) {
    // Your code here
    if(env === null) {
        return;
    }
    if(env.name === v) {
        return env.value;
    }
    return lookup(env.outer, v);
};
<commit_msg>added some more utility code to 5-functions<commit_after>

// 6

var add_binding = function (env, v, val) {
    env.bindings[v] = val;
    return env;
};

// 5

var evalScheem = function (expr, env) {
    if (typeof expr === 'number') {
        return expr;
    }
    if (typeof expr === 'string') {
        return lookup(env, expr);
    }
    // Look at head of list for operation
    switch (expr[0]) {
        case '+':
            return evalScheem(expr[1], env) +
                   evalScheem(expr[2], env);
        case 'lambda-one':
              return function(_arg) {
                var b = {};
                b[expr[1]] = _arg;
                return evalScheem(expr[2],
                                  {bindings: b, outer: env});
            };
        default:
            // Simple application
            var func = evalScheem(expr[0], env);
            var arg = evalScheem(expr[1], env);
            return func(arg);
    }
};

// 4

var evalScheem = function (expr, env) {
    if (typeof expr === 'number') {
        return expr;
    }
    if (typeof expr === 'string') {
        return lookup(env, expr);
    }
    // Look at head of list for operation
    switch (expr[0]) {
        case '+':
            return evalScheem(expr[1], env) +
                   evalScheem(expr[2], env);
        case 'quote':
            return expr[1];
        default:
            var fn = evalScheem(expr[0], env);
            return fn(evalScheem(expr[1], env));
    }
};

// 3

var update = function (env, v, val) {
    if(env.bindings[v] !== undefined) {
        env.bindings[v] = val;
        return env;
    }
    return update(env.outer, v, val);
};

// 2

var evalScheem = function (expr, env) {
    // Numbers evaluate to themselves
    if (typeof expr === 'number') {
        return expr;
    }
    // Strings are variable references
    if (typeof expr === 'string') {
        return lookup(env, expr);
    }
    // Look at head of list for operation
    switch (expr[0]) {
        case '+':
            return evalScheem(expr[1], env) +
                   evalScheem(expr[2], env);
        case 'let-one':
            var bindings = {};
            bindings[expr[1]] = evalScheem(expr[2], env);
            return evalScheem(expr[3], {bindings: bindings,
                                        outer: env});
    }
};

// 1

var lookup = function (env, v) {
    if(env === {}) {
        return;
    }
    if(env.bindings[v] !== undefined) {
        return env.bindings[v];
    }
    return lookup(env.outer, v);
};
<|endoftext|>


<commit_before>/*
 * Copyright 2002-2014, University of Colorado Boulder
 */

/**
 * A numeric range.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  var dot = require( 'DOT/dot' );

  dot.Range = function Range( min, max, defaultValue ) {

    this.min = min;
    this.max = max;
    this.defaultValue = ( defaultValue === undefined ) ? min : defaultValue;

    assert && assert( min <= max );
    assert && assert( ( this.defaultValue >= min ) && ( this.defaultValue <= max ) );
  };
  var Range = dot.Range;

  Range.prototype = {

    constructor: Range,

    copy: function() {
      return new Range( this.min, this.max, this.defaultValue );
    },

    getLength: function() {
      return this.max - this.min;
    },

    getCenter: function() {
      return (this.max + this.min) / 2;
    },

    contains: function( value ) {
      return ( value >= this.min ) && ( value <= this.max );
    },

    /**
     * Does this range contain the specified range?
     * @param {Range} range
     * @returns {boolean}
     */
    containsRange: function( range ) {
      return this.min <= range.min && this.max >= range.max;
    },

    intersects: function( range ) {
      return ( this.max >= range.min ) && ( range.max >= this.min );
    },

    /**
     * Do the two ranges overlap with one another?  Note that this assumes that
     * this is a open interval.
     */
    intersectsExclusive: function( range ) {
      return ( this.max > range.min ) && ( range.max > this.min );
    },

    toString: function() {
      return "[Range (min:" + this.min + " max:" + this.max + " defaultValue:" + this.defaultValue + ")]";
    },

    /**
     * Constrains a value to the range.
     * @param {number} value
     * @returns {number}
     */
    constrainValue: function( value ) {
      return Math.min( Math.max( value, this.min ), this.max );
    }
  };

  return Range;
} );
<commit_msg>add dot.Range.equals<commit_after>/*
 * Copyright 2002-2014, University of Colorado Boulder
 */

/**
 * A numeric range.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  var dot = require( 'DOT/dot' );

  dot.Range = function Range( min, max, defaultValue ) {

    this.min = min;
    this.max = max;
    this.defaultValue = ( defaultValue === undefined ) ? min : defaultValue;

    assert && assert( min <= max );
    assert && assert( ( this.defaultValue >= min ) && ( this.defaultValue <= max ) );
  };
  var Range = dot.Range;

  Range.prototype = {

    constructor: Range,

    copy: function() {
      return new Range( this.min, this.max, this.defaultValue );
    },

    getLength: function() {
      return this.max - this.min;
    },

    getCenter: function() {
      return (this.max + this.min) / 2;
    },

    contains: function( value ) {
      return ( value >= this.min ) && ( value <= this.max );
    },

    /**
     * Does this range contain the specified range?
     * @param {Range} range
     * @returns {boolean}
     */
    containsRange: function( range ) {
      return this.min <= range.min && this.max >= range.max;
    },

    intersects: function( range ) {
      return ( this.max >= range.min ) && ( range.max >= this.min );
    },

    /**
     * Do the two ranges overlap with one another?  Note that this assumes that
     * this is a open interval.
     */
    intersectsExclusive: function( range ) {
      return ( this.max > range.min ) && ( range.max > this.min );
    },

    toString: function() {
      return "[Range (min:" + this.min + " max:" + this.max + " defaultValue:" + this.defaultValue + ")]";
    },

    /**
     * Constrains a value to the range.
     * @param {number} value
     * @returns {number}
     */
    constrainValue: function( value ) {
      return Math.min( Math.max( value, this.min ), this.max );
    },

    equals: function( other ) {
      return this.min === other.min && this.max === other.max && this.defaultValue === this.defaultValue;
    }
  };

  return Range;
} );
<|endoftext|>


<commit_before>function changeIfEqual(variable, value, newValue){
    return variable == value ? newValue : variable;
}
Array.prototype.contains = function(obj){
    var i = this.length;
    while(i--)
        if(this[i] == obj)
            return true;
    return false;

    for(var i in this)
        if(this[i] == obj)
            return true;
    return false;
}
<commit_msg>Update Utils.js<commit_after>function changeIfEqual(variable, value, newValue){
    return variable == value ? newValue : variable;
}
function negate(func) {
	return function(x) {
		return !func(x);
	};
}

function equal(el1, el2){
	return el1 == el2;
}

function greaterThen(el1, el2){
	return el1 > el2;
}

function lessThen(el1, el2){
	return el1 < el2;
}

Array.prototype.sum = function(total = 0) {
	this.forEach(function (number){
		total += number;
	});
	return total;
}

Array.prototype.max = function(numbers) {
	var max, j = 0;
	this.forEach(function (number) {
		max = equal(j++, 0) ? number : (greaterThen(max, number) ? max : number);
	});
	return max;
}

Array.prototype.min = function(numbers) {
	var min, j = 0;
	this.forEach(function (number) {
		min = equal(j++, 0) ? number : (lessThen(min, number) ? min : number);
	});
	return min;
}

Array.prototype.head = function(){//existuje shift - ale ten vymazava
	this.length > 0 ? this[0] : false;
}

Array.prototype.last = function(){//existuje pop - ale ten vymazava
	this.length > 0 ? this[this.length - 1] : false;
}

Array.prototype.merge = function(){
	return this.concat().sort();
}

Array.prototype.product = function(func, result = []){
	this.forEach(function(element){
		result.push(func(element));
	});
	return result;
}
<|endoftext|>


<commit_before>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class bitso extends Exchange {

    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'bitso',
            'name': 'Bitso',
            'countries': 'MX', // Mexico
            'rateLimit': 2000, // 30 requests per minute
            'version': 'v3',
            'has': {
                'CORS': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766335-715ce7aa-5ed5-11e7-88a8-173a27bb30fe.jpg',
                'api': 'https://api.bitso.com',
                'www': 'https://bitso.com',
                'doc': 'https://bitso.com/api_info',
            },
            'api': {
                'public': {
                    'get': [
                        'available_books',
                        'ticker',
                        'order_book',
                        'trades',
                    ],
                },
                'private': {
                    'get': [
                        'account_status',
                        'balance',
                        'fees',
                        'fundings',
                        'fundings/{fid}',
                        'funding_destination',
                        'kyc_documents',
                        'ledger',
                        'ledger/trades',
                        'ledger/fees',
                        'ledger/fundings',
                        'ledger/withdrawals',
                        'mx_bank_codes',
                        'open_orders',
                        'order_trades/{oid}',
                        'orders/{oid}',
                        'user_trades',
                        'user_trades/{tid}',
                        'withdrawals/',
                        'withdrawals/{wid}',
                    ],
                    'post': [
                        'bitcoin_withdrawal',
                        'debit_card_withdrawal',
                        'ether_withdrawal',
                        'orders',
                        'phone_number',
                        'phone_verification',
                        'phone_withdrawal',
                        'spei_withdrawal',
                    ],
                    'delete': [
                        'orders/{oid}',
                        'orders/all',
                    ],
                },
            },
        });
    }

    async fetchMarkets () {
        let markets = await this.publicGetAvailableBooks ();
        let result = [];
        for (let i = 0; i < markets['payload'].length; i++) {
            let market = markets['payload'][i];
            let id = market['book'];
            let symbol = id.toUpperCase ().replace ('_', '/');
            let [ base, quote ] = symbol.split ('/');
            let limits = {
                'amount': {
                    'min': parseFloat (market['minimum_amount']),
                    'max': parseFloat (market['maximum_amount']),
                },
                'price': {
                    'min': parseFloat (market['minimum_price']),
                    'max': parseFloat (market['maximum_price']),
                },
                'cost': {
                    'min': parseFloat (market['minimum_value']),
                    'max': parseFloat (market['maximum_value']),
                },
            };
            let precision = {
                'amount': this.precisionFromString (market['minimum_amount']),
                'price': this.precisionFromString (market['minimum_price']),
            };
            let lot = limits['amount']['min'];
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'info': market,
                'lot': lot,
                'limits': limits,
                'precision': precision,
            });
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let response = await this.privateGetBalance ();
        let balances = response['payload']['balances'];
        let result = { 'info': response };
        for (let b = 0; b < balances.length; b++) {
            let balance = balances[b];
            let currency = balance['currency'].toUpperCase ();
            let account = {
                'free': parseFloat (balance['available']),
                'used': parseFloat (balance['locked']),
                'total': parseFloat (balance['total']),
            };
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, params = {}) {
        await this.loadMarkets ();
        let response = await this.publicGetOrderBook (this.extend ({
            'book': this.marketId (symbol),
        }, params));
        let orderbook = response['payload'];
        let timestamp = this.parse8601 (orderbook['updated_at']);
        return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount');
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let response = await this.publicGetTicker (this.extend ({
            'book': this.marketId (symbol),
        }, params));
        let ticker = response['payload'];
        let timestamp = this.parse8601 (ticker['created_at']);
        let vwap = parseFloat (ticker['vwap']);
        let baseVolume = parseFloat (ticker['volume']);
        let quoteVolume = baseVolume * vwap;
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': parseFloat (ticker['high']),
            'low': parseFloat (ticker['low']),
            'bid': parseFloat (ticker['bid']),
            'ask': parseFloat (ticker['ask']),
            'vwap': vwap,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': parseFloat (ticker['last']),
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        };
    }

    parseTrade (trade, market = undefined) {
        let timestamp = this.parse8601 (trade['created_at']);
        let symbol = undefined;
        if (!market) {
            if ('book' in trade)
                market = this.markets_by_id[trade['book']];
        }
        if (market)
            symbol = market['symbol'];
        return {
            'id': trade['tid'].toString (),
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'order': undefined,
            'type': undefined,
            'side': trade['maker_side'],
            'price': parseFloat (trade['price']),
            'amount': parseFloat (trade['amount']),
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTrades (this.extend ({
            'book': market['id'],
        }, params));
        return this.parseTrades (response['payload'], market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let order = {
            'book': this.marketId (symbol),
            'side': side,
            'type': type,
            'major': this.amountToPrecision (symbol, amount),
        };
        if (type === 'limit')
            order['price'] = this.priceToPrecision (symbol, price);
        let response = await this.privatePostOrders (this.extend (order, params));
        return {
            'info': response,
            'id': response['payload']['oid'],
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        return await this.privateDeleteOrders ({ 'oid': id });
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let query = '/' + this.version + '/' + this.implodeParams (path, params);
        let url = this.urls['api'] + query;
        if (api === 'public') {
            if (Object.keys (params).length)
                url += '?' + this.urlencode (params);
        } else {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ().toString ();
            let request = [ nonce, method, query ].join ('');
            if (Object.keys (params).length) {
                body = this.json (params);
                request += body;
            }
            let signature = this.hmac (this.encode (request), this.encode (this.secret));
            let auth = this.apiKey + ':' + nonce + ':' + signature;
            headers = {
                'Authorization': 'Bitso ' + auth,
                'Content-Type': 'application/json',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if ('success' in response)
            if (response['success'])
                return response;
        throw new ExchangeError (this.id + ' ' + this.json (response));
    }
}
<commit_msg>bitso cancelOrder privateDeleteOrdersOid #1437<commit_after>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class bitso extends Exchange {

    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'bitso',
            'name': 'Bitso',
            'countries': 'MX', // Mexico
            'rateLimit': 2000, // 30 requests per minute
            'version': 'v3',
            'has': {
                'CORS': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766335-715ce7aa-5ed5-11e7-88a8-173a27bb30fe.jpg',
                'api': 'https://api.bitso.com',
                'www': 'https://bitso.com',
                'doc': 'https://bitso.com/api_info',
            },
            'api': {
                'public': {
                    'get': [
                        'available_books',
                        'ticker',
                        'order_book',
                        'trades',
                    ],
                },
                'private': {
                    'get': [
                        'account_status',
                        'balance',
                        'fees',
                        'fundings',
                        'fundings/{fid}',
                        'funding_destination',
                        'kyc_documents',
                        'ledger',
                        'ledger/trades',
                        'ledger/fees',
                        'ledger/fundings',
                        'ledger/withdrawals',
                        'mx_bank_codes',
                        'open_orders',
                        'order_trades/{oid}',
                        'orders/{oid}',
                        'user_trades',
                        'user_trades/{tid}',
                        'withdrawals/',
                        'withdrawals/{wid}',
                    ],
                    'post': [
                        'bitcoin_withdrawal',
                        'debit_card_withdrawal',
                        'ether_withdrawal',
                        'orders',
                        'phone_number',
                        'phone_verification',
                        'phone_withdrawal',
                        'spei_withdrawal',
                    ],
                    'delete': [
                        'orders/{oid}',
                        'orders/all',
                    ],
                },
            },
        });
    }

    async fetchMarkets () {
        let markets = await this.publicGetAvailableBooks ();
        let result = [];
        for (let i = 0; i < markets['payload'].length; i++) {
            let market = markets['payload'][i];
            let id = market['book'];
            let symbol = id.toUpperCase ().replace ('_', '/');
            let [ base, quote ] = symbol.split ('/');
            let limits = {
                'amount': {
                    'min': parseFloat (market['minimum_amount']),
                    'max': parseFloat (market['maximum_amount']),
                },
                'price': {
                    'min': parseFloat (market['minimum_price']),
                    'max': parseFloat (market['maximum_price']),
                },
                'cost': {
                    'min': parseFloat (market['minimum_value']),
                    'max': parseFloat (market['maximum_value']),
                },
            };
            let precision = {
                'amount': this.precisionFromString (market['minimum_amount']),
                'price': this.precisionFromString (market['minimum_price']),
            };
            let lot = limits['amount']['min'];
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'info': market,
                'lot': lot,
                'limits': limits,
                'precision': precision,
            });
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let response = await this.privateGetBalance ();
        let balances = response['payload']['balances'];
        let result = { 'info': response };
        for (let b = 0; b < balances.length; b++) {
            let balance = balances[b];
            let currency = balance['currency'].toUpperCase ();
            let account = {
                'free': parseFloat (balance['available']),
                'used': parseFloat (balance['locked']),
                'total': parseFloat (balance['total']),
            };
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, params = {}) {
        await this.loadMarkets ();
        let response = await this.publicGetOrderBook (this.extend ({
            'book': this.marketId (symbol),
        }, params));
        let orderbook = response['payload'];
        let timestamp = this.parse8601 (orderbook['updated_at']);
        return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount');
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let response = await this.publicGetTicker (this.extend ({
            'book': this.marketId (symbol),
        }, params));
        let ticker = response['payload'];
        let timestamp = this.parse8601 (ticker['created_at']);
        let vwap = parseFloat (ticker['vwap']);
        let baseVolume = parseFloat (ticker['volume']);
        let quoteVolume = baseVolume * vwap;
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': parseFloat (ticker['high']),
            'low': parseFloat (ticker['low']),
            'bid': parseFloat (ticker['bid']),
            'ask': parseFloat (ticker['ask']),
            'vwap': vwap,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': parseFloat (ticker['last']),
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        };
    }

    parseTrade (trade, market = undefined) {
        let timestamp = this.parse8601 (trade['created_at']);
        let symbol = undefined;
        if (!market) {
            if ('book' in trade)
                market = this.markets_by_id[trade['book']];
        }
        if (market)
            symbol = market['symbol'];
        return {
            'id': trade['tid'].toString (),
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'order': undefined,
            'type': undefined,
            'side': trade['maker_side'],
            'price': parseFloat (trade['price']),
            'amount': parseFloat (trade['amount']),
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTrades (this.extend ({
            'book': market['id'],
        }, params));
        return this.parseTrades (response['payload'], market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let order = {
            'book': this.marketId (symbol),
            'side': side,
            'type': type,
            'major': this.amountToPrecision (symbol, amount),
        };
        if (type === 'limit')
            order['price'] = this.priceToPrecision (symbol, price);
        let response = await this.privatePostOrders (this.extend (order, params));
        return {
            'info': response,
            'id': response['payload']['oid'],
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        return await this.privateDeleteOrdersOid ({ 'oid': id });
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let query = '/' + this.version + '/' + this.implodeParams (path, params);
        let url = this.urls['api'] + query;
        if (api === 'public') {
            if (Object.keys (params).length)
                url += '?' + this.urlencode (params);
        } else {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ().toString ();
            let request = [ nonce, method, query ].join ('');
            if (Object.keys (params).length) {
                body = this.json (params);
                request += body;
            }
            let signature = this.hmac (this.encode (request), this.encode (this.secret));
            let auth = this.apiKey + ':' + nonce + ':' + signature;
            headers = {
                'Authorization': 'Bitso ' + auth,
                'Content-Type': 'application/json',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if ('success' in response)
            if (response['success'])
                return response;
        throw new ExchangeError (this.id + ' ' + this.json (response));
    }
}
<|endoftext|>