<commit_before>var socket;
var users = [];
var foods = [];
var player = {};

// var onevent = socket.onevent;
// socket.onevent = function (packet) {
//     var args = packet.data || [];
//     onevent.call (this, packet);    // original call
//     packet.data = ["*"].concat(args);
//     onevent.call(this, packet);      // additional call to catch-all
// };
//
// socket.on('*', function(event,data) {
//     if(!data) data = "";
//     PACKET += objectSize(socket.handshake, data);
// } );
// }

var PACKETR = 0;
var PACKETE = 0;

function objectSize( object , msg )
{
		/*
			Ici ta fonction qui calcule la taille du packet envoyé
			en faisant une fonction récursive et en calculant le
			nombre de byte, cf function en dessous
		*/
		var result = roughSizeOfObject(object);
		if(msg)
			result += roughSizeOfObject(msg);
		return result;
}

function roughSizeOfObject( value, level ) {
    if(level == undefined) level = 0;
    var bytes = 0;

    if ( typeof value === 'boolean' ) {
        bytes = 4;
    }
    else if ( typeof value === 'string' ) {
        bytes = value.length * 2;
    }
    else if ( typeof value === 'number' ) {
        bytes = 8;
    }
    else if ( typeof value === 'object' ) {
        if(value['__visited__']) return 0;
        value['__visited__'] = 1;
        for( i in value ) {
            bytes += i.length * 2;
            bytes+= 8; // an assumed existence overhead
            bytes+= roughSizeOfObject( value[i], 1 )
        }
    }

    if(level == 0){
        clear__visited__(value);
    }
    return bytes;
}

function clear__visited__(value){
    if(typeof value == 'object'){
        delete value['__visited__'];
        for(var i in value){
            clear__visited__(value[i]);
        }
    }
}

function setupSocket() {
  var onevent = socket.onevent;
  socket.onevent = function(packet) {
    var args = packet.data || [];
    onevent.call (this, packet);    // original call
    packet.data = ["*"].concat(args);
    onevent.call(this, packet);
  };

  socket.on('*', function(event, data) {
    if(!data) data = "";
    PACKETR += objectSize(socket.handshake, data);
  });

  socket.on('1', function (playerSettings) {
    var playerSettings = JSON.parse(playerSettings);
    player = playerSettings;
    player.name = playerName;
    player.w = screenWidth;
    player.h = screenHeight;
    // chat.addSystemLine('Bienvenue petit serpent!');
    PACKETE += objectSize(player);
    socket.emit('2', player);
    gameStart = true;
    c.focus();
  } );

  socket.on('4', function(data) {
    console.log("[INFO] Game Setup");
    data = JSON.parse(data);
    gameWidth = data.w;
    gameHeight = data.h;
    player.s = data.s;
    player.sa = data.sa;
    resize();
  });

  socket.on('6', function (userData) {
		if(userData != undefined) {
	    userData = JSON.parse(userData);
	    for (var key in userData) {
	      users[key] = userData[key];
				if(userData[key][4]) {
					player[0] = userData[key][0];
					player[1] = userData[key][1];
				}
	    }
		}
  });
}

window.addEventListener('resize', resize);

function resize() {
    player.w = screenWidth = window.innerWidth;
    player.h = screenHeight = window.innerHeight;

    if (socket != undefined)
      socket.emit('r', { w: screenWidth, h: screenHeight });
}
<commit_msg>Always delete array user:<commit_after>var socket;
var users = [];
var foods = [];
var player = {};

// var onevent = socket.onevent;
// socket.onevent = function (packet) {
//     var args = packet.data || [];
//     onevent.call (this, packet);    // original call
//     packet.data = ["*"].concat(args);
//     onevent.call(this, packet);      // additional call to catch-all
// };
//
// socket.on('*', function(event,data) {
//     if(!data) data = "";
//     PACKET += objectSize(socket.handshake, data);
// } );
// }

var PACKETR = 0;
var PACKETE = 0;

function objectSize( object , msg )
{
		/*
			Ici ta fonction qui calcule la taille du packet envoyé
			en faisant une fonction récursive et en calculant le
			nombre de byte, cf function en dessous
		*/
		var result = roughSizeOfObject(object);
		if(msg)
			result += roughSizeOfObject(msg);
		return result;
}

function roughSizeOfObject( value, level ) {
    if(level == undefined) level = 0;
    var bytes = 0;

    if ( typeof value === 'boolean' ) {
        bytes = 4;
    }
    else if ( typeof value === 'string' ) {
        bytes = value.length * 2;
    }
    else if ( typeof value === 'number' ) {
        bytes = 8;
    }
    else if ( typeof value === 'object' ) {
        if(value['__visited__']) return 0;
        value['__visited__'] = 1;
        for( i in value ) {
            bytes += i.length * 2;
            bytes+= 8; // an assumed existence overhead
            bytes+= roughSizeOfObject( value[i], 1 )
        }
    }

    if(level == 0){
        clear__visited__(value);
    }
    return bytes;
}

function clear__visited__(value){
    if(typeof value == 'object'){
        delete value['__visited__'];
        for(var i in value){
            clear__visited__(value[i]);
        }
    }
}

function setupSocket() {
  var onevent = socket.onevent;
  socket.onevent = function(packet) {
    var args = packet.data || [];
    onevent.call (this, packet);    // original call
    packet.data = ["*"].concat(args);
    onevent.call(this, packet);
  };

  socket.on('*', function(event, data) {
    if(!data) data = "";
    PACKETR += objectSize(socket.handshake, data);
  });

  socket.on('1', function (playerSettings) {
    var playerSettings = JSON.parse(playerSettings);
    player = playerSettings;
    player.name = playerName;
    player.w = screenWidth;
    player.h = screenHeight;
    // chat.addSystemLine('Bienvenue petit serpent!');
    PACKETE += objectSize(player);
    socket.emit('2', player);
    gameStart = true;
    c.focus();
  } );

  socket.on('4', function(data) {
    console.log("[INFO] Game Setup");
    data = JSON.parse(data);
    gameWidth = data.w;
    gameHeight = data.h;
    player.s = data.s;
    player.sa = data.sa;
    resize();
  });

  socket.on('6', function (userData) {
		if(userData != undefined) {
	    userData = JSON.parse(userData);
			users = [];
	    for (var key in userData) {
	      users[key] = userData[key];
				if(userData[key][4]) {
					player[0] = userData[key][0];
					player[1] = userData[key][1];
				}
	    }
		}
  });
}

window.addEventListener('resize', resize);

function resize() {
    player.w = screenWidth = window.innerWidth;
    player.h = screenHeight = window.innerHeight;

    if (socket != undefined)
      socket.emit('r', { w: screenWidth, h: screenHeight });
}
<|endoftext|>


<commit_before>import {isEqual} from 'lodash'

import Action from '../../action'
import {http, websocket} from './network'
import Codec from './codec'

export default function API(getState, dispatch, next) {
  return {
    async fetchUser() {
      const user = await http('GET', '/api/user/userinfo')
      next(Action.User.profile.create(
        Codec.User.decode(user)
      ))
    },

    async fetchPlaylist() {
      const state = getState()
      const sync = state.user.preferences.sync
      if (!sync) return
      const urls = sync.split('\n').filter(line => line)
      if (!urls.length) return
      const lists = await Promise.all(urls.map(url =>
        http('POST', '/provider/songListWithUrl', {
          url,
          withCookie: state.user.preferences.cookie
        })
      ))
      const songs = [].concat(...lists.map(list => list.songs || []))
      next(Action.Song.assign.create(
        songs.map(Codec.Song.decode)
      ))
    },

    async sendChannel(prevState) {
      const state = getState()
      const channel = state.channel.name
      if (!channel) return
      const prevChannel = prevState && prevState.channel.name
      if (channel == prevChannel) return
      await http('POST', `/api/channel/join/${channel}`)
    },

    async sendUpnext(prevState) {
      const state = getState()
      if (!state.channel.name) return
      const song = Codec.Song.encode(
        state.user.preferences.listenOnly
          ? undefined
          : state.song.playlist[0]
      )
      const prevSong = Codec.Song.encode(prevState && (
          prevState.user.preferences.listenOnly
            ? undefined
            : prevState.song.playlist[0]
        ))
      if (prevState && isEqual(song, prevSong)) return
      await http('POST', `/api/channel/updateNextSong`, {
        ...song,
        withCookie: state.user.preferences.cookie
      })
    },

    async sendSync() {
      const state = getState()
      if (!state.channel.name) return
      const song = Codec.Song.encode(state.song.playing)
      await http('POST', `/api/channel/finished`,
        song.songId ? song : null
      )
    },

    async sendDownvote() {
      const state = getState()
      if (!state.channel.name) return
      const song = Codec.Song.encode(state.song.playing)
      await http('POST', `/api/channel/downVote`,
        song.songId ? song : null
      )
    },

    async sendSearch() {
      const state = getState()
      const keyword = state.search.keyword
      if (keyword) {
        const results = await http('POST', '/provider/searchSongs', {
          key: keyword,
          withCookie: state.user.preferences.cookie
        })
        next(Action.Search.results.create(
          results.map(Codec.Song.decode)
        ))
      } else {
        next(Action.Search.results.create([]))
      }
    },

    receiveMessage(callback) {
      websocket('/api/ws', ({connect, send}) => async (event, data) => {
        switch (event) {
          case 'open':
            callback(event)
            break
          case 'close':
            setTimeout(connect, 5000)
            callback(event)
            break
          case 'error':
            callback(event)
            break
          case 'UserListUpdated':
            next(Action.Channel.members.create(
              data.users.map(Codec.User.decode)
            ))
            break
          case 'Play':
            next(Action.Player.reset.create())
            if (data.downvote) {
              next(Action.Player.downvote.create())
            }
            next(Action.Song.play.create(data.song && {
              ...Codec.Song.decode(data.song),
              player: data.user || '',
              time: (Date.now() / 1000) - (data.elapsed || 0)
            }))
            break
          case 'NextSongUpdate':
            next(Action.Song.preload.create(data.song && {
              ...Codec.Song.decode(data.song)
            }))
            break
        }
      })
    }
  }
}
<commit_msg>fix lint<commit_after>import {isEqual} from 'lodash'

import Action from '../../action'
import {http, websocket} from './network'
import Codec from './codec'

export default function API(getState, dispatch, next) {
  return {
    async fetchUser() {
      const user = await http('GET', '/api/user/userinfo')
      next(Action.User.profile.create(
        Codec.User.decode(user)
      ))
    },

    async fetchPlaylist() {
      const state = getState()
      const sync = state.user.preferences.sync
      if (!sync) return
      const urls = sync.split('\n').filter(line => line)
      if (!urls.length) return
      const lists = await Promise.all(urls.map(url =>
        http('POST', '/provider/songListWithUrl', {
          url,
          withCookie: state.user.preferences.cookie
        })
      ))
      const songs = [].concat(...lists.map(list => list.songs || []))
      next(Action.Song.assign.create(
        songs.map(Codec.Song.decode)
      ))
    },

    async sendChannel(prevState) {
      const state = getState()
      const channel = state.channel.name
      if (!channel) return
      const prevChannel = prevState && prevState.channel.name
      if (channel == prevChannel) return
      await http('POST', `/api/channel/join/${channel}`)
    },

    async sendUpnext(prevState) {
      const state = getState()
      if (!state.channel.name) return
      const song = Codec.Song.encode(
        state.user.preferences.listenOnly
          ? undefined
          : state.song.playlist[0]
      )
      const prevSong = Codec.Song.encode(prevState && (
          prevState.user.preferences.listenOnly
            ? undefined
            : prevState.song.playlist[0]
        ))
      if (prevState && isEqual(song, prevSong)) return
      await http('POST', '/api/channel/updateNextSong', {
        ...song,
        withCookie: state.user.preferences.cookie
      })
    },

    async sendSync() {
      const state = getState()
      if (!state.channel.name) return
      const song = Codec.Song.encode(state.song.playing)
      await http('POST', '/api/channel/finished',
        song.songId ? song : null
      )
    },

    async sendDownvote() {
      const state = getState()
      if (!state.channel.name) return
      const song = Codec.Song.encode(state.song.playing)
      await http('POST', '/api/channel/downVote',
        song.songId ? song : null
      )
    },

    async sendSearch() {
      const state = getState()
      const keyword = state.search.keyword
      if (keyword) {
        const results = await http('POST', '/provider/searchSongs', {
          key: keyword,
          withCookie: state.user.preferences.cookie
        })
        next(Action.Search.results.create(
          results.map(Codec.Song.decode)
        ))
      } else {
        next(Action.Search.results.create([]))
      }
    },

    receiveMessage(callback) {
      websocket('/api/ws', ({connect, send}) => async (event, data) => {
        switch (event) {
          case 'open':
            callback(event)
            break
          case 'close':
            setTimeout(connect, 5000)
            callback(event)
            break
          case 'error':
            callback(event)
            break
          case 'UserListUpdated':
            next(Action.Channel.members.create(
              data.users.map(Codec.User.decode)
            ))
            break
          case 'Play':
            next(Action.Player.reset.create())
            if (data.downvote) {
              next(Action.Player.downvote.create())
            }
            next(Action.Song.play.create(data.song && {
              ...Codec.Song.decode(data.song),
              player: data.user || '',
              time: (Date.now() / 1000) - (data.elapsed || 0)
            }))
            break
          case 'NextSongUpdate':
            next(Action.Song.preload.create(data.song && {
              ...Codec.Song.decode(data.song)
            }))
            break
        }
      })
    }
  }
}
<|endoftext|>


<commit_before>var FactRelationSearchView = Backbone.View.extend({
  events: {
    "keyup input": "doSearch",
    "blur input": "cancelSearch",
    "click li.add": "addNewFactRelation"
  },

  _lastKnownSearch: "",

  _searchResultViews: [],

  initialize: function() {
    this.useTemplate("fact_relations","_evidence_search");
  },

  render: function() {
    $(this.el).html(Mustache.to_html(this.tmpl, {}, this.partials));

    if ( this.options.type === "supporting" ) {
      $( this.el ).find('.add-evidence.supporting' ).css('display','block');
    } else {
      $( this.el ).find('.add-evidence.weakening' ).css('display','block');
    }

    return this;
  },

  cancelSearch: function(e) {
    this.truncateSearchContainer();

    $('input', this.el).val('');
  },

  doSearch: _.throttle(function() {
    var searchVal = $('input:visible', this.el).val();
    var self = this;

    if ( searchVal.length < 2 && searchVal !== this._lastKnownSearch ) {
      this.truncateSearchContainer();

      return;
    }

    this._lastKnownSearch = searchVal;

    this.setLoading();

    $.ajax({
      url: this.options.factRelations.fact.url() + "/evidence_search",
      data: {
        s: searchVal
      },
      success: function(searchResults) {
        self.parseSearchResults.call(self, searchResults);

        $( self.el )
          .find('li.add>span.word')
          .text(searchVal)
          .closest('li')
          .show();

        self.stopLoading();
      }
    });
  }, 300),

  parseSearchResults: function(searchResults) {
    var self = this;
    var searchResultsContainer = $(this.el).find('.search-results');

    this.truncateSearchContainer();

    _.forEach(searchResults, function(searchResult) {
      var view = new FactRelationSearchResultView({
        model: new FactRelationSearchResult(searchResult),
        // Give the search result a reference to the FactRelation collection
        factRelations: self.options.factRelations,
        parentView: self
      });

      searchResultsContainer.find('li.loading').after( view.render().el );

      self._searchResultViews.push(view);
    });
  },

  addNewFactRelation: function(e) {
    var self = this;
    var factRelations = self.options.factRelations;

    self.setAddingIndicator();

    $.ajax({
      url: factRelations.url(),
      type: "POST",
      data: {
        fact_id: factRelations.fact.get('id'),
        displaystring: this._lastKnownSearch
      },
      success: function(newFactRelation) {
        factRelations.add(new factRelations.model(newFactRelation), {
          highlight: true
        });

        self.cancelSearch();
        self.stopAddingIndicator();
      },
      error: function() {
        self.stopAddingIndicator();
      }
    });
  },

  truncateSearchContainer: function() {
    _.forEach(this._searchResultViews, function(view) {
      view.remove();
    });

    $( this.el ).find('li.add').hide();

    this._searchResultViews = [];
  },

  setLoading: function() {
    $( this.el ).find('li.loading').show();
  },

  stopLoading: function() {
    $( this.el ).find('li.loading').hide();
  },

  setAddingIndicator: function() {
    $( this.el ).find('.add img').show();
    $( this.el ).find('.add .add-message').text('Adding');
  },
  stopAddingIndicator: function() {
    $( this.el ).find('.add img').hide();
    $( this.el ).find('.add .add-message').text('Add');
  }
});
<commit_msg>Cancelling the search on blur stops evidence from being added. Remove for now<commit_after>var FactRelationSearchView = Backbone.View.extend({
  events: {
    "keyup input": "doSearch",
    "click li.add": "addNewFactRelation"
  },

  _lastKnownSearch: "",

  _searchResultViews: [],

  initialize: function() {
    this.useTemplate("fact_relations","_evidence_search");
  },

  render: function() {
    $(this.el).html(Mustache.to_html(this.tmpl, {}, this.partials));

    if ( this.options.type === "supporting" ) {
      $( this.el ).find('.add-evidence.supporting' ).css('display','block');
    } else {
      $( this.el ).find('.add-evidence.weakening' ).css('display','block');
    }

    return this;
  },

  cancelSearch: function(e) {
    this.truncateSearchContainer();

    $('input', this.el).val('');
  },

  doSearch: _.throttle(function() {
    var searchVal = $('input:visible', this.el).val();
    var self = this;

    if ( searchVal.length < 2 && searchVal !== this._lastKnownSearch ) {
      this.truncateSearchContainer();

      return;
    }

    this._lastKnownSearch = searchVal;

    this.setLoading();

    $.ajax({
      url: this.options.factRelations.fact.url() + "/evidence_search",
      data: {
        s: searchVal
      },
      success: function(searchResults) {
        self.parseSearchResults.call(self, searchResults);

        $( self.el )
          .find('li.add>span.word')
          .text(searchVal)
          .closest('li')
          .show();

        self.stopLoading();
      }
    });
  }, 300),

  parseSearchResults: function(searchResults) {
    var self = this;
    var searchResultsContainer = $(this.el).find('.search-results');

    this.truncateSearchContainer();

    _.forEach(searchResults, function(searchResult) {
      var view = new FactRelationSearchResultView({
        model: new FactRelationSearchResult(searchResult),
        // Give the search result a reference to the FactRelation collection
        factRelations: self.options.factRelations,
        parentView: self
      });

      searchResultsContainer.find('li.loading').after( view.render().el );

      self._searchResultViews.push(view);
    });
  },

  addNewFactRelation: function(e) {
    var self = this;
    var factRelations = self.options.factRelations;

    self.setAddingIndicator();

    $.ajax({
      url: factRelations.url(),
      type: "POST",
      data: {
        fact_id: factRelations.fact.get('id'),
        displaystring: this._lastKnownSearch
      },
      success: function(newFactRelation) {
        factRelations.add(new factRelations.model(newFactRelation), {
          highlight: true
        });

        self.cancelSearch();
        self.stopAddingIndicator();
      },
      error: function() {
        self.stopAddingIndicator();
      }
    });
  },

  truncateSearchContainer: function() {
    _.forEach(this._searchResultViews, function(view) {
      view.remove();
    });

    $( this.el ).find('li.add').hide();

    this._searchResultViews = [];
  },

  setLoading: function() {
    $( this.el ).find('li.loading').show();
  },

  stopLoading: function() {
    $( this.el ).find('li.loading').hide();
  },

  setAddingIndicator: function() {
    $( this.el ).find('.add img').show();
    $( this.el ).find('.add .add-message').text('Adding');
  },
  stopAddingIndicator: function() {
    $( this.el ).find('.add img').hide();
    $( this.el ).find('.add .add-message').text('Add');
  }
});
<|endoftext|>


<commit_before>module.exports = {
    preset: "ts-jest",
    testEnvironment: "node",
    moduleFileExtensions: ["ts", "js"],
    globals: {
        'ts-jest': {
            tsConfig: {
                types: undefined,
            },
            diagnostics: {
                ignoreCodes: [151001],
            },
        },
    },
};<commit_msg>Generate code coverage for sites unit tests<commit_after>module.exports = {
    preset: "ts-jest",
    testEnvironment: "node",
    moduleFileExtensions: ["ts", "js"],
    globals: {
        'ts-jest': {
            tsConfig: {
                types: undefined,
            },
            diagnostics: {
                ignoreCodes: [151001],
            },
        },
    },

    // Code coverage
    collectCoverage: true,
    coverageDirectory: "../../coverage/",
    collectCoverageFrom: [
        "**/*.ts",
        "!**/node_modules/**",
        "!test-*.ts",
    ],
};<|endoftext|>


<commit_before>import React, {Component} from 'react';
import Highlight from 'react-syntax-highlight';
import Note from '../Note';

class OnYourOwn extends Component {
  render() {
    return(

14. On your own

Congrats you are done.  If you have gotten this far because you are super fast, try implementing a Map component!

Home Work

Follow the complete get starting guide by Facebook - Guide

Bring questions for the next session.

Be sure to vote on the App to build next time.

    );
  }
}

export default OnYourOwn;
<commit_msg>update<commit_after>import React, {Component} from 'react';
import Highlight from 'react-syntax-highlight';
import Note from '../Note';

class OnYourOwn extends Component {
  render() {
    return(

14. On your own

In the following workshop, we will do deeper dive into building our app.  We will tackle styling our components, fetching real data, showing loading states, etc.

Home Work

Learn about Redux, we will use it to manage all of our state and date within our app - http://redux.js.org/

Bring questions for the next session.

    );
  }
}

export default OnYourOwn;
<|endoftext|>


<commit_before>import { computed, observable, action } from 'mobx';
import { omitBy, reduce } from 'lodash';

class ModalStore {
	@observable modalProps = {};
	@observable state = {};

	_prefix = '__';

	getOmitPaths = (val, key) => {
		return key.startsWith(this._prefix);
	};

	@computed
	get name() {
		return this.state.name;
	}

	@computed
	get visible() {
		return !!this.name;
	}

	close() {
		this._clearLocation();
	}

	@action
	setState(state, useLocation) {
		if (useLocation) {
			this._setLocation(state);
		} else {
			this.state = state;
		}
	}

	@action
	setModalProps(props) {
		const ensureNumber = function ensureNumber(propName) {
			const val = props[propName];
			if (val && /^\d+$/.test(val)) {
				props[propName] = +val;
			}
		};
		const ensureBoolean = function ensureBoolean(propName) {
			if (!props.hasOwnProperty(propName)) {
				return;
			}
			const val = props[propName];
			if (val === 'false' || val === 'no') {
				props[propName] = false;
			} else if (val) {
				props[propName] = true;
			}
		};
		ensureNumber('width');
		ensureNumber('zIndex');
		ensureBoolean('closable');
		ensureBoolean('confirmLoading');
		ensureBoolean('mask');
		ensureBoolean('maskClosable');
		this.modalProps = props;
	}

	bindRouter(router) {
		if (this._router) {
			return;
		}
		router.history.listen(({ query }) => {
			const prefixLength = this._prefix.length;
			this.state = reduce(
				query,
				(state, val, key) => {
					if (key.startsWith(this._prefix)) {
						const stateKey = key.substr(prefixLength);
						state[stateKey] = val;
					}
					return state;
				},
				{}
			);
		});

		this._setLocation = (state) => {
			const modalQuery = reduce(
				state,
				(query, val, key) => {
					query[`${this._prefix}${key}`] = val;
					return query;
				},
				{}
			);
			const { location } = router;
			location.query = { ...location.query, ...modalQuery };
		};

		this._clearLocation = () => {
			const { location } = router;
			location.query = omitBy(location.query, (val, key) =>
				key.startsWith(this._prefix)
			);
		};
	}
}

export default new ModalStore();
<commit_msg>fix modalStore bug<commit_after>import { computed, observable, action } from 'mobx';
import { omitBy, reduce } from 'lodash';

class ModalStore {
	@observable modalProps = {};
	@observable state = {};

	_prefix = '__';

	getOmitPaths = (val, key) => {
		return key.startsWith(this._prefix);
	};

	@computed
	get name() {
		return this.state.name;
	}

	@computed
	get visible() {
		return !!this.name;
	}

	close() {
		this._clearLocation();
	}

	@action
	setState(state, useLocation) {
		if (useLocation) {
			this._setLocation(state);
		} else {
			this.state = state;
		}
	}

	@action
	setModalProps(props) {
		const ensureNumber = function ensureNumber(propName) {
			const val = props[propName];
			if (val && /^\d+$/.test(val)) {
				props[propName] = +val;
			}
		};
		const ensureBoolean = function ensureBoolean(propName) {
			if (!props.hasOwnProperty(propName)) {
				return;
			}
			const val = props[propName];
			if (val === 'false' || val === 'no') {
				props[propName] = false;
			} else if (val) {
				props[propName] = true;
			}
		};
		ensureNumber('width');
		ensureNumber('zIndex');
		ensureBoolean('closable');
		ensureBoolean('confirmLoading');
		ensureBoolean('mask');
		ensureBoolean('maskClosable');
		this.modalProps = props;
	}

	bindRouter(router) {
		if (this._router) {
			return;
		}

		const handleQueryChange = ({ query }) => {
			const prefixLength = this._prefix.length;
			this.state = reduce(
				query,
				(state, val, key) => {
					if (key.startsWith(this._prefix)) {
						const stateKey = key.substr(prefixLength);
						state[stateKey] = val;
					}
					return state;
				},
				{}
			);
		};

		router.history.listen(handleQueryChange);
		handleQueryChange(router.location);

		this._setLocation = (state) => {
			const modalQuery = reduce(
				state,
				(query, val, key) => {
					query[`${this._prefix}${key}`] = val;
					return query;
				},
				{}
			);
			const { location } = router;
			location.query = { ...location.query, ...modalQuery };
		};

		this._clearLocation = () => {
			const { location } = router;
			location.query = omitBy(location.query, (val, key) =>
				key.startsWith(this._prefix)
			);
		};
	}
}

export default new ModalStore();
<|endoftext|>


<commit_before>import colors from './colors';

const styleSheet = {
  emptyState: {
    alignItems: 'center',
    backgroundPosition: 'center bottom',
    backgroundSize: 'cover',
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'center',
    padding: '24px'
  },
  header: {
    fontSize: '16',
    fontWeight: 'bold',
    color: colors.secondaryText,
    margin: '0',
    textAlign: 'center'
  },
  body: {
    fontSize: '16',
    color: colors.secondaryText,
    marginBottom: '30px',
    marginTop: '16px',
    textAlign: 'center'
  }
};

export default styleSheet;
<commit_msg>:hankey: Styling bom Oepsies.<commit_after>import colors from './colors';

const styleSheet = {
  emptyState: {
    alignItems: 'center',
    backgroundPosition: 'center bottom',
    backgroundSize: 'cover',
    boxSizing: 'border-box',
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'center',
    padding: '24px'
  },
  header: {
    fontSize: '16',
    fontWeight: 'bold',
    color: colors.secondaryText,
    margin: '0',
    textAlign: 'center'
  },
  body: {
    fontSize: '16',
    color: colors.secondaryText,
    marginBottom: '30px',
    marginTop: '16px',
    textAlign: 'center'
  }
};

export default styleSheet;
<|endoftext|>


<commit_before>import { Animated, Easing } from 'react-native-universal'
import matchMedia from 'react-native-match-media'

import { Breakpoints } from './Grid'

export const Curves = {
  standard: Easing.bezier(0.4, 0, 0.2, 1),
  deceleration: Easing.bezier(0, 0, 0.2, 1),
  acceleration: Easing.bezier(0.4, 0, 1, 1),
  sharp: Easing.bezier(0.4, 0, 0.6, 1),
}

// MD spec only specifies a range of durations for desktop, so I created a function
// to interpolate the desktop value from the mobile value.
//
// 150 is the shortest desktop duration
// 0.28 is desktop ms per mobile ms, or
// (longest desktop - shortest desktop) / (longest mobile - shortest mobile)
// or (200 - 150) / (375 - 195)
// 195 is the shortest mobile duration
const interpolateDesktopValue = from => 150 + (0.28 * (from - 195))

// Creates a function that calculates the duration value for different screen
// sizes
// The resulting function accepts an "add" parameter that gets added to the value
// before interpolation
const getResponsiveDurationFn = value => () => {
  const desktopMediaQuery = Breakpoints.ml.split('@media')[1]
  const tabletMediaQuery = Breakpoints.md.split('@media')[1]
  // Desktop, not in MD spec so I interpolated it
  if (matchMedia(desktopMediaQuery).matches) return interpolateDesktopValue(value)
  // Tablet, according to MD spec 30% longer than mobile
  if (matchMedia(tabletMediaQuery).matches) return value * 1.3
  // Mobile
  return value
}

export const Durations = {
  standard: getResponsiveDurationFn(300),
  large: getResponsiveDurationFn(375),
  entering: getResponsiveDurationFn(225),
  leaving: getResponsiveDurationFn(195),
  custom: getResponsiveDurationFn,
}

const Animations = {
  standard: (av, toValue = 1, duration) => Animated.timing(av, {
    duration: duration ? Durations.custom(duration)() : Durations.standard(),
    easing: Curves.standard,
    toValue,
  }),
  large: (av, toValue = 1, duration) => Animated.timing(av, {
    duration: duration ? Durations.custom(duration)() : Durations.large(),
    easing: Curves.standard,
    toValue,
  }),
  entrance: (av, toValue = 1, duration) => Animated.timing(av, {
    duration: duration ? Durations.custom(duration)() : Durations.entering(),
    easing: Curves.deceleration,
    toValue,
  }),
  exit: (av, toValue = 1, duration) => Animated.timing(av, {
    duration: duration ? Durations.custom(duration)() : Durations.leaving(),
    easing: Curves.acceleration,
    toValue,
  }),
  tempExit: (av, toValue = 1, duration) => Animated.timing(av, {
    duration: duration ? Durations.custom(duration)() : Durations.leaving(),
    easing: Curves.sharp,
    toValue,
  }),
  staggered: (av, staggerAV, toValue = 1, staggerAmount = 50) =>
    Animated.stagger(staggerAmount, [
      Animated.timing(toValue ? av : staggerAV, {
        duration: Durations.custom(300 - staggerAmount)(),
        easing: Curves.standard,
        toValue,
      }),
      Animated.timing(toValue ? staggerAV : av, {
        duration: Durations.custom(300 - staggerAmount)(),
        easing: Curves.standard,
        delay: Durations.custom(staggerAmount)(),
        toValue,
      }),
    ]),
}

export default Animations
<commit_msg>Remove dependency on react-native-match-media<commit_after>import { Animated, Easing } from 'react-native-universal'

import { Breakpoints } from './Grid'

export const Curves = {
  standard: Easing.bezier(0.4, 0, 0.2, 1),
  deceleration: Easing.bezier(0, 0, 0.2, 1),
  acceleration: Easing.bezier(0.4, 0, 1, 1),
  sharp: Easing.bezier(0.4, 0, 0.6, 1),
}

// MD spec only specifies a range of durations for desktop, so I created a function
// to interpolate the desktop value from the mobile value.
//
// 150 is the shortest desktop duration
// 0.28 is desktop ms per mobile ms, or
// (longest desktop - shortest desktop) / (longest mobile - shortest mobile)
// or (200 - 150) / (375 - 195)
// 195 is the shortest mobile duration
const interpolateDesktopValue = from => 150 + (0.28 * (from - 195))

// Creates a function that calculates the duration value for different screen
// sizes
// The resulting function accepts an "add" parameter that gets added to the value
// before interpolation
const getResponsiveDurationFn = value => () => {
  const desktopMediaQuery = Breakpoints.ml.split('@media')[1]
  const tabletMediaQuery = Breakpoints.md.split('@media')[1]
  // Desktop, not in MD spec so I interpolated it
  if (global.matchMedia(desktopMediaQuery).matches) return interpolateDesktopValue(value)
  // Tablet, according to MD spec 30% longer than mobile
  if (global.matchMedia(tabletMediaQuery).matches) return value * 1.3
  // Mobile
  return value
}

export const Durations = {
  standard: getResponsiveDurationFn(300),
  large: getResponsiveDurationFn(375),
  entering: getResponsiveDurationFn(225),
  leaving: getResponsiveDurationFn(195),
  custom: getResponsiveDurationFn,
}

const Animations = {
  standard: (av, toValue = 1, duration) => Animated.timing(av, {
    duration: duration ? Durations.custom(duration)() : Durations.standard(),
    easing: Curves.standard,
    toValue,
  }),
  large: (av, toValue = 1, duration) => Animated.timing(av, {
    duration: duration ? Durations.custom(duration)() : Durations.large(),
    easing: Curves.standard,
    toValue,
  }),
  entrance: (av, toValue = 1, duration) => Animated.timing(av, {
    duration: duration ? Durations.custom(duration)() : Durations.entering(),
    easing: Curves.deceleration,
    toValue,
  }),
  exit: (av, toValue = 1, duration) => Animated.timing(av, {
    duration: duration ? Durations.custom(duration)() : Durations.leaving(),
    easing: Curves.acceleration,
    toValue,
  }),
  tempExit: (av, toValue = 1, duration) => Animated.timing(av, {
    duration: duration ? Durations.custom(duration)() : Durations.leaving(),
    easing: Curves.sharp,
    toValue,
  }),
  staggered: (av, staggerAV, toValue = 1, staggerAmount = 50) =>
    Animated.stagger(staggerAmount, [
      Animated.timing(toValue ? av : staggerAV, {
        duration: Durations.custom(300 - staggerAmount)(),
        easing: Curves.standard,
        toValue,
      }),
      Animated.timing(toValue ? staggerAV : av, {
        duration: Durations.custom(300 - staggerAmount)(),
        easing: Curves.standard,
        delay: Durations.custom(staggerAmount)(),
        toValue,
      }),
    ]),
}

export default Animations
<|endoftext|>


<commit_before>/**
 * Copyright (C) 2017 Kim Burgaard <kim@burgaard.us>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import suffixArray, { suffixArrayToString } from './suffix-array';
import stringToSequence from './string-sequence';

function expectStringOrder(a, b) {
  let [i, j] = [0, 0];
  const na = a.length - 1;
  const nb = b.length - 1;
  while (i < na && j < nb && a[i] === b[j]) {
    i++;
    j++;
  }

  let result;

  switch ((i === na ? 1 : 0) + (j === nb ? 2 : 0)) {
    case 0:
      result = a[i] - b[j];
      break;
    case 1:
      // a is a substring of b
      result = -1;
      break;
    case 2:
      // b is a substring of a
      result = 1;
      break;
    default:
      throw new Error(`a ${a} and b ${b} are equal`);
  }

  if (result > 0) {
    // eslint-disable-next-line no-console
    throw new Error(`Expected a to compare less than or equal to b:\n    a: ${a}\n    b: ${b}`);
  }
}

function expectSuffixArray(s, terminator, sa) {
  const n = s.length;
  expect(sa.length).toEqual(n + 1);

  const sequence = [
    ...typeof s === 'string' ? stringToSequence(s) : s,
    terminator,
  ];
  const counter = {};

  let prev = sa[0];
  counter[prev] = 1;
  expect(prev).toBeLessThanOrEqual(n);
  if (n > 1) {
    for (let i = 1; i <= n; i++) {
      const next = sa[i];
      expect(next).toBeLessThanOrEqual(n);

      expect(counter).not.toHaveProperty(`${next}`);
      counter[next] = 1;

      expectStringOrder(sequence.slice(prev), sequence.slice(next));

      prev = next;
    }
  }
}

describe('suffixArray', () => {
  it('returns the suffix array corresponding to the given string', () => {
    const s = 'monsoonnomnoms';

    const result = suffixArray(s);

    expectSuffixArray(s, -1, result);
  });

  it('accepts array input', () => {
    const s = [97, 120, -1, 97, 120, -2, 97, -3];

    const result = suffixArray(s);

    expectSuffixArray(s, -4, result);
  });

  it('handles a string with repetitive sections', () => {
    const s = ' 1 2 apple 3 4~4 5 apple 6 7!8 9 apple 1 2@apple 3 4#5 6 apple$apple%'.split('').map(c => c.charCodeAt(0));

    const result = suffixArray(s);

    expectSuffixArray(s, -1, result);
  });

  it('handles recursion', () => {
    const sequence = 'abc3abc2abc1'.split('').map(c => c.charCodeAt(0));

    const result = suffixArray(sequence);

    expectSuffixArray(sequence, -1, result);
  });

  it('handles no recursion', () => {
    const sequence = 'abcdefghijklmnopqrstuvwxyz'.split('').map(c => c.charCodeAt(0));

    const result = suffixArray(sequence);

    expectSuffixArray(sequence, -1, result);
  });

  it('handles the empty string', () => {
    const result = suffixArray('');

    expectSuffixArray('', -1, result);
  });

  it('handles a one character string', () => {
    const result = suffixArray('1');

    expectSuffixArray('1', -1, result);
  });

  it('handles a two character string where the first character is less than the second', () => {
    const result = suffixArray('12');

    expectSuffixArray('12', -1, result);
  });

  it('handles a two character string where the second character is less than the first', () => {
    const result = suffixArray('21');

    expectSuffixArray('21', -1, result);
  });

  it('handles array input', () => {
    const result = suffixArray('mississippi'.split('').map(c => c.charCodeAt(0)));

    expectSuffixArray('mississippi', -1, result);
  });

  it('handles a negative terminator input', () => {
    const result = suffixArray('mississippi', -2);

    expectSuffixArray('mississippi', -2, result);
  });

  it('handles a null terminator', () => {
    const result = suffixArray('mississippi', null);

    expectSuffixArray('mississippi', -1, result);
  });

  it('handles an undefined terminator', () => {
    const result = suffixArray('mississippi', undefined);

    expectSuffixArray('mississippi', -1, result);
  });

  it('handles invalid terminator terminator input', () => {
    expect(() => suffixArray('mississippi', 0)).toThrow();
    expect(() => suffixArray('mississippi', 1)).toThrow();
    expect(() => suffixArray('mississippi', 'a')).toThrow();
    expect(() => suffixArray('mississippi', [])).toThrow();
    expect(() => suffixArray('mississippi', {})).toThrow();
    expect(() => suffixArray('mississippi', () => {})).toThrow();
  });
});

describe('suffixArrayToString', () => {
  it('returns a string representation of the given suffix array', () => {
    const s = 'mississippi';
    const sa = suffixArray(s);

    const result = suffixArrayToString(s, sa);

    expect(result).toEqual('[\n  11, // \n  10, // i\n  7, // ippi\n  4, // issippi\n  1, // ississippi\n  0, // mississippi\n  9, // pi\n  8, // ppi\n  6, // sippi\n  3, // sissippi\n  5, // ssippi\n  2, // ssissippi\n]');
  });

  it('handles array input', () => {
    const s = 'mississippi';
    const sa = suffixArray(s);

    const result = suffixArrayToString(s.split('').map(c => c.charCodeAt(0)), sa);

    expect(result).toEqual('[\n  11, // \n  10, // i\n  7, // ippi\n  4, // issippi\n  1, // ississippi\n  0, // mississippi\n  9, // pi\n  8, // ppi\n  6, // sippi\n  3, // sissippi\n  5, // ssippi\n  2, // ssissippi\n]');
  });
});
<commit_msg>Fixed unit test<commit_after>/**
 * Copyright (C) 2017 Kim Burgaard <kim@burgaard.us>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import suffixArray, { suffixArrayToString } from './suffix-array';
import stringToSequence from './string-sequence';

function expectStringOrder(a, b) {
  let [i, j] = [0, 0];
  const na = a.length - 1;
  const nb = b.length - 1;
  while (i < na && j < nb && a[i] === b[j]) {
    i++;
    j++;
  }

  let result;

  switch ((i === na ? 1 : 0) + (j === nb ? 2 : 0)) {
    case 0: {
      const av = a[i];
      const bv = b[j];
      result = (av < 0 && bv < 0) ? true : a[i] <= b[j];
      break;
    }
    case 1:
      // a is a substring of b
      result = true;
      break;
    case 2:
      // b is a substring of a
      result = a[i] < 0 && b[j] < 0;
      break;
    default:
      throw new Error(`a ${a} and b ${b} are equal`);
  }

  if (!result) {
    // eslint-disable-next-line no-console
    throw new Error(`Expected a to compare less than or equal to b:\n    a: ${a}\n    b: ${b}`);
  }
}

function expectSuffixArray(s, terminator, sa) {
  const n = s.length;
  expect(sa.length).toEqual(n + 1);

  const sequence = [
    ...typeof s === 'string' ? stringToSequence(s) : s,
    terminator,
  ];
  const counter = {};

  let prev = sa[0];
  counter[prev] = 1;
  expect(prev).toBeLessThanOrEqual(n);
  if (n > 1) {
    for (let i = 1; i <= n; i++) {
      const next = sa[i];
      expect(next).toBeLessThanOrEqual(n);

      expect(counter).not.toHaveProperty(`${next}`);
      counter[next] = 1;

      expectStringOrder(sequence.slice(prev), sequence.slice(next));

      prev = next;
    }
  }
}

describe('suffixArray', () => {
  it('returns the suffix array corresponding to the given string', () => {
    const s = 'monsoonnomnoms';

    const result = suffixArray(s);

    expectSuffixArray(s, -1, result);
  });

  it('accepts array input', () => {
    const s = [97, 120, -1, 97, 120, -2, 97, -3];

    const result = suffixArray(s);

    expectSuffixArray(s, -4, result);
  });

  it('handles a string with repetitive sections', () => {
    const s = ' 1 2 apple 3 4~4 5 apple 6 7!8 9 apple 1 2@apple 3 4#5 6 apple$apple%'.split('').map(c => c.charCodeAt(0));

    const result = suffixArray(s);

    expectSuffixArray(s, -1, result);
  });

  it('handles recursion', () => {
    const sequence = 'abc3abc2abc1'.split('').map(c => c.charCodeAt(0));

    const result = suffixArray(sequence);

    expectSuffixArray(sequence, -1, result);
  });

  it('handles no recursion', () => {
    const sequence = 'abcdefghijklmnopqrstuvwxyz'.split('').map(c => c.charCodeAt(0));

    const result = suffixArray(sequence);

    expectSuffixArray(sequence, -1, result);
  });

  it('handles the empty string', () => {
    const result = suffixArray('');

    expectSuffixArray('', -1, result);
  });

  it('handles a one character string', () => {
    const result = suffixArray('1');

    expectSuffixArray('1', -1, result);
  });

  it('handles a two character string where the first character is less than the second', () => {
    const result = suffixArray('12');

    expectSuffixArray('12', -1, result);
  });

  it('handles a two character string where the second character is less than the first', () => {
    const result = suffixArray('21');

    expectSuffixArray('21', -1, result);
  });

  it('handles array input', () => {
    const result = suffixArray('mississippi'.split('').map(c => c.charCodeAt(0)));

    expectSuffixArray('mississippi', -1, result);
  });

  it('handles a negative terminator input', () => {
    const result = suffixArray('mississippi', -2);

    expectSuffixArray('mississippi', -2, result);
  });

  it('handles a null terminator', () => {
    const result = suffixArray('mississippi', null);

    expectSuffixArray('mississippi', -1, result);
  });

  it('handles an undefined terminator', () => {
    const result = suffixArray('mississippi', undefined);

    expectSuffixArray('mississippi', -1, result);
  });

  it('handles invalid terminator terminator input', () => {
    expect(() => suffixArray('mississippi', 0)).toThrow();
    expect(() => suffixArray('mississippi', 1)).toThrow();
    expect(() => suffixArray('mississippi', 'a')).toThrow();
    expect(() => suffixArray('mississippi', [])).toThrow();
    expect(() => suffixArray('mississippi', {})).toThrow();
    expect(() => suffixArray('mississippi', () => {})).toThrow();
  });
});

describe('suffixArrayToString', () => {
  it('returns a string representation of the given suffix array', () => {
    const s = 'mississippi';
    const sa = suffixArray(s);

    const result = suffixArrayToString(s, sa);

    expect(result).toEqual('[\n  11, // \n  10, // i\n  7, // ippi\n  4, // issippi\n  1, // ississippi\n  0, // mississippi\n  9, // pi\n  8, // ppi\n  6, // sippi\n  3, // sissippi\n  5, // ssippi\n  2, // ssissippi\n]');
  });

  it('handles array input', () => {
    const s = 'mississippi';
    const sa = suffixArray(s);

    const result = suffixArrayToString(s.split('').map(c => c.charCodeAt(0)), sa);

    expect(result).toEqual('[\n  11, // \n  10, // i\n  7, // ippi\n  4, // issippi\n  1, // ississippi\n  0, // mississippi\n  9, // pi\n  8, // ppi\n  6, // sippi\n  3, // sissippi\n  5, // ssippi\n  2, // ssissippi\n]');
  });
});
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import { SyncQueue } from './SyncQueue';
import { SyncDatabase } from './SyncDatabase';
import { generateSyncJson } from './outgoingSyncUtils';
import { integrateRecord } from './incomingSyncUtils';
import { SETTINGS_KEYS } from '../settings';
import {
  incrementSyncProgress,
  setSyncProgress,
  setSyncProgressMessage,
  setSyncTotal,
  setSyncError,
  setSyncIsSyncing,
  setSyncCompletionTime,
} from './actions';

const {
  SYNC_IS_INITIALISED,
  SYNC_PRIOR_FAILED,
  SYNC_SITE_NAME,
  SYNC_SERVER_ID,
  SYNC_SITE_ID,
  SYNC_URL,
} = SETTINGS_KEYS;

const INITIAL_BATCH_SIZE = 20; // Number of records to sync at one time

/**
 * Provides core synchronization functionality, initilising the database with an
 * initial full sync, pushing, and pulling (any regular scheduling of synchronization
 * must be coordinated externally)
 * @param  {Realm}             database       The local database
 * @param  {SyncAuthenticator} authenticator  Provides authentication with the sync server
 * @param  {Settings}          settings       Access to locally stored settings
 */
export class Synchroniser {
  constructor(database, authenticator, settings, dispatch) {
    this.database = new SyncDatabase(database);
    this.authenticator = authenticator;
    this.settings = settings;
    this.syncQueue = new SyncQueue(this.database);
    this.dispatch = dispatch;
    if (this.isInitialised()) this.syncQueue.enable();
  }

  /**
   * Redux progress setting functions
   */
  setTotal = totalCount => this.dispatch(setSyncTotal(totalCount));
  incrementProgress = increment => this.dispatch(incrementSyncProgress(increment));
  setProgress = currentCount => this.dispatch(setSyncProgress(currentCount));
  setProgressMessage = message => this.dispatch(setSyncProgressMessage(message));
  setError = errorMessage => this.dispatch(setSyncError(errorMessage));
  setIsSyncing = isSyncing => this.dispatch(setSyncIsSyncing(isSyncing));
  setCompletionTime = time => this.dispatch(setSyncCompletionTime(time));

  /**
   * Wipe the current database, check that the given url can be synced against
   * using the given name and password, and if so populate the database by pulling
   * from the remote server.
   * @param  {string} serverURL        The URL to be synced against
   * @param  {string} syncSiteName     The username to use in sync auth
   * @param  {string} syncSitePassword The password to use in sync auth
   * @return {Promise}                 Resolve if successfully authenticated and
   *                                   initialised, otherwise throw error
   */
  initialise = async (serverURL, syncSiteName, syncSitePassword) => {
    this.setIsSyncing(true);
    this.setProgressMessage('Initialising...');
    this.syncQueue.disable(); // Stop sync queue listening to database changes
    // Check if the serverURL passed in is the same as one we have already been using during
    // initialisation, in which case we are continuing a failed partial initialisation. If the
    // serverURL is different, it is either completely fresh, or the URL has been changed so we
    // should start afresh. Do the same for syncSiteName, so that it isn't possible to start syncing
    // data from a site different to what initialisation was previously started with.
    const oldSyncUrl = this.settings.get(SYNC_URL);
    const oldSyncSiteName = this.settings.get(SYNC_SITE_NAME);
    const isFresh =
      !oldSyncUrl || serverURL !== oldSyncUrl || !syncSiteName || syncSiteName !== oldSyncSiteName;

    if (isFresh) {
      this.database.write(() => {
        this.database.deleteAll();
      });
    }
    try {
      await this.authenticator.authenticate(serverURL, syncSiteName, syncSitePassword);
      const thisSiteId = this.settings.get(SYNC_SITE_ID);
      const serverId = this.settings.get(SYNC_SERVER_ID);
      if (isFresh) {
        // If a fresh initialisation, tell the server to prepare required sync records
        await fetch(
          `${serverURL}/sync/v3/initial_dump/?from_site=${thisSiteId}&to_site=${serverId}`,
          {
            headers: {
              Authorization: this.authenticator.getAuthHeader(),
            },
          },
        );
        // If the initial_dump has been successful, serverURL is valid, and should now have all sync
        // records queued and ready to send. Safe to store as this.serverURL
        this.serverURL = serverURL;
      }
      await this.pull();
    } catch (error) {
      // Did not authenticate, sync error, or no internet, pass error up
      this.setError(error.message);
      this.setIsSyncing(false);
      throw error;
    }
    this.settings.set(SYNC_IS_INITIALISED, 'true');
    this.syncQueue.enable(); // Begin the sync queue listening to database changes
    this.setIsSyncing(false);
  };

  /**
   * Return whether the synchroniser has been initialised.
   * @return {boolean} True if initial sync has been completed successfully
   */
  isInitialised = () => {
    const syncIsInitialised = this.settings.get(SYNC_IS_INITIALISED);
    return syncIsInitialised && syncIsInitialised === 'true';
  };

  /**
   * Return whether or not the last sync of the app failed
   * @return {boolean} 'true' if the last call of synchronise failed
   */
  lastSyncFailed = () => {
    const lastSyncFailed = this.settings.get(SYNC_PRIOR_FAILED);
    return lastSyncFailed && lastSyncFailed === 'true';
  };

  /**
   * Carry out a synchronization, first pushing any local changes, then pulling
   * down remote changes and integrating them into the local database.
   * @return {[type]} [description]
   */
  synchronise = async () => {
    try {
      if (!this.isInitialised()) throw new Error('Not yet initialised');
      this.setIsSyncing(true);

      // Keeps track between app close/open whether last sync was successful
      this.settings.set(SYNC_PRIOR_FAILED, 'true');

      // Using async/await here means that any errors thrown by push or pull will be caught by the
      // outer try/catch
      await this.push();
      await this.pull();

      // Store persistent sync details in settings
      this.settings.set(SYNC_PRIOR_FAILED, 'false');

      // Store sync completion progress in redux
      this.setIsSyncing(false);
      this.setCompletionTime(new Date().getTime());
    } catch (error) {
      this.setError(error.message);
      this.setIsSyncing(false);
    }
  };

  /**
   * Push batches of changes to the local database up to the remote server, until
   * all local changes have been synced.
   * @return {Promise} Resolves if successful, or passes up any error thrown
   */
  push = async () => {
    this.setProgressMessage('Pushing changes to the server');
    this.setProgress(0);
    this.setTotal(this.syncQueue.length);
    while (this.syncQueue.length > 0) {
      const recordsToSync = this.syncQueue.next(INITIAL_BATCH_SIZE);
      const translatedRecords = recordsToSync
        .map(this.translateRecord) // Apply map function to get translated records
        .filter((record) => !!record); // If error thrown, may be null so filter falsy values out
      await this.pushRecords(translatedRecords);
      // Records that threw errors in translateRecord() are still removed
      this.syncQueue.use(recordsToSync);
      this.incrementProgress(recordsToSync.length);
    }
  };

  /**
   * Handles translating records for push() and catching errors
   * @param {object} record The outgoing record to be translated
   * @return {object} The translated record output
   */
  translateRecord = record => {
    try {
      // Try translate the record
      return generateSyncJson(this.database, this.settings, record);
    } catch (error) {
      // If error not safe to continue sync throw it again to pass it up to next handler
      // See outgoingSyncUtils.js
      if (!error.canDeleteSyncOut) throw error;
      return null;
    }
  };

  /**
   * Pushes a collection of records to the remote sync server
   * @param  {array}   records The records to push
   * @return {Promise}         Resolves if successful, or passes up any error thrown
   */
  pushRecords = async records => {
    const serverURL = this.settings.get(SYNC_URL);
    const thisSiteId = this.settings.get(SYNC_SITE_ID);
    const serverId = this.settings.get(SYNC_SERVER_ID);
    const response = await fetch(
      `${serverURL}/sync/v3/queued_records/?from_site=${thisSiteId}&to_site=${serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authenticator.getAuthHeader(),
        },
        body: JSON.stringify(records),
      },
    );
    let responseJson;
    try {
      responseJson = await response.json();
    } catch (error) {
      throw new Error('Unexpected response from sync server');
    }
    if (responseJson.error.length > 0) {
      throw new Error('Server rejected pushed records');
    }
  };

  /**
   * Pulls any changes to data on the sync server down to the local database
   * @return {Promise} Resolves if successful, or passes up any error thrown
   */
  pull = async () => {
    this.setProgressMessage('Pulling changes from the server');
    this.setProgress(0);
    this.setTotal(0);
    const serverURL = this.settings.get(SYNC_URL);
    const thisSiteId = this.settings.get(SYNC_SITE_ID);
    const serverId = this.settings.get(SYNC_SERVER_ID);
    const authHeader = this.authenticator.getAuthHeader();

    let waitingRecordCount = await this.getWaitingRecordCount(
      serverURL,
      thisSiteId,
      serverId,
      authHeader,
    );
    let total = waitingRecordCount;
    // Pull BATCH_SIZE amount of records at a time from server
    while (waitingRecordCount > 0) {
      // Allow total to increase in case more records are added to the server sync queue during sync
      if (waitingRecordCount > total) {
        total = waitingRecordCount;
        this.setTotal(total);
      }

      // Get a batch of records and integrate them
      const incomingRecords = await this.getIncomingRecords(
        serverURL,
        thisSiteId,
        serverId,
        authHeader,
        INITIAL_BATCH_SIZE
      );
      this.integrateRecords(incomingRecords);
      await this.acknowledgeRecords(serverURL, thisSiteId, serverId, authHeader, incomingRecords);
      this.incrementProgress(incomingRecords.length);

      // Get updated waiting record count. End when waitingRecordCount reaches 0
      waitingRecordCount = await this.getWaitingRecordCount(
        serverURL,
        thisSiteId,
        serverId,
        authHeader
      );
    }
  }

  /**
   * Returns the number of records left to pull
   * @param  {string} serverURL  URL of the sync server
   * @param  {string} thisSiteId Sync ID of this sync site
   * @param  {string} serverId   Sync ID of the server
   * @return {Promise}           Resolves with the record count, or passes up any error thrown
   */
  getWaitingRecordCount = async (serverURL, thisSiteId, serverId, authHeader) => {
    const response = await fetch(
      `${serverURL}/sync/v3/queued_records/count?from_site=${thisSiteId}&to_site=${serverId}`,
      {
        headers: {
          Authorization: authHeader,
        },
      },
    );
    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while attempting to sync.');
    }
    const responseJson = await response.json();
    if (responseJson.error && responseJson.error.length > 0) {
      throw new Error(responseJson.error);
    }
    if (typeof responseJson.NumRecords !== 'number') {
      throw new Error('Unexpected response from server');
    }
    return responseJson.NumRecords;
  };

  /**
   * Returns the next batch of incoming sync records
   * @param  {string}  serverURL  The URL of the sync server
   * @param  {string}  thisSiteId The sync ID of this sync site
   * @param  {string}  serverId   The sync ID of the server
   * @param  {integer} numRecords The number of records to fetch
   * @return {Promise}            Resolves with the records, or passes up any error thrown
   */
  getIncomingRecords = async (serverURL, thisSiteId, serverId, authHeader, numRecords) => {
    const response = await fetch(
      `${serverURL}/sync/v3/queued_records` +
        `?from_site=${thisSiteId}&to_site=${serverId}&limit=${numRecords}`,
      {
        headers: {
          Authorization: authHeader,
        },
      },
    );
    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while pulling sync records.');
    }
    const responseJson = await response.json();
    if (responseJson.error && responseJson.error.length > 0) {
      throw new Error(responseJson.error);
    }
    return responseJson;
  };

  /**
   * Parse the batch of incoming records, and integrate them into the local database
   * @param  {Realm}  database The local database
   * @param  {object} records  The json object the server sent to represent records
   * @return {none}
   */
  integrateRecords(syncJson) {
    this.database.write(() => {
      syncJson.forEach(syncRecord => {
        integrateRecord(this.database, this.settings, syncRecord);
      });
    });
  }

  /**
   * Sends the sync server a message to indicate the sync records have been consumed
   * @param  {string}  serverURL  Base URL of the sync server
   * @param  {string}  thisSiteId Sync id of this mobile site
   * @param  {string}  serverId   Sync id of the primary server
   * @param  {string}  authHeader Base64 encoded Basic auth header
   * @param  {array}   records    Sync records that have been integrated
   * @return {none}
   */
  acknowledgeRecords = async (serverURL, thisSiteId, serverId, authHeader, records) => {
    const syncIds = records.map(record => record.SyncID);
    const requestBody = {
      SyncRecordIDs: syncIds,
    };
    await fetch(
      `${serverURL}/sync/v3/acknowledged_records?from_site=${thisSiteId}&to_site=${serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: authHeader,
        },
        body: JSON.stringify(requestBody),
      },
    );
  };
}
<commit_msg>refactor of sync params, while approach works<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import { SyncQueue } from './SyncQueue';
import { SyncDatabase } from './SyncDatabase';
import { generateSyncJson } from './outgoingSyncUtils';
import { integrateRecord } from './incomingSyncUtils';
import { SETTINGS_KEYS } from '../settings';
import {
  incrementSyncProgress,
  setSyncProgress,
  setSyncProgressMessage,
  setSyncTotal,
  setSyncError,
  setSyncIsSyncing,
  setSyncCompletionTime,
} from './actions';

const {
  SYNC_IS_INITIALISED,
  SYNC_PRIOR_FAILED,
  SYNC_SITE_NAME,
  SYNC_SERVER_ID,
  SYNC_SITE_ID,
  SYNC_URL,
} = SETTINGS_KEYS;

const INITIAL_BATCH_SIZE = 20; // Number of records to sync at one time

/**
 * Provides core synchronization functionality, initilising the database with an
 * initial full sync, pushing, and pulling (any regular scheduling of synchronization
 * must be coordinated externally)
 * @param  {Realm}             database       The local database
 * @param  {SyncAuthenticator} authenticator  Provides authentication with the sync server
 * @param  {Settings}          settings       Access to locally stored settings
 */
export class Synchroniser {
  constructor(database, authenticator, settings, dispatch) {
    this.database = new SyncDatabase(database);
    this.authenticator = authenticator;
    this.settings = settings;
    this.syncQueue = new SyncQueue(this.database);
    this.dispatch = dispatch;
    this.refreshSyncParams();
    if (this.isInitialised()) this.syncQueue.enable();
  }

  /**
   * Redux progress setting functions
   */
  setTotal = totalCount => this.dispatch(setSyncTotal(totalCount));
  incrementProgress = increment => this.dispatch(incrementSyncProgress(increment));
  setProgress = currentCount => this.dispatch(setSyncProgress(currentCount));
  setProgressMessage = message => this.dispatch(setSyncProgressMessage(message));
  setError = errorMessage => this.dispatch(setSyncError(errorMessage));
  setIsSyncing = isSyncing => this.dispatch(setSyncIsSyncing(isSyncing));
  setCompletionTime = time => this.dispatch(setSyncCompletionTime(time));

  refreshSyncParams = () => {
    this.serverURL = this.settings.get(SYNC_URL);
    this.thisSiteId = this.settings.get(SYNC_SITE_ID);
    this.serverId = this.settings.get(SYNC_SERVER_ID);
    this.thisSiteName = this.settings.get(SYNC_SITE_NAME);
    this.authHeader = this.authenticator.getAuthHeader();
    this.batchSize = INITIAL_BATCH_SIZE;
  };

  /**
   * Wipe the current database, check that the given url can be synced against
   * using the given name and password, and if so populate the database by pulling
   * from the remote server.
   * @param  {string} serverURL        The URL to be synced against
   * @param  {string} syncSiteName     The username to use in sync auth
   * @param  {string} syncSitePassword The password to use in sync auth
   * @return {Promise}                 Resolve if successfully authenticated and
   *                                   initialised, otherwise throw error
   */
  initialise = async (serverURL, syncSiteName, syncSitePassword) => {
    this.setIsSyncing(true);
    this.setProgressMessage('Initialising...');
    this.syncQueue.disable(); // Stop sync queue listening to database changes
    // Check if the serverURL passed in is the same as one we have already been using during
    // initialisation, in which case we are continuing a failed partial initialisation. If the
    // serverURL is different, it is either completely fresh, or the URL has been changed so we
    // should start afresh. Do the same for syncSiteName, so that it isn't possible to start syncing
    // data from a site different to what initialisation was previously started with.
    const oldSyncURL = this.serverURL;
    const oldSyncSiteName = this.thisSiteName;
    const isFresh =
      !oldSyncURL || serverURL !== oldSyncURL || !syncSiteName || syncSiteName !== oldSyncSiteName;

    if (isFresh) {
      this.database.write(() => {
        this.database.deleteAll();
      });
    }
    try {
      await this.authenticator.authenticate(serverURL, syncSiteName, syncSitePassword);
      this.refreshSyncParams(); // authenticate sets all the sync settings in database, so refresh

      if (isFresh) {
        // If a fresh initialisation, tell the server to prepare required sync records
        await fetch(
          `${this.serverURL}/sync/v3/initial_dump/` +
            `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
          {
            headers: {
              Authorization: this.authHeader,
            },
          },
        );
        // If the initial_dump has been successful, serverURL is valid, and should now have all sync
        // records queued and ready to send. Safe to store as this.serverURL
        this.serverURL = serverURL;
      }
      await this.pull();
    } catch (error) {
      // Did not authenticate, sync error, or no internet, pass error up
      this.setError(error.message);
      this.setIsSyncing(false);
      throw error;
    }

    this.settings.set(SYNC_IS_INITIALISED, 'true');
    this.syncQueue.enable(); // Begin the sync queue listening to database changes
    this.setIsSyncing(false);
  };

  /**
   * Return whether the synchroniser has been initialised.
   * @return {boolean} True if initial sync has been completed successfully
   */
  isInitialised = () => {
    const syncIsInitialised = this.settings.get(SYNC_IS_INITIALISED);
    return syncIsInitialised && syncIsInitialised === 'true';
  };

  /**
   * Return whether or not the last sync of the app failed
   * @return {boolean} 'true' if the last call of synchronise failed
   */
  lastSyncFailed = () => {
    const lastSyncFailed = this.settings.get(SYNC_PRIOR_FAILED);
    return lastSyncFailed && lastSyncFailed === 'true';
  };

  /**
   * Carry out a synchronization, first pushing any local changes, then pulling
   * down remote changes and integrating them into the local database.
   * @return {[type]} [description]
   */
  synchronise = async () => {
    try {
      if (!this.isInitialised()) throw new Error('Not yet initialised');
      this.setIsSyncing(true);
      this.refreshSyncParams();

      // Keeps track between app close/open whether last sync was successful
      this.settings.set(SYNC_PRIOR_FAILED, 'true');

      // Using async/await here means that any errors thrown by push or pull will be caught by the
      // outer try/catch
      await this.push();
      await this.pull();

      // Store persistent sync details in settings
      this.settings.set(SYNC_PRIOR_FAILED, 'false');

      // Store sync completion progress in redux
      this.setIsSyncing(false);
      this.setCompletionTime(new Date().getTime());
    } catch (error) {
      this.setError(error.message);
      this.setIsSyncing(false);
    }
  };

  /**
   * Push batches of changes to the local database up to the remote server, until
   * all local changes have been synced.
   * @return {Promise} Resolves if successful, or passes up any error thrown
   */
  push = async () => {
    this.setProgressMessage('Pushing changes to the server');
    this.setProgress(0);
    this.setTotal(this.syncQueue.length);
    while (this.syncQueue.length > 0) {
      const recordsToSync = this.syncQueue.next(this.batchSize);
      const translatedRecords = recordsToSync
        .map(this.translateRecord) // Apply map function to get translated records
        .filter(record => !!record); // If error thrown, may be null so filter falsy values out
      await this.pushRecords(translatedRecords);
      // Records that threw errors in translateRecord() are still removed
      this.syncQueue.use(recordsToSync);
      this.incrementProgress(recordsToSync.length);
    }
  };

  /**
   * Handles translating records for push() and catching errors
   * @param {object} record The outgoing record to be translated
   * @return {object} The translated record output
   */
  translateRecord = record => {
    try {
      // Try translate the record
      return generateSyncJson(this.database, this.settings, record);
    } catch (error) {
      // If error not safe to continue sync throw it again to pass it up to next handler
      // See outgoingSyncUtils.js
      if (!error.canDeleteSyncOut) throw error;
      return null;
    }
  };

  /**
   * Pushes a collection of records to the remote sync server
   * @param  {array}   records The records to push
   * @return {Promise}         Resolves if successful, or passes up any error thrown
   */
  pushRecords = async records => {
    const response = await fetch(
      `${this.serverURL}/sync/v3/queued_records/` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authHeader,
        },
        body: JSON.stringify(records),
      },
    );
    let responseJson;
    try {
      responseJson = await response.json();
    } catch (error) {
      throw new Error('Unexpected response from sync server');
    }
    if (responseJson.error.length > 0) {
      throw new Error('Server rejected pushed records');
    }
  };

  /**
   * Pulls any changes to data on the sync server down to the local database
   * @return {Promise} Resolves if successful, or passes up any error thrown
   */
  pull = async () => {
    let waitingRecordCount = await this.getWaitingRecordCount();
    let total = waitingRecordCount;
    let progress = 0;

    this.setProgressMessage('Pulling changes from the server');
    this.setProgress(progress);
    this.setTotal(total);

    this.setTotal(waitingRecordCount);
    // Pull BATCH_SIZE amount of records at a time from server
    while (progress < total) {
      // Get a batch of records and integrate them
      const incomingRecords = await this.getIncomingRecords();
      this.integrateRecords(incomingRecords);
      await this.acknowledgeRecords(incomingRecords);
      this.incrementProgress(incomingRecords.length);
      progress += incomingRecords.length;

      // Check no new records added to incoming queue on last iteration.
      if (progress >= total) {
        waitingRecordCount = await this.getWaitingRecordCount();
        if (waitingRecordCount > total) {
          total = waitingRecordCount;
          this.setTotal(total);
        }
      }
    }
  };

  /**
   * Returns the number of records left to pull
   * @return {Promise}           Resolves with the record count, or passes up any error thrown
   */
  getWaitingRecordCount = async () => {
    const response = await fetch(
      `${this.serverURL}/sync/v3/queued_records/count` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        headers: {
          Authorization: this.authHeader,
        },
      },
    );
    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while attempting to sync.');
    }
    const responseJson = await response.json();
    if (responseJson.error && responseJson.error.length > 0) {
      throw new Error(responseJson.error);
    }
    if (typeof responseJson.NumRecords !== 'number') {
      throw new Error('Unexpected response from server');
    }
    return responseJson.NumRecords;
  };

  /**
   * Returns the next batch of incoming sync records
   * @return {Promise}            Resolves with the records, or passes up any error thrown
   */
  getIncomingRecords = async () => {
    const response = await fetch(
      `${this.serverURL}/sync/v3/queued_records` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}&limit=${this.batchSize}`,
      {
        headers: {
          Authorization: this.authHeader,
        },
      },
    );
    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while pulling sync records.');
    }
    const responseJson = await response.json();
    if (responseJson.error && responseJson.error.length > 0) {
      throw new Error(responseJson.error);
    }
    return responseJson;
  };

  /**
   * Parse the batch of incoming records, and integrate them into the local database
   * @param  {Realm}  database The local database
   * @param  {object} records  The json object the server sent to represent records
   * @return {none}
   */
  integrateRecords(syncJson) {
    this.database.write(() => {
      syncJson.forEach(syncRecord => {
        integrateRecord(this.database, this.settings, syncRecord);
      });
    });
  }

  /**
   * Sends the sync server a message to indicate the sync records have been consumed
   * @param  {string}  serverURL  Base URL of the sync server
   * @param  {string}  thisSiteId Sync id of this mobile site
   * @param  {string}  serverId   Sync id of the primary server
   * @param  {string}  authHeader Base64 encoded Basic auth header
   * @param  {array}   records    Sync records that have been integrated
   * @return {none}
   */
  acknowledgeRecords = async records => {
    const syncIds = records.map(record => record.SyncID);
    const requestBody = {
      SyncRecordIDs: syncIds,
    };
    await fetch(
      `${this.serverURL}/sync/v3/acknowledged_records` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authHeader,
        },
        body: JSON.stringify(requestBody),
      },
    );
  };
}
<|endoftext|>


<commit_before>/*
==================================================================================================
  Core - JavaScript Foundation
  Copyright 2010-2012 Sebastian Werner
--------------------------------------------------------------------------------------------------
  Based on the work of:
  Hogan.JS by Twitter, Inc.
  https://github.com/twitter/hogan.js
  Licensed under the Apache License, Version 2.0
  http://www.apache.org/licenses/LICENSE-2.0
==================================================================================================
*/

(function ()
{

	var rSlash = /\\/g;
	var rQuot = /\"/g;
	var rNewline = /\n/g;
	var rCr = /\r/g;

	var accessTags = {
		"#" : 1,
		"^" : 1,
		"{" : 1,
		"&" : 1,
		"$" : 1
	};

	function esc(s) {
		return s.replace(rSlash, '\\\\').replace(rQuot, '\\\"').replace(rNewline, '\\n').replace(rCr, '\\r');
	}

	function chooseAccessMethod(str) {
		return
	}

	function walk(node)
	{
		var code = '';
		var tag, name;

		for (var i = 0, l = node.length; i < l; i++)
		{
			var current = node[i];
			var tag = current.tag;

			if (tag == null)
			{
				code += 'buf+="' + esc(current) + '";';
			}
			else
			{
				var name = current.name;

				if (accessTags[tag]) {
					var accessMethod = ~name.indexOf('.') ? 'getDotted' : 'get';
				}

				if (tag == '#') {
					code += section(current.nodes, name, accessMethod);
				} else if (tag == '^') {
					code += invertedSection(current.nodes, name, accessMethod);
				} else if (tag == '{' || tag == '&') {
					code += data(name, accessMethod);
				} else if (tag == '$') {
					code += variable(name, accessMethod);
				} else if (tag == '<' || tag == '>') {
					code += partial(current);
				} else if (tag == '\n') {
					code += 'buf+="\\n";';
				}
			}
		}

		return code;
	}

	function section(nodes, id, accessMethod, start, end) {
		return 'if(this.section(this.' + accessMethod + '("' + esc(id) + '",context,partials,true),context,partials,false)){this.renderSection(context,partials,function(context,partials){' + walk(nodes) + '});context.pop();}';
	}

	function invertedSection(nodes, id, accessMethod) {
		return 'if(!this.section(this.' + accessMethod + '("' + esc(id) + '",context,partials,true),context,partials,true)){' + walk(nodes) + '};';
	}

	function partial(tok) {
		return 'buf+=this.renderPartial("' + esc(tok.name) + '",context,partials,false);';
	}

	function data(id, accessMethod) {
		return 'buf+=this.data(this.' + accessMethod + '("' + esc(id) + '",context,partials,false));';
	}

	function variable(id, accessMethod) {
		return 'buf+=this.variable(this.' + accessMethod + '("' + esc(id) + '",context,partials,false));';
	}

	/**
	 * This is a compiler for the [Mustache](http://mustache.github.com/) templating language which is based on [Hogan.js](http://twitter.github.com/hogan.js/).
	 * For information on Mustache, see the [manpage](http://mustache.github.com/mustache.5.html) and the [spec](https://github.com/mustache/spec).
	 */
	core.Module("core.template.Compiler",
	{
		/**
		 * {core.template.Template} Translates the @code {Array} tree from {#parse} into actual JavaScript
		 * code (in form of a {core.template.Template} instance) to insert dynamic data fields. It uses
		 * the original @text {String} for template construction.
		 */
		compile : function(text) {

			var tree = core.template.Parser.parse(text);
			var code = walk(tree);
			var wrapped = 'var buf="";' + code + 'return buf;';

			return new core.template.Template(new Function('context', 'partials', wrapped), text);
		}
	});

})();<commit_msg>Reduced code size<commit_after>/*
==================================================================================================
  Core - JavaScript Foundation
  Copyright 2010-2012 Sebastian Werner
--------------------------------------------------------------------------------------------------
  Based on the work of:
  Hogan.JS by Twitter, Inc.
  https://github.com/twitter/hogan.js
  Licensed under the Apache License, Version 2.0
  http://www.apache.org/licenses/LICENSE-2.0
==================================================================================================
*/

(function ()
{
	var rSlash = /\\/g;
	var rQuot = /\"/g;
	var rNewline = /\n/g;
	var rCr = /\r/g;

	var accessTags = {
		"#" : 1,
		"^" : 1,
		"{" : 1,
		"&" : 1,
		"$" : 1
	};

	function esc(s) {
		return s.replace(rSlash, '\\\\').replace(rQuot, '\\\"').replace(rNewline, '\\n').replace(rCr, '\\r');
	}

	function chooseAccessMethod(str) {
		return
	}

	function walk(node)
	{
		var code = '';
		var tag, name;

		for (var i = 0, l = node.length; i < l; i++)
		{
			var current = node[i];
			var tag = current.tag;

			if (tag == null)
			{
				code += 'buf+="' + esc(current) + '";';
			}
			else
			{
				var name = current.name;

				if (accessTags[tag]) {
					var accessMethod = ~name.indexOf('.') ? 'getDotted' : 'get';
				}

				if (tag == '#') {
					code += section(current.nodes, name, accessMethod);
				} else if (tag == '^') {
					code += invertedSection(current.nodes, name, accessMethod);
				} else if (tag == '{' || tag == '&') {
					code += data(name, accessMethod);
				} else if (tag == '$') {
					code += variable(name, accessMethod);
				} else if (tag == '<' || tag == '>') {
					code += partial(current);
				} else if (tag == '\n') {
					code += 'buf+="\\n";';
				}
			}
		}

		return code;
	}

	function section(nodes, id, accessMethod, start, end) {
		return 'if(this.section(this.' + accessMethod + '("' + esc(id) + '",ctx,partials,true),ctx,partials,false)){this.renderSection(ctx,partials,function(ctx,partials){' + walk(nodes) + '});ctx.pop();}';
	}

	function invertedSection(nodes, id, accessMethod) {
		return 'if(!this.section(this.' + accessMethod + '("' + esc(id) + '",ctx,partials,true),ctx,partials,true)){' + walk(nodes) + '};';
	}

	function partial(tok) {
		return 'buf+=this.renderPartial("' + esc(tok.name) + '",ctx,partials,false);';
	}

	function data(id, accessMethod) {
		return 'buf+=this.data(this.' + accessMethod + '("' + esc(id) + '",ctx,partials,false));';
	}

	function variable(id, accessMethod) {
		return 'buf+=this.variable(this.' + accessMethod + '("' + esc(id) + '",ctx,partials,false));';
	}

	/**
	 * This is a compiler for the [Mustache](http://mustache.github.com/) templating language which is based on [Hogan.js](http://twitter.github.com/hogan.js/).
	 * For information on Mustache, see the [manpage](http://mustache.github.com/mustache.5.html) and the [spec](https://github.com/mustache/spec).
	 */
	core.Module("core.template.Compiler",
	{
		/**
		 * {core.template.Template} Translates the @code {Array} tree from {#parse} into actual JavaScript
		 * code (in form of a {core.template.Template} instance) to insert dynamic data fields. It uses
		 * the original @text {String} for template construction.
		 */
		compile : function(text) {

			var tree = core.template.Parser.parse(text);
			var wrapped = 'var buf="";' + walk(tree) + 'return buf;';

			return new core.template.Template(new Function('ctx', 'partials', wrapped), text);
		}
	});

})();<|endoftext|>


<commit_before>/**
 * Mixin to display a suitable modal dialog to either allow navigating away from a page with unsaved changes
 * or to cancel and stay on that page.
 *
 * The component using this mixin MUST:
 * - Be a route-level component (used as part of a route) as the beforeRoute* callbacks only get called for these components.
 * - Override the isUnsaved() computed property to return true or false depending on whether they have unsaved changes.
 *
 * The component using this mixin MAY:
 * - Modify the dialog box title, message and button texts and type by specifying the relevant attributes in its own data
 * - Use any method to determine what counts as unsaved(). One example strategy is:
 *   - Ensure the component isn't in its initialisation / loading state
 *   - JSON.stringify() the relevant data on initial load and on saving
 *   - Compare this with the JSON.stringify() version of the current version of the data
 */
export default {

	beforeRouteLeave(to, from, next) {
		this.promptToSave(next);
	},

	beforeRouteUpdate(to, from, next) {
		this.promptToSave(next);
	},

	created() {
		window.addEventListener('beforeunload', this.promptToSaveWindow);
	},

	data() {
		return {
			// override any of these in the component using this mixin's data() to modify the prompt messages
			// vue only does a shallow merge https://vuejs.org/v2/guide/mixins.html#Option-Merging
			savePromptTitle: 'Are you sure you want to leave?',
			savePromptMessage: 'There are unsaved changes',
			savePromptConfirmButtonText: 'OK',
			savePromptCancelButtonText: 'Cancel',
			savePromptType: 'warning'
		};
	},

	computed: {
		/**
		 * Override this wherever you use this mixin
		 * @returns {boolean}
		 */
		isUnsaved() {
			return false;
		}
	},

	methods: {

		promptToSaveWindow(e) {
			/* we are very limited as to what we can do when someone tries to leave
			 https://developer.mozilla.org/en/docs/Web/Events/beforeunload
			 */
			if (this.isUnsaved) {
				const confirmationMessage = this.savePromptTitle + "\n\n" + this.savePromptMessage;
				e.returnValue = confirmationMessage;     // Gecko, Trident, Chrome 34+
				return confirmationMessage;              // Gecko, WebKit, Chrome <34
			}
		},

		promptToSave(callback) {
			if(this.isUnsaved) {
				return this.$confirm(
					this.savePromptMessage,
					this.savePromptTitle,
					{
						confirmButtonText: this.savePromptConfirmButtonText,
						cancelButtonText: this.savePromptCancelButtonText,
						type: this.savePromptType
					}
				).then(() => {
					if(callback) {
						callback(true);
					}
				}).catch(() => {
					if(callback) {
						callback(false);
					}
				});
			}

			callback(true);
			return true;
		}
	}
};
<commit_msg>Remove beforeunload event on component destroyed.<commit_after>/**
 * Mixin to display a suitable modal dialog to either allow navigating away from a page with unsaved changes
 * or to cancel and stay on that page.
 *
 * The component using this mixin MUST:
 * - Be a route-level component (used as part of a route) as the beforeRoute* callbacks only get called for these components.
 * - Override the isUnsaved() computed property to return true or false depending on whether they have unsaved changes.
 *
 * The component using this mixin MAY:
 * - Modify the dialog box title, message and button texts and type by specifying the relevant attributes in its own data
 * - Use any method to determine what counts as unsaved(). One example strategy is:
 *   - Ensure the component isn't in its initialisation / loading state
 *   - JSON.stringify() the relevant data on initial load and on saving
 *   - Compare this with the JSON.stringify() version of the current version of the data
 */
export default {

	beforeRouteLeave(to, from, next) {
		this.promptToSave(next);
	},

	beforeRouteUpdate(to, from, next) {
		this.promptToSave(next);
	},

	created() {
		window.addEventListener('beforeunload', this.promptToSaveWindow);
	},

	destroyed() {
		window.removeEventListener('beforeunload', this.promptToSaveWindow);
	},

	data() {
		return {
			// override any of these in the component using this mixin's data() to modify the prompt messages
			// vue only does a shallow merge https://vuejs.org/v2/guide/mixins.html#Option-Merging
			savePromptTitle: 'Are you sure you want to leave?',
			savePromptMessage: 'There are unsaved changes',
			savePromptConfirmButtonText: 'OK',
			savePromptCancelButtonText: 'Cancel',
			savePromptType: 'warning'
		};
	},

	computed: {
		/**
		 * Override this wherever you use this mixin
		 * @returns {boolean}
		 */
		isUnsaved() {
			return false;
		}
	},

	methods: {

		promptToSaveWindow(e) {
			/* we are very limited as to what we can do when someone tries to leave
			 https://developer.mozilla.org/en/docs/Web/Events/beforeunload
			 */
			if (this.isUnsaved) {
				const confirmationMessage = this.savePromptTitle + "\n\n" + this.savePromptMessage;
				e.returnValue = confirmationMessage;     // Gecko, Trident, Chrome 34+
				return confirmationMessage;              // Gecko, WebKit, Chrome <34
			}
		},

		promptToSave(callback) {
			if(this.isUnsaved) {
				return this.$confirm(
					this.savePromptMessage,
					this.savePromptTitle,
					{
						confirmButtonText: this.savePromptConfirmButtonText,
						cancelButtonText: this.savePromptCancelButtonText,
						type: this.savePromptType
					}
				).then(() => {
					if(callback) {
						callback(true);
					}
				}).catch(() => {
					if(callback) {
						callback(false);
					}
				});
			}

			callback(true);
			return true;
		}
	}
};
<|endoftext|>