<commit_before>/**
 * Output Canvas
 * This is where the output vector is being generated and shown in the DOM
 */

/* Grid */
var drawGridLines = function(num_rectangles_wide, num_rectangles_tall, boundingRect) {
  var width_per_rectangle = boundingRect.width / num_rectangles_wide;
  var height_per_rectangle = boundingRect.height / num_rectangles_tall;
  for (var i = 0; i <= num_rectangles_wide; i++) {
    var xPos = boundingRect.left + i * width_per_rectangle;
    var topPoint = new paper.Point(xPos, boundingRect.top);
    var bottomPoint = new paper.Point(xPos, boundingRect.bottom);
    var aLine = new paper.Path.Line(topPoint, bottomPoint);
    aLine.strokeColor = 'black';

    if (i == num_rectangles_wide / 2) {
      aLine.strokeWidth = 5;
    }
  }
  for (var i = 0; i <= num_rectangles_tall; i++) {
    var yPos = boundingRect.top + i * height_per_rectangle;
    var leftPoint = new paper.Point(boundingRect.left, yPos);
    var rightPoint = new paper.Point(boundingRect.right, yPos);
    var aLine = new paper.Path.Line(leftPoint, rightPoint);
    aLine.strokeColor = 'black';

    if (i == num_rectangles_tall / 2) {
      aLine.strokeWidth = 5;
    }
  }
}

drawGridLines(20, 20, paper.view.bounds);

/* Target */
var targetX = getRandomInt(10, 490);
var targetY = getRandomInt(10, 490);
var targetPath = new Path.Circle(new Point(targetX, targetY), 10);
targetPath.fillColor = 'blue';

/* Vector */
var values = {
  fixLength: false,
  fixAngle: false,
  showCircle: false,
  showAngleLength: false,
  showCoordinates: false
};

var vectorStart, vector, vectorPrevious;
var vectorItem, items, dashedItems;

function processVector(event, drag) {
  vector = event.point - vectorStart;
  if (vectorPrevious) {
    if (values.fixLength && values.fixAngle) {
      vector = vectorPrevious;
    } else if (values.fixLength) {
      vector.length = vectorPrevious.length;
    } else if (values.fixAngle) {
      vector = vector.project(vectorPrevious);
    }
  }
  drawVector(drag);
}

function drawVector(drag) {

  console.log(drag);

  if (items) {
    for (var i = 0, l = items.length; i < l; i++) {
      items[i].remove();
    }
  }
  if (vectorItem)
    vectorItem.remove();
  items = [];
  var arrowVector = vector.normalize(10);
  var end = vectorStart + vector;
  vectorItem = new Group([
    new Path([vectorStart, end]),
    new Path([
      end + arrowVector.rotate(135),
      end,
      end + arrowVector.rotate(-135)
    ])
  ]);
  vectorItem.strokeWidth = 5;
  vectorItem.strokeColor = '#e4141b';

  // Display:
  dashedItems = [];

  // Draw Circle
  if (values.showCircle) {
    dashedItems.push(new Path.Circle({
      center: vectorStart,
      radius: vector.length
    }));
  }
  // Draw Labels
  if (values.showAngleLength) {
    drawAngle(vectorStart, vector, !drag);
    if (!drag)
      drawLength(vectorStart, end, vector.angle < 0 ? -1 : 1, true);
  }
  var quadrant = vector.quadrant;
  if (values.showCoordinates && !drag) {
    drawLength(vectorStart, vectorStart + [vector.x, 0], [1, 3].indexOf(quadrant) != -1 ? -1 : 1, true, vector.x, 'x: ');
    drawLength(vectorStart, vectorStart + [0, vector.y], [1, 3].indexOf(quadrant) != -1 ? 1 : -1, true, vector.y, 'y: ');
  }
  for (var i = 0, l = dashedItems.length; i < l; i++) {
    var item = dashedItems[i];
    item.strokeColor = 'black';
    item.dashArray = [1, 2];
    items.push(item);
  }
  // Update palette
  values.x = vector.x;
  values.y = vector.y;
  values.length = vector.length;
  values.angle = vector.angle;
}

function drawAngle(center, vector, label) {
  var radius = 25,
    threshold = 10;
  if (vector.length < radius + threshold || Math.abs(vector.angle) < 15)
    return;
  var from = new Point(radius, 0);
  var through = from.rotate(vector.angle / 2);
  var to = from.rotate(vector.angle);
  var end = center + to;
  dashedItems.push(new Path.Line(center,
    center + new Point(radius + threshold, 0)));
  dashedItems.push(new Path.Arc(center + from, center + through, end));
  var arrowVector = to.normalize(7.5).rotate(vector.angle < 0 ? -90 : 90);
  dashedItems.push(new Path([
    end + arrowVector.rotate(135),
    end,
    end + arrowVector.rotate(-135)
  ]));
  if (label) {
    // Angle Label
    var text = new PointText(center + through.normalize(radius + 10) + new Point(0, 3));
    text.content = Math.floor(vector.angle * 100) / 100 + '°';
    text.fillColor = 'black';
    items.push(text);
  }
}

function drawLength(from, to, sign, label, value, prefix) {
  var lengthSize = 5;
  if ((to - from).length < lengthSize * 4)
    return;
  var vector = to - from;
  var awayVector = vector.normalize(lengthSize).rotate(90 * sign);
  var upVector = vector.normalize(lengthSize).rotate(45 * sign);
  var downVector = upVector.rotate(-90 * sign);
  var lengthVector = vector.normalize(
    vector.length / 2 - lengthSize * Math.sqrt(2));
  var line = new Path();
  line.add(from + awayVector);
  line.lineBy(upVector);
  line.lineBy(lengthVector);
  line.lineBy(upVector);
  var middle = line.lastSegment.point;
  line.lineBy(downVector);
  line.lineBy(lengthVector);
  line.lineBy(downVector);
  dashedItems.push(line);
  if (label) {
    // Length Label
    var textAngle = Math.abs(vector.angle) > 90 ? textAngle = 180 + vector.angle : vector.angle;
    // Label needs to move away by different amounts based on the
    // vector's quadrant:
    var away = (sign >= 0 ? [1, 4] : [2, 3]).indexOf(vector.quadrant) != -1 ? 8 : 0;
    value = value || vector.length;
    var text = new PointText({
      point: middle + awayVector.normalize(away + lengthSize),
      content: (prefix || '') + Math.floor(value * 1000) / 1000,
      fillColor: 'black',
      justification: 'center'
    });
    text.rotate(textAngle);
    items.push(text);
  }
}

var dashItem;

// function onMouseDrag(inputEvent) {
//   if (!inputEvent.modifiers.shift && values.fixLength && values.fixAngle)
//     vectorStart = inputEvent.point;
//   processVector(inputEvent, inputEvent.modifiers.shift);
// }

function onMouseDown(event) {
  console.log(input);

  fro = new Point(250, 250);

  /* Same as input */
  // to = new Point(250 + input.x, 250 + input.y);

  /* New Formula  */
  var mathCoords = convertToMathCoords(250 + input.x, 250 + input.y);
  var matrixApplied = applyMatrix(mathCoords[0], mathCoords[1], matrix);
  var screenCoords = convertToScreenCoords(matrixApplied[0], matrixApplied[1]);
  to = new Point(screenCoords[0], screenCoords[1]);

  straightLine = to - fro;

  var arrowVector = straightLine.normalize(10);

  if (vectorItem)
    vectorItem.remove();

  // Draw New Vector
  vectorItem = new Group([
    new Path([{
      x: 250,
      y: 250
    }, {
      /* Same as input */
      // x: 250 + input.x,
      // y: 250 + input.y

      /* New Formula */
      x: screenCoords[0],
      y: screenCoords[1]
    }]),

    // Arrows
    new Path([
      to + arrowVector.rotate(135),
      to,
      to + arrowVector.rotate(-135)
    ])
  ]);

  vectorItem.strokeColor = 'red';
  vectorItem.strokeWidth = 5;

  /* If target is hit */
  if(Math.abs(targetX - screenCoords[0]) <= 10 && Math.abs(targetY - screenCoords[1]) <= 10) {
    targetPath.visible = false;
    console.log("Target was hit!");

    targetX = getRandomInt(10, 490);
    targetY = getRandomInt(10, 490);
    targetPath = new Path.Circle(new Point(targetX, targetY), 10);
    targetPath.fillColor = 'blue';
  }

  console.log(straightLine);
}
<commit_msg>Add scoring system<commit_after>/**
 * Output Canvas
 * This is where the output vector is being generated and shown in the DOM
 */

/* Grid */
var drawGridLines = function(num_rectangles_wide, num_rectangles_tall, boundingRect) {
  var width_per_rectangle = boundingRect.width / num_rectangles_wide;
  var height_per_rectangle = boundingRect.height / num_rectangles_tall;
  for (var i = 0; i <= num_rectangles_wide; i++) {
    var xPos = boundingRect.left + i * width_per_rectangle;
    var topPoint = new paper.Point(xPos, boundingRect.top);
    var bottomPoint = new paper.Point(xPos, boundingRect.bottom);
    var aLine = new paper.Path.Line(topPoint, bottomPoint);
    aLine.strokeColor = 'black';

    if (i == num_rectangles_wide / 2) {
      aLine.strokeWidth = 5;
    }
  }
  for (var i = 0; i <= num_rectangles_tall; i++) {
    var yPos = boundingRect.top + i * height_per_rectangle;
    var leftPoint = new paper.Point(boundingRect.left, yPos);
    var rightPoint = new paper.Point(boundingRect.right, yPos);
    var aLine = new paper.Path.Line(leftPoint, rightPoint);
    aLine.strokeColor = 'black';

    if (i == num_rectangles_tall / 2) {
      aLine.strokeWidth = 5;
    }
  }
}

drawGridLines(20, 20, paper.view.bounds);

/* Target */
var targetX = getRandomInt(10, 490);
var targetY = getRandomInt(10, 490);
var targetPath = new Path.Circle(new Point(targetX, targetY), 10);
targetPath.fillColor = 'blue';
var score = 0;

/* Vector */
var values = {
  fixLength: false,
  fixAngle: false,
  showCircle: false,
  showAngleLength: false,
  showCoordinates: false
};

var vectorStart, vector, vectorPrevious;
var vectorItem, items, dashedItems;

function processVector(event, drag) {
  vector = event.point - vectorStart;
  if (vectorPrevious) {
    if (values.fixLength && values.fixAngle) {
      vector = vectorPrevious;
    } else if (values.fixLength) {
      vector.length = vectorPrevious.length;
    } else if (values.fixAngle) {
      vector = vector.project(vectorPrevious);
    }
  }
  drawVector(drag);
}

function drawVector(drag) {

  console.log(drag);

  if (items) {
    for (var i = 0, l = items.length; i < l; i++) {
      items[i].remove();
    }
  }
  if (vectorItem)
    vectorItem.remove();
  items = [];
  var arrowVector = vector.normalize(10);
  var end = vectorStart + vector;
  vectorItem = new Group([
    new Path([vectorStart, end]),
    new Path([
      end + arrowVector.rotate(135),
      end,
      end + arrowVector.rotate(-135)
    ])
  ]);
  vectorItem.strokeWidth = 5;
  vectorItem.strokeColor = '#e4141b';

  // Display:
  dashedItems = [];

  // Draw Circle
  if (values.showCircle) {
    dashedItems.push(new Path.Circle({
      center: vectorStart,
      radius: vector.length
    }));
  }
  // Draw Labels
  if (values.showAngleLength) {
    drawAngle(vectorStart, vector, !drag);
    if (!drag)
      drawLength(vectorStart, end, vector.angle < 0 ? -1 : 1, true);
  }
  var quadrant = vector.quadrant;
  if (values.showCoordinates && !drag) {
    drawLength(vectorStart, vectorStart + [vector.x, 0], [1, 3].indexOf(quadrant) != -1 ? -1 : 1, true, vector.x, 'x: ');
    drawLength(vectorStart, vectorStart + [0, vector.y], [1, 3].indexOf(quadrant) != -1 ? 1 : -1, true, vector.y, 'y: ');
  }
  for (var i = 0, l = dashedItems.length; i < l; i++) {
    var item = dashedItems[i];
    item.strokeColor = 'black';
    item.dashArray = [1, 2];
    items.push(item);
  }
  // Update palette
  values.x = vector.x;
  values.y = vector.y;
  values.length = vector.length;
  values.angle = vector.angle;
}

function drawAngle(center, vector, label) {
  var radius = 25,
    threshold = 10;
  if (vector.length < radius + threshold || Math.abs(vector.angle) < 15)
    return;
  var from = new Point(radius, 0);
  var through = from.rotate(vector.angle / 2);
  var to = from.rotate(vector.angle);
  var end = center + to;
  dashedItems.push(new Path.Line(center,
    center + new Point(radius + threshold, 0)));
  dashedItems.push(new Path.Arc(center + from, center + through, end));
  var arrowVector = to.normalize(7.5).rotate(vector.angle < 0 ? -90 : 90);
  dashedItems.push(new Path([
    end + arrowVector.rotate(135),
    end,
    end + arrowVector.rotate(-135)
  ]));
  if (label) {
    // Angle Label
    var text = new PointText(center + through.normalize(radius + 10) + new Point(0, 3));
    text.content = Math.floor(vector.angle * 100) / 100 + '°';
    text.fillColor = 'black';
    items.push(text);
  }
}

function drawLength(from, to, sign, label, value, prefix) {
  var lengthSize = 5;
  if ((to - from).length < lengthSize * 4)
    return;
  var vector = to - from;
  var awayVector = vector.normalize(lengthSize).rotate(90 * sign);
  var upVector = vector.normalize(lengthSize).rotate(45 * sign);
  var downVector = upVector.rotate(-90 * sign);
  var lengthVector = vector.normalize(
    vector.length / 2 - lengthSize * Math.sqrt(2));
  var line = new Path();
  line.add(from + awayVector);
  line.lineBy(upVector);
  line.lineBy(lengthVector);
  line.lineBy(upVector);
  var middle = line.lastSegment.point;
  line.lineBy(downVector);
  line.lineBy(lengthVector);
  line.lineBy(downVector);
  dashedItems.push(line);
  if (label) {
    // Length Label
    var textAngle = Math.abs(vector.angle) > 90 ? textAngle = 180 + vector.angle : vector.angle;
    // Label needs to move away by different amounts based on the
    // vector's quadrant:
    var away = (sign >= 0 ? [1, 4] : [2, 3]).indexOf(vector.quadrant) != -1 ? 8 : 0;
    value = value || vector.length;
    var text = new PointText({
      point: middle + awayVector.normalize(away + lengthSize),
      content: (prefix || '') + Math.floor(value * 1000) / 1000,
      fillColor: 'black',
      justification: 'center'
    });
    text.rotate(textAngle);
    items.push(text);
  }
}

var dashItem;

// function onMouseDrag(inputEvent) {
//   if (!inputEvent.modifiers.shift && values.fixLength && values.fixAngle)
//     vectorStart = inputEvent.point;
//   processVector(inputEvent, inputEvent.modifiers.shift);
// }

function onMouseDown(event) {
  console.log(input);

  fro = new Point(250, 250);

  /* Same as input */
  // to = new Point(250 + input.x, 250 + input.y);

  /* New Formula  */
  var mathCoords = convertToMathCoords(250 + input.x, 250 + input.y);
  var matrixApplied = applyMatrix(mathCoords[0], mathCoords[1], matrix);
  var screenCoords = convertToScreenCoords(matrixApplied[0], matrixApplied[1]);
  to = new Point(screenCoords[0], screenCoords[1]);

  straightLine = to - fro;

  var arrowVector = straightLine.normalize(10);

  if (vectorItem)
    vectorItem.remove();

  // Draw New Vector
  vectorItem = new Group([
    new Path([{
      x: 250,
      y: 250
    }, {
      /* Same as input */
      // x: 250 + input.x,
      // y: 250 + input.y

      /* New Formula */
      x: screenCoords[0],
      y: screenCoords[1]
    }]),

    // Arrows
    new Path([
      to + arrowVector.rotate(135),
      to,
      to + arrowVector.rotate(-135)
    ])
  ]);

  vectorItem.strokeColor = 'red';
  vectorItem.strokeWidth = 5;

  /* If target is hit */
  if(Math.abs(targetX - screenCoords[0]) <= 10 && Math.abs(targetY - screenCoords[1]) <= 10) {
    targetPath.visible = false;
    console.log("Target was hit!");

    scoreX = 460;
    scoreY = 40;
    var scorePath = new Path.Circle(new Point(scoreX, scoreY), 30);
    scorePath.fillColor = '#7CFC00';
    // scorePath.opacity = 0.75;
    score += 10;

    var text = new PointText(new Point(scoreX, scoreY + 7));
    text.justification = 'center';
    text.fillColor = 'black';
    text.content = score;
    text.fontSize = 20;

    targetX = getRandomInt(10, 460);
    targetY = getRandomInt(10, 460);
    targetPath = new Path.Circle(new Point(targetX, targetY), 10);
    targetPath.fillColor = 'blue';
  }

  console.log(straightLine);
}
<|endoftext|>


<commit_before>import React from 'react';
import { createStore } from 'redux';
import { Link } from 'react-router';

export default class EggHeadTutorial extends React.Component {

  constructor (props) {
    super(props);

    this.test();

    console.log('Tests passed !');
  }

  test = () => {
    const counter = (state = 0, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return state + 1;
        case 'DECREMENT':
          return state - 1;
        default:
          return state;
      }
    };
    const store = createStore(counter);

    const render = () => {
      document.getElementsByClassName('episode6')[0].innerText = store.getState();
    };

    store.subscribe(render);
    setTimeout(render, 100);

    document.body.addEventListener('click', () => {
      store.dispatch({ type : 'INCREMENT' });
    });
  };

  render () {
    return (

This is the tutorial

Check out the console for assertions

The root of the tests are in views/EggHeadTutorial/EggHeadTutorial.js

        <Link to='/'>Back To Home View</Link>

    );
  };

}
<commit_msg>7 : Redux: Implementing Store from Scratch<commit_after>import React from 'react';
import { Link } from 'react-router';

export default class EggHeadTutorial extends React.Component {

  constructor (props) {
    super(props);

    this.test();

    console.log('Tests passed !');
  }

  test = () => {
    const counter = (state = 0, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return state + 1;
        case 'DECREMENT':
          return state - 1;
        default:
          return state;
      }
    };

    const createStore = (reducer) => {
      let state;
      let listeners = [];

      const getState = () => state;

      const dispatch = (action) => {
        state = reducer(state, action);
        listeners.forEach(listener => listener());
      };

      const subscribe = (listener) => {
        listeners.push(listener);
        return () => {
          listeners = listeners.filter(l => l !== listener);
        };
      };

      dispatch({});

      return { getState, dispatch, subscribe };
    };

    const store = createStore(counter);

    const render = () => {
      document.getElementsByClassName('episode6')[0].innerText = store.getState();
    };

    const cancel = store.subscribe(render);
    setTimeout(render, 100);

    document.body.addEventListener('click', () => {
      if (store.getState() === 5) {
        cancel();
      } else {
        store.dispatch({ type : 'INCREMENT' });
      }
    });
  };

  render () {
    return (

This is the tutorial

Check out the console for assertions

The root of the tests are in views/EggHeadTutorial/EggHeadTutorial.js

        <Link to='/'>Back To Home View</Link>

    );
  };

}
<|endoftext|>


<commit_before>
/******************************
        key-mappings.js
 ******************************/

/*

 Initialized by Alex Johnson
 With work from Alex Jacks

 This file stores a number of key-mapping
 settings for converting .csv files
 into their relvant JSON data files.

 Essentially, when a user imports a .csv
 file, we do not know WHICH column represents
 which data field.

 To solve this problem, we store an internal
 set of "kay-mappings". Using the first value
 of a .csv row, we can decide the "type" of
 action that row represents.

 For example, if the first row is "Position_Bar",
 We know that the data represents a player position
 and a specific point, thus we can use the
 key mapping for "position"s. This allows us
 to correlate columns 1->9 with their key.

 */

/*

This is a lookup table.
The first column of a row will always
tell us the name of the action. Using
this name, we fetch the corresponding
key mapping.

*/

// Lookup Table for Mapping action
// name to action type.

var lookup_table = {

	"Attacked" : "action",
	"CraftingTable" : null,
	"Creature Giant Rat attacked first" : null,
	"Dialogue" : null,
	"InteractionContainer" : "interaction",
	"InteractionDoor" : "interaction",
	"InteractionInterior" : "interaction",
	"InteractionNPC" : "interaction",
	"InteractionObject" : "interaction",
	"NPC Bean attacked first" : null,
	"NPC Bobby D attacked first" : null,
	"NPC Collin attacked first" : null,
	"NPC Dough attacked first" : null,
	"NPC JD attacked first" : null,
	"NPC Mel attacked first" : null,
	"NPC Munz attacked first" : null,
	"NPC Ray attacked first" : null,
	"NPC Sara attacked first" : null,
	"NPC Sheriff attacked first" : null,
	"NPC Slater attacked first" : null,
	"ObjectOnActivate" : null,
	"player attacked first" : null,
	"Player killed" : null,
	"Player looted dead" : null,
	"Player looted Dead" : null,
	"Player shooting a dead" : null,
	"PlayerDropItem" : null,
	"PlayerDroppedItem" : null,
	"PlayerEquipped" : null,
	"PlayerJumped" : null,
	"PlayerLootedItem" : null,
	"PlayerShot" : null,
	"PlayerUnequipped" : null,
	"Position_AbandonedHouse" : "position",
	"Position_Bar" : "position",
	"Position_Hotel" : "position",
	"Position_Introhouse" : "position",
	"Position_Outside" : "position",
	"Position_SheriffOffice" : "position",
	"Quest" : null,
	"Sta" : null,
}

// Store a list of available key mappings
var mappings = [];

// Represent a player position in a map
mappings.push({
	game  : "Fallout New Vegas",
	type  : "position",
	keys : {
		area      : 0, // (string) *
		playerID  : 1, // (int)    *
		timestamp : 2, // (double) *
		posX      : 3, // (double) *
		posY      : 4, // (double) *
		cameraX   : 6, // (double)
		cameraY   : 7, // (double)
	},
});

// Represent a user action
mappings.push({
	game  : "Fallout New Vegas",
	type  : "action",

	keys : {
		area      : 0, // (string) *
		playerID  : 1, // (int)    *
		timestamp : 2, // (double) *
		posX      : 3, // (double) *
		posY      : 4, // (double) *
	},
});

// Represent a user interaction
mappings.push({
	game  : "Fallout New Vegas",
	type  : "interaction",

	keys : {
		area      : 0, // (string) *
		playerID  : 1, // (int)    *
		timestamp : 2, // (double) *
		posX      : 3, // (double) *
		posY      : 4, // (double) *
	},
});

// Return the key mapping given the
// game name, and the event name

// ex: getKeyMapping("Fallout New Vegas", "Attacked")

var getKeyMapping = function(game, eventName){

	// Get the type of event from the lookup table
	var type = lookup_table[eventName]

	// Find mapping
	var mapping = _.findWhere(mappings, {game : game, type : type});

	if (!mapping || !type) {
		console.log("Unable to find key mapping for: " + eventName);
		return;
	}

	return mapping;
}

// assignKeys() returns a JSON object, where
// the values of the 'values' array are assigned
// to the keys provided in the 'mapping' array.

// For example:
// values : ["apple", "orange", "pear"]
// mapping : { fruit : 0, color : 1, shape : 0 }

// Results in:
// { fruit : "Apple", color : "orange", shape : "pear" }

var assignKeys = function(values, mapping){
	var acc = {};

	_.each(mapping, function(value, key){
		acc[key] = values[value];
	})

	return acc;
}

// Given a list of arrays, convert the data into JSON objects.
//   game : String of game name
//   eventName : the event name for this table
//   data : multudimensional array container player data
var assignKeysForEntireTable = function(game, eventName, data) {
	var acc = [];

	// Key the key mapping
	var mapping = getKeyMapping(game, eventName).keys;

	// Apply key mapping to each object in the data array
	_.each(data, function(d){
		var json = assigKeys(d, mapping);
		if (json) acc.push(json);
	})

	return acc;
}<commit_msg>Add sample data<commit_after>
/******************************
        key-mappings.js
 ******************************/

/*

 Initialized by Alex Johnson
 With work from Alex Jacks

 This file stores a number of key-mapping
 settings for converting .csv files
 into their relvant JSON data files.

 Essentially, when a user imports a .csv
 file, we do not know WHICH column represents
 which data field.

 To solve this problem, we store an internal
 set of "kay-mappings". Using the first value
 of a .csv row, we can decide the "type" of
 action that row represents.

 For example, if the first row is "Position_Bar",
 We know that the data represents a player position
 and a specific point, thus we can use the
 key mapping for "position"s. This allows us
 to correlate columns 1->9 with their key.

 */

/*

This is a lookup table.
The first column of a row will always
tell us the name of the action. Using
this name, we fetch the corresponding
key mapping.

*/

// Lookup Table for Mapping action
// name to action type.

var lookup_table = {

	"Attacked" : "action",
	"CraftingTable" : null,
	"Creature Giant Rat attacked first" : null,
	"Dialogue" : null,
	"InteractionContainer" : "interaction",
	"InteractionDoor" : "interaction",
	"InteractionInterior" : "interaction",
	"InteractionNPC" : "interaction",
	"InteractionObject" : "interaction",
	"NPC Bean attacked first" : null,
	"NPC Bobby D attacked first" : null,
	"NPC Collin attacked first" : null,
	"NPC Dough attacked first" : null,
	"NPC JD attacked first" : null,
	"NPC Mel attacked first" : null,
	"NPC Munz attacked first" : null,
	"NPC Ray attacked first" : null,
	"NPC Sara attacked first" : null,
	"NPC Sheriff attacked first" : null,
	"NPC Slater attacked first" : null,
	"ObjectOnActivate" : null,
	"player attacked first" : null,
	"Player killed" : null,
	"Player looted dead" : null,
	"Player looted Dead" : null,
	"Player shooting a dead" : null,
	"PlayerDropItem" : null,
	"PlayerDroppedItem" : null,
	"PlayerEquipped" : null,
	"PlayerJumped" : null,
	"PlayerLootedItem" : null,
	"PlayerShot" : null,
	"PlayerUnequipped" : null,
	"Position_AbandonedHouse" : "position",
	"Position_Bar" : "position",
	"Position_Hotel" : "position",
	"Position_Introhouse" : "position",
	"Position_Outside" : "position",
	"Position_SheriffOffice" : "position",
	"Quest" : null,
	"Sta" : null,
}

// Store a list of available key mappings
var mappings = [];

// Represent a player position in a map
mappings.push({
	game  : "Fallout New Vegas",
	type  : "position",
	keys : {
		area      : 0, // (string) *
		playerID  : 1, // (int)    *
		timestamp : 2, // (double) *
		posX      : 3, // (double) *
		posY      : 4, // (double) *
		cameraX   : 6, // (double)
		cameraY   : 7, // (double)
	},
});

// Represent a user action
mappings.push({
	game  : "Fallout New Vegas",
	type  : "action",

	keys : {
		area      : 0, // (string) *
		playerID  : 1, // (int)    *
		timestamp : 2, // (double) *
		posX      : 3, // (double) *
		posY      : 4, // (double) *
	},
});

// Represent a user interaction
mappings.push({
	game  : "Fallout New Vegas",
	type  : "interaction",

	keys : {
		area      : 0, // (string) *
		playerID  : 1, // (int)    *
		timestamp : 2, // (double) *
		posX      : 3, // (double) *
		posY      : 4, // (double) *
	},
});

// Represent a sample mapping
mappings.push({
	game  : "Example",
	type  : "foobar",

	keys : {
		fruit : 0,
		color : 1,
		shape : 2,
	},
});

// Return the key mapping given the
// game name, and the event name

// ex: getKeyMapping("Fallout New Vegas", "Attacked")

var getKeyMapping = function(game, eventName){

	// Get the type of event from the lookup table
	var type = lookup_table[eventName]

	// Find mapping
	var mapping = _.findWhere(mappings, {game : game, type : type});

	if (!mapping || !type) {
		console.log("Unable to find key mapping for: " + eventName);
		return;
	}

	return mapping;
}

// assignKeys() returns a JSON object, where
// the values of the 'values' array are assigned
// to the keys provided in the 'mapping' array.

// For example:
// values : ["apple", "orange", "pear"]
// mapping : { fruit : 0, color : 1, shape : 0 }

// Results in:
// { fruit : "Apple", color : "orange", shape : "pear" }

var assignKeys = function(values, mapping){
	var acc = {};

	_.each(mapping, function(value, key){
		acc[key] = values[value];
	})

	return acc;
	// ex: assignKeys(["apple", "orange", "pear"], { fruit : 0, color : 1, shape : 0 })
}

// Given a list of arrays, convert the data into JSON objects.
//   game : String of game name
//   eventName : the event name for this table
//   data : multudimensional array container player data
var assignKeysForEntireTable = function(game, eventName, data) {
	var acc = [];

	// Key the key mapping
	var mapping = getKeyMapping(game, eventName).keys;

	// Apply key mapping to each object in the data array
	_.each(data, function(d){
		var json = assigKeys(d, mapping);
		if (json) acc.push(json);
	})

	return acc;
}<|endoftext|>


<commit_before>/*
Copyright 2017 Vector Creations Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

import url from 'url';
import qs from 'qs';
import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg';
import PlatformPeg from '../../../PlatformPeg';
import ScalarAuthClient from '../../../ScalarAuthClient';
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
import { _t, _td } from '../../../languageHandler';
import sdk from '../../../index';
import AppPermission from './AppPermission';
import AppWarning from './AppWarning';
import MessageSpinner from './MessageSpinner';
import WidgetUtils from '../../../WidgetUtils';
import dis from '../../../dispatcher';

const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];

export default React.createClass({
    displayName: 'AppTile',

    propTypes: {
        id: React.PropTypes.string.isRequired,
        url: React.PropTypes.string.isRequired,
        name: React.PropTypes.string.isRequired,
        room: React.PropTypes.object.isRequired,
        type: React.PropTypes.string.isRequired,
        // Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer.
        // This should be set to true when there is only one widget in the app drawer, otherwise it should be false.
        fullWidth: React.PropTypes.bool,
        // UserId of the current user
        userId: React.PropTypes.string.isRequired,
        // UserId of the entity that added / modified the widget
        creatorUserId: React.PropTypes.string,
    },

    getDefaultProps() {
        return {
            url: "",
        };
    },

    /**
     * Set initial component state when the App wUrl (widget URL) is being updated. Component props *must* be passed (rather than relying on this.props).
     * @param  {Object} newProps The new properties of the component
     * @return {Object} Updated component state to be set with setState
     */
    _getNewUrlState(newProps) {
        const widgetPermissionId = [newProps.room.roomId, encodeURIComponent(newProps.url)].join('_');
        const hasPermissionToLoad = localStorage.getItem(widgetPermissionId);
        return {
            initialising: true,   // True while we are mangling the widget URL
            loading: true,        // True while the iframe content is loading
            widgetUrl: newProps.url,
            widgetPermissionId: widgetPermissionId,
            // Assume that widget has permission to load if we are the user who added it to the room, or if explicitly granted by the user
            hasPermissionToLoad: hasPermissionToLoad === 'true' || newProps.userId === newProps.creatorUserId,
            error: null,
            deleting: false,
        };
    },

    getInitialState() {
        return this._getNewUrlState(this.props);
    },

    /**
     * Returns true if specified url is a scalar URL, typically https://scalar.vector.im/api
     * @param  {[type]}  url URL to check
     * @return {Boolean} True if specified URL is a scalar URL
     */
    isScalarUrl(url) {
        if (!url) {
            return false;
        }

        let scalarUrls = SdkConfig.get().integrations_widgets_urls;
        if (!scalarUrls || scalarUrls.length == 0) {
            scalarUrls = [SdkConfig.get().integrations_rest_url];
        }

        for (let i = 0; i < scalarUrls.length; i++) {
            if (url.startsWith(scalarUrls[i])) {
                return true;
            }
        }
        return false;
    },

    isMixedContent() {
        const parentContentProtocol = window.location.protocol;
        const u = url.parse(this.props.url);
        const childContentProtocol = u.protocol;
        if (parentContentProtocol === 'https:' && childContentProtocol !== 'https:') {
            console.warn("Refusing to load mixed-content app:",
            parentContentProtocol, childContentProtocol, window.location, this.props.url);
            return true;
        }
        return false;
    },

    componentWillMount() {
        window.addEventListener('message', this._onMessage, false);
        this.setScalarToken();
    },

    /**
     * Adds a scalar token to the widget URL, if required
     * Component initialisation is only complete when this function has resolved
     */
    setScalarToken() {
        if (!this.isScalarUrl(this.props.url)) {
            this.setState({
                error: null,
                widgetUrl: this.props.url,
                initialising: false,
            });
            return;
        }

        // Fetch the token before loading the iframe as we need it to mangle the URL
        if (!this._scalarClient) {
            this._scalarClient = new ScalarAuthClient();
        }
        this._scalarClient.getScalarToken().done((token) => {
            // Append scalar_token as a query param if not already present
            this._scalarClient.scalarToken = token;
            const u = url.parse(this.props.url);
            const params = qs.parse(u.query);
            if (!params.scalar_token) {
                params.scalar_token = encodeURIComponent(token);
                u.query = qs.stringify(params);
            }

            this.setState({
                error: null,
                widgetUrl: u.format(),
                initialising: false,
            });
        }, (err) => {
            this.setState({
                error: err.message,
                initialising: false,
            });
        });
    },

    componentWillUnmount() {
        window.removeEventListener('message', this._onMessage);
    },

    componentWillReceiveProps(nextProps) {
        if (nextProps.url !== this.props.url) {
            this._getNewUrlState(nextProps);
            this.setScalarToken();
        }
    },

    _onMessage(event) {
        if (this.props.type !== 'jitsi') {
            return;
        }
        if (!event.origin) {
            event.origin = event.originalEvent.origin;
        }

        if (!this.state.widgetUrl.startsWith(event.origin)) {
            return;
        }

        if (event.data.widgetAction === 'jitsi_iframe_loaded') {
            const iframe = this.refs.appFrame.contentWindow
                .document.querySelector('iframe[id^="jitsiConferenceFrame"]');
            PlatformPeg.get().setupScreenSharingForIframe(iframe);
        }
    },

    _canUserModify() {
        return WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
    },

    _onEditClick(e) {
        console.log("Edit widget ID ", this.props.id);
        const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
        const src = this._scalarClient.getScalarInterfaceUrlForRoom(
            this.props.room.roomId, 'type_' + this.props.type, this.props.id);
        Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
            src: src,
        }, "mx_IntegrationsManager");
    },

    /* If user has permission to modify widgets, delete the widget, otherwise revoke access for the widget to load in the user's browser
    */
    _onDeleteClick() {
        if (this._canUserModify()) {
            // Show delete confirmation dialog
            const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
            Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, {
                title: _t("Delete Widget"),
                description: _t(
                    "Deleting a widget removes it for all users in this room." +
                    " Are you sure you want to delete this widget?"),
                button: _t("Delete widget"),
                onFinished: (confirmed) => {
                    if (!confirmed) {
                        return;
                    }
                    this.setState({deleting: true});
                    MatrixClientPeg.get().sendStateEvent(
                        this.props.room.roomId,
                        'im.vector.modular.widgets',
                        {}, // empty content
                        this.props.id,
                    ).catch((e) => {
                        console.error('Failed to delete widget', e);
                        this.setState({deleting: false});
                    });
                },
            });
        } else {
            console.log("Revoke widget permissions - %s", this.props.id);
            this._revokeWidgetPermission();
        }
    },

    _onLoaded() {
        this.setState({loading: false});
    },

    // Widget labels to render, depending upon user permissions
    // These strings are translated at the point that they are inserted in to the DOM, in the render method
    _deleteWidgetLabel() {
        if (this._canUserModify()) {
            return _td('Delete widget');
        }
        return _td('Revoke widget access');
    },

    /* TODO -- Store permission in account data so that it is persisted across multiple devices */
    _grantWidgetPermission() {
        console.warn('Granting permission to load widget - ', this.state.widgetUrl);
        localStorage.setItem(this.state.widgetPermissionId, true);
        this.setState({hasPermissionToLoad: true});
    },

    _revokeWidgetPermission() {
        console.warn('Revoking permission to load widget - ', this.state.widgetUrl);
        localStorage.removeItem(this.state.widgetPermissionId);
        this.setState({hasPermissionToLoad: false});
    },

    formatAppTileName() {
        let appTileName = "No name";
        if(this.props.name && this.props.name.trim()) {
            appTileName = this.props.name.trim();
        }
        return appTileName;
    },

    onClickMenuBar(ev) {
        ev.preventDefault();

        // Ignore clicks on menu bar children
        if (ev.target !== this.refs.menu_bar) {
            return;
        }

        // Toggle the view state of the apps drawer
        dis.dispatch({
            action: 'appsDrawer',
            show: !this.props.show,
        });
    },

    render() {
        let appTileBody;

        // Don't render widget if it is in the process of being deleted
        if (this.state.deleting) {
            return

;
        }

        // Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin
        // because that would allow the iframe to prgramatically remove the sandbox attribute, but
        // this would only be for content hosted on the same origin as the riot client: anything
        // hosted on the same origin as the client will get the same access as if you clicked
        // a link to it.
        const sandboxFlags = "allow-forms allow-popups allow-popups-to-escape-sandbox "+
            "allow-same-origin allow-scripts allow-presentation";
        const parsedWidgetUrl = url.parse(this.state.widgetUrl);
        let safeWidgetUrl = '';
        if (ALLOWED_APP_URL_SCHEMES.indexOf(parsedWidgetUrl.protocol) !== -1) {
            safeWidgetUrl = url.format(parsedWidgetUrl);
        }

        if (this.props.show) {
            const loadingElement = (

                    <MessageSpinner msg='Loading...' />

            );
            if (this.state.initialising) {
                appTileBody = loadingElement;
            } else if (this.state.hasPermissionToLoad == true) {
                if (this.isMixedContent()) {
                    appTileBody = (

                            <AppWarning errorMsg="Error - Mixed content" />

                    );
                } else {
                    appTileBody = (

                            { this.loading && loadingElement }

                    );
                }
            } else {
                const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
                appTileBody = (

                        <AppPermission
                            isRoomEncrypted={isRoomEncrypted}
                            url={this.state.widgetUrl}
                            onPermissionGranted={this._grantWidgetPermission}
                        />

                );
            }
        }

        // editing is done in scalar
        const showEditButton = Boolean(this._scalarClient && this._canUserModify());
        const deleteWidgetLabel = this._deleteWidgetLabel();
        let deleteIcon = 'img/cancel.svg';
        let deleteClasses = 'mx_filterFlipColor mx_AppTileMenuBarWidget';
        if(this._canUserModify()) {
            deleteIcon = 'img/icon-delete-pink.svg';
            deleteClasses += ' mx_AppTileMenuBarWidgetDelete';
        }

        return (

                    { this.formatAppTileName() }

                        { /* Edit widget */ }
                        { showEditButton &&  }

                        { /* Delete widget */ }

                { appTileBody }

        );
    },
});
<commit_msg>Fix broken commit.<commit_after>/*
Copyright 2017 Vector Creations Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

import url from 'url';
import qs from 'qs';
import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg';
import PlatformPeg from '../../../PlatformPeg';
import ScalarAuthClient from '../../../ScalarAuthClient';
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
import { _t, _td } from '../../../languageHandler';
import sdk from '../../../index';
import AppPermission from './AppPermission';
import AppWarning from './AppWarning';
import MessageSpinner from './MessageSpinner';
import WidgetUtils from '../../../WidgetUtils';
import dis from '../../../dispatcher';

const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];

export default React.createClass({
    displayName: 'AppTile',

    propTypes: {
        id: React.PropTypes.string.isRequired,
        url: React.PropTypes.string.isRequired,
        name: React.PropTypes.string.isRequired,
        room: React.PropTypes.object.isRequired,
        type: React.PropTypes.string.isRequired,
        // Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer.
        // This should be set to true when there is only one widget in the app drawer, otherwise it should be false.
        fullWidth: React.PropTypes.bool,
        // UserId of the current user
        userId: React.PropTypes.string.isRequired,
        // UserId of the entity that added / modified the widget
        creatorUserId: React.PropTypes.string,
    },

    getDefaultProps() {
        return {
            url: "",
        };
    },

    /**
     * Set initial component state when the App wUrl (widget URL) is being updated. Component props *must* be passed (rather than relying on this.props).
     * @param  {Object} newProps The new properties of the component
     * @return {Object} Updated component state to be set with setState
     */
    _getNewUrlState(newProps) {
        const widgetPermissionId = [newProps.room.roomId, encodeURIComponent(newProps.url)].join('_');
        const hasPermissionToLoad = localStorage.getItem(widgetPermissionId);
        return {
            initialising: true,   // True while we are mangling the widget URL
            loading: true,        // True while the iframe content is loading
            widgetUrl: newProps.url,
            widgetPermissionId: widgetPermissionId,
            // Assume that widget has permission to load if we are the user who added it to the room, or if explicitly granted by the user
            hasPermissionToLoad: hasPermissionToLoad === 'true' || newProps.userId === newProps.creatorUserId,
            error: null,
            deleting: false,
        };
    },

    getInitialState() {
        return this._getNewUrlState(this.props);
    },

    /**
     * Returns true if specified url is a scalar URL, typically https://scalar.vector.im/api
     * @param  {[type]}  url URL to check
     * @return {Boolean} True if specified URL is a scalar URL
     */
    isScalarUrl(url) {
        if (!url) {
            return false;
        }

        let scalarUrls = SdkConfig.get().integrations_widgets_urls;
        if (!scalarUrls || scalarUrls.length == 0) {
            scalarUrls = [SdkConfig.get().integrations_rest_url];
        }

        for (let i = 0; i < scalarUrls.length; i++) {
            if (url.startsWith(scalarUrls[i])) {
                return true;
            }
        }
        return false;
    },

    isMixedContent() {
        const parentContentProtocol = window.location.protocol;
        const u = url.parse(this.props.url);
        const childContentProtocol = u.protocol;
        if (parentContentProtocol === 'https:' && childContentProtocol !== 'https:') {
            console.warn("Refusing to load mixed-content app:",
            parentContentProtocol, childContentProtocol, window.location, this.props.url);
            return true;
        }
        return false;
    },

    componentWillMount() {
        window.addEventListener('message', this._onMessage, false);
        this.setScalarToken();
    },

    /**
     * Adds a scalar token to the widget URL, if required
     * Component initialisation is only complete when this function has resolved
     */
    setScalarToken() {
        if (!this.isScalarUrl(this.props.url)) {
            this.setState({
                error: null,
                widgetUrl: this.props.url,
                initialising: false,
            });
            return;
        }

        // Fetch the token before loading the iframe as we need it to mangle the URL
        if (!this._scalarClient) {
            this._scalarClient = new ScalarAuthClient();
        }
        this._scalarClient.getScalarToken().done((token) => {
            // Append scalar_token as a query param if not already present
            this._scalarClient.scalarToken = token;
            const u = url.parse(this.props.url);
            const params = qs.parse(u.query);
            if (!params.scalar_token) {
                params.scalar_token = encodeURIComponent(token);
                u.query = qs.stringify(params);
            }

            this.setState({
                error: null,
                widgetUrl: u.format(),
                initialising: false,
            });
        }, (err) => {
            this.setState({
                error: err.message,
                initialising: false,
            });
        });
    },

    componentWillUnmount() {
        window.removeEventListener('message', this._onMessage);
    },

    componentWillReceiveProps(nextProps) {
        if (nextProps.url !== this.props.url) {
            this._getNewUrlState(nextProps);
            this.setScalarToken();
        }
    },

    _onMessage(event) {
        if (this.props.type !== 'jitsi') {
            return;
        }
        if (!event.origin) {
            event.origin = event.originalEvent.origin;
        }

        if (!this.state.widgetUrl.startsWith(event.origin)) {
            return;
        }

        if (event.data.widgetAction === 'jitsi_iframe_loaded') {
            const iframe = this.refs.appFrame.contentWindow
                .document.querySelector('iframe[id^="jitsiConferenceFrame"]');
            PlatformPeg.get().setupScreenSharingForIframe(iframe);
        }
    },

    _canUserModify() {
        return WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
    },

    _onEditClick(e) {
        console.log("Edit widget ID ", this.props.id);
        const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
        const src = this._scalarClient.getScalarInterfaceUrlForRoom(
            this.props.room.roomId, 'type_' + this.props.type, this.props.id);
        Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
            src: src,
        }, "mx_IntegrationsManager");
    },

    /* If user has permission to modify widgets, delete the widget, otherwise revoke access for the widget to load in the user's browser
    */
    _onDeleteClick() {
        if (this._canUserModify()) {
            // Show delete confirmation dialog
            const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
            Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, {
                title: _t("Delete Widget"),
                description: _t(
                    "Deleting a widget removes it for all users in this room." +
                    " Are you sure you want to delete this widget?"),
                button: _t("Delete widget"),
                onFinished: (confirmed) => {
                    if (!confirmed) {
                        return;
                    }
                    this.setState({deleting: true});
                    MatrixClientPeg.get().sendStateEvent(
                        this.props.room.roomId,
                        'im.vector.modular.widgets',
                        {}, // empty content
                        this.props.id,
                    ).catch((e) => {
                        console.error('Failed to delete widget', e);
                        this.setState({deleting: false});
                    });
                },
            });
        } else {
            console.log("Revoke widget permissions - %s", this.props.id);
            this._revokeWidgetPermission();
        }
    },

    _onLoaded() {
        this.setState({loading: false});
    },

    // Widget labels to render, depending upon user permissions
    // These strings are translated at the point that they are inserted in to the DOM, in the render method
    _deleteWidgetLabel() {
        if (this._canUserModify()) {
            return _td('Delete widget');
        }
        return _td('Revoke widget access');
    },

    /* TODO -- Store permission in account data so that it is persisted across multiple devices */
    _grantWidgetPermission() {
        console.warn('Granting permission to load widget - ', this.state.widgetUrl);
        localStorage.setItem(this.state.widgetPermissionId, true);
        this.setState({hasPermissionToLoad: true});
    },

    _revokeWidgetPermission() {
        console.warn('Revoking permission to load widget - ', this.state.widgetUrl);
        localStorage.removeItem(this.state.widgetPermissionId);
        this.setState({hasPermissionToLoad: false});
    },

    formatAppTileName() {
        let appTileName = "No name";
        if(this.props.name && this.props.name.trim()) {
            appTileName = this.props.name.trim();
        }
        return appTileName;
    },

    onClickMenuBar(ev) {
        ev.preventDefault();

        // Ignore clicks on menu bar children
        if (ev.target !== this.refs.menu_bar) {
            return;
        }

        // Toggle the view state of the apps drawer
        dis.dispatch({
            action: 'appsDrawer',
            show: !this.props.show,
        });
    },

    render() {
        let appTileBody;

        // Don't render widget if it is in the process of being deleted
        if (this.state.deleting) {
            return

;
        }

        // Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin
        // because that would allow the iframe to prgramatically remove the sandbox attribute, but
        // this would only be for content hosted on the same origin as the riot client: anything
        // hosted on the same origin as the client will get the same access as if you clicked
        // a link to it.
        const sandboxFlags = "allow-forms allow-popups allow-popups-to-escape-sandbox "+
            "allow-same-origin allow-scripts allow-presentation";
        const parsedWidgetUrl = url.parse(this.state.widgetUrl);
        let safeWidgetUrl = '';
        if (ALLOWED_APP_URL_SCHEMES.indexOf(parsedWidgetUrl.protocol) !== -1) {
            safeWidgetUrl = url.format(parsedWidgetUrl);
        }

        if (this.props.show) {
            const loadingElement = (

                    <MessageSpinner msg='Loading...' />

            );
            if (this.state.initialising) {
                appTileBody = loadingElement;
            } else if (this.state.hasPermissionToLoad == true) {
                if (this.isMixedContent()) {
                    appTileBody = (

                            <AppWarning errorMsg="Error - Mixed content" />

                    );
                } else {
                    appTileBody = (

                            { this.state.loading && loadingElement }

                    );
                }
            } else {
                const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
                appTileBody = (

                        <AppPermission
                            isRoomEncrypted={isRoomEncrypted}
                            url={this.state.widgetUrl}
                            onPermissionGranted={this._grantWidgetPermission}
                        />

                );
            }
        }

        // editing is done in scalar
        const showEditButton = Boolean(this._scalarClient && this._canUserModify());
        const deleteWidgetLabel = this._deleteWidgetLabel();
        let deleteIcon = 'img/cancel.svg';
        let deleteClasses = 'mx_filterFlipColor mx_AppTileMenuBarWidget';
        if(this._canUserModify()) {
            deleteIcon = 'img/icon-delete-pink.svg';
            deleteClasses += ' mx_AppTileMenuBarWidgetDelete';
        }

        return (

                    { this.formatAppTileName() }

                        { /* Edit widget */ }
                        { showEditButton &&  }

                        { /* Delete widget */ }

                { appTileBody }

        );
    },
});
<|endoftext|>


<commit_before>self.addEventListener('message', function(event) {
    self.client = event.source;
});

self.onnotificationclose = function (event) {
    self.client.postMessage(JSON.stringify({
        id: event.notification.data.id,
        action: 'close'
    }));
};

self.onnotificationclick = function (event) {
  if (typeof event.notification.data.link !== 'undefined' &&
      event.notification.data.link !== null) {

    event.notification.close();
    var link = event.notification.data.link;

    self.client.postMessage(JSON.stringify({
      link: link,
      action: 'notificationclick'
    }));

    var promise =
      new Promise(function(resolve) {
                    setTimeout(resolve, 5000);
                  }).then(function() {
                    return clients.openWindow("https://tankmeister.de/reserve?url=" + encodeURIComponent(link));
                  });
    event.waitUntil(promise);
  }
};
<commit_msg>does clients.openWindow accept url parameters?<commit_after>self.addEventListener('message', function(event) {
    self.client = event.source;
});

self.onnotificationclose = function (event) {
    self.client.postMessage(JSON.stringify({
        id: event.notification.data.id,
        action: 'close'
    }));
};

self.onnotificationclick = function (event) {
  if (typeof event.notification.data.link !== 'undefined' &&
      event.notification.data.link !== null) {

    event.notification.close();
    var link = event.notification.data.link;

    self.client.postMessage(JSON.stringify({
      link: link,
      action: 'notificationclick'
    }));

    var promise =
      new Promise(function(resolve) {
                    setTimeout(resolve, 5000);
                  }).then(function() {
                    return clients.openWindow("https://tankmeister.de/reserve/" + btoa(link));
                  });
    event.waitUntil(promise);
  }
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import './App.css';
import TodayInfo from './component/TodayInfo';
import TodoRegister from './component/TodoRegister';
import TodaysTodos from './component/TodaysTodos';
import TriedTodos from './component/TriedTodos';

class App extends Component {
  constructor(props){
    super();
    this.todoFilter = this.todoFilter.bind(this);
    this.onRegisterTodo = this.onRegisterTodo.bind(this);
    this.state = {
      todos: [
       // 오늘이 8월 10일(Aug 10 2017)인 상태,
       { // #Todays_todo 완료된 todo
         completed: true,
         registeredDate: new Date(),
         completedDate: new Date(),
         content: "리액트 스터디 과제하기"
       },
       { // #Todays_todo 아직 완료하지 않은 todo
         completed: false,
         registeredDate: new Date(),
         completedDate: null,
         content: "책상 정리하기"
       },
       { // 화면에 보이지 않는, 과거에 완료한 todo
         completed: true,
         registeredDate: new Date("8/10/2017"),
         completedDate: new Date("8/10/2017"),
         content: "책 반납하기"
       },
       { // #Tried_todo에 노출되는, 오늘보다 이전에 등록되었는데(registeredDate이 오늘 이전) 아직 완료하지 않은 todo
         completed: false,
         registeredDate: new Date("8/10/2017"),
         completedDate: null,
         content: "사진 출력하기"
       }
     ]
    };
  }

  todoFilter(filter){
    switch (filter) {
      case "today":
        //registeredDate가 오늘인 친구들
        return this._getTodaysTodos();
      case "tried":
        //registeredDate가 오늘 이전인데, completed가 false인 친구들
        return this._getTriedTodos();
      default:
        return this.state.todos;
    }
  }

  _getTodaysTodos(){
    return this.state.todos.filter(this._isToday);
  }

  _getTriedTodos(){
    const pastTodos = this.state.todos.filter(function(todo){
      if (this._isToday(todo)) {
        return false;
      } else {
        return true;
      }
    }.bind(this));
    return pastTodos.filter(this._isNotCompleted);
  }

  _isNotCompleted(todo){
    return todo.completed === false;
  }

  _isToday(todo){
    const now = new Date();
    const year = now.getFullYear();
    const month = now.getMonth() + 1;
    const day = now.getDate();

    const registeredDate = todo.registeredDate;
    const registeredYear = registeredDate.getFullYear();
    const registeredMonth = registeredDate.getMonth() + 1;
    const registeredDay = registeredDate.getDate();

    return (year === registeredYear) && (month === registeredMonth) && (day === registeredDay);
  }

  onRegisterTodo(newTodo){
    this.setState({
      todos: [...this.state.todos, newTodo]
    });
  }

  //[TODO] todo.content === target.content 부분 개선
  //TodaysTodo에서부터 타고 타고 거슬러 올라와서, target을 전체 투두 리스트에서 찾을 방법....?
  updateTodaysTodos = (target) => {
    this.setState({
      todos: this.state.todos.map((todo, id)=>{
        return (todo.content === target.content) ? {...todo, completed: target.completed, completedDate: target.completedDate} : todo;
      })
    }, ()=>{
      // console.log(this.state.todos);
    });
  }

  render() {
    return (
      <main id="tt-wrapper">
        <TodayInfo/>
        <TodoRegister onRegister={this.onRegisterTodo}/>
        <TodaysTodos todaysTodos={this.todoFilter("today")} updateTodaysTodos={this.updateTodaysTodos}/>
        <TriedTodos triedTodos={this.todoFilter("tried")}/>
      </main>
    );
  }
}

export default App;
<commit_msg>[05] todos를 todays/tried로 나누어 각각을 state로 관리하도록 한다<commit_after>import React, { Component } from 'react';
import './App.css';
import TodayInfo from './component/TodayInfo';
import TodoRegister from './component/TodoRegister';
import TodaysTodos from './component/TodaysTodos';
import TriedTodos from './component/TriedTodos';

const defaultTodos = [
  // 오늘이 8월 10일(Aug 10 2017)인 상태,
  { // #Todays_todo 완료된 todo
    completed: true,
    registeredDate: new Date(),
    completedDate: new Date(),
    content: "리액트 스터디 과제하기"
  },
  { // #Todays_todo 아직 완료하지 않은 todo
    completed: false,
    registeredDate: new Date(),
    completedDate: null,
    content: "책상 정리하기"
  },
  { // 화면에 보이지 않는, 과거에 완료한 todo
    completed: true,
    registeredDate: new Date("8/10/2017"),
    completedDate: new Date("8/10/2017"),
    content: "책 반납하기"
  },
  { // #Tried_todo에 노출되는, 오늘보다 이전에 등록되었는데(registeredDate이 오늘 이전) 아직 완료하지 않은 todo
    completed: false,
    registeredDate: new Date("8/10/2017"),
    completedDate: null,
    content: "사진 출력하기"
  }
];

class App extends Component {
  constructor(props){
    super();
    this.onRegisterTodo = this.onRegisterTodo.bind(this);
    this.state = {
      todaysTodos: this._getTodaysTodos(defaultTodos),
      triedTodos: this._getTriedTodos(defaultTodos)
    };
  }

  _getTodaysTodos(todos){
    return todos.filter(this._isToday);
  }

  _getTriedTodos(todos){
    const pastTodos = todos.filter(function(todo){
      if (this._isToday(todo)) {
        return false;
      } else {
        return true;
      }
    }.bind(this));
    return pastTodos.filter(this._isNotCompleted);
  }

  _isNotCompleted(todo){
    return todo.completed === false;
  }

  _isToday(todo){
    const now = new Date();
    const year = now.getFullYear();
    const month = now.getMonth() + 1;
    const day = now.getDate();

    const registeredDate = todo.registeredDate;
    const registeredYear = registeredDate.getFullYear();
    const registeredMonth = registeredDate.getMonth() + 1;
    const registeredDay = registeredDate.getDate();

    return (year === registeredYear) && (month === registeredMonth) && (day === registeredDay);
  }

  onRegisterTodo(newTodo){
    this.setState({
      todaysTodos: [...this.state.todaysTodos, newTodo]
    });
  }

  updateTodaysTodo = (targetTodo) => {
    this.setState({
      todaysTodos: this.state.todaysTodos.map((todo, id) => {
        return (targetTodo.id === id) ? {...todo, completed: targetTodo.completed, completedDate: targetTodo.completedDate} : todo;
      })
    }, ()=>{
      console.log(this.state.todaysTodos);
    });
  }

  render() {
    return (
      <main id="tt-wrapper">
        <TodayInfo/>
        <TodoRegister onRegister={this.onRegisterTodo}/>
        <TodaysTodos todaysTodos={this.state.todaysTodos} updateTodaysTodo={this.updateTodaysTodo}/>
        <TriedTodos triedTodos={this.state.triedTodos}/>
      </main>
    );
  }
}

export default App;
<|endoftext|>


<commit_before>'use strict';

var TexturePackage = require('../../packages/texture/package');
var ExampleXMLStore = require('../ExampleXMLStore');

module.exports = {
  name: 'author-example',
  configure: function(config) {
    // Use the default Texture package
    config.import(TexturePackage);

    // Define XML Store
    config.setXMLStore(ExampleXMLStore);
    config.addStyle(__dirname, 'app.scss');
  }
};
<commit_msg>addStyle is no longer.<commit_after>'use strict';

var TexturePackage = require('../../packages/texture/package');
var ExampleXMLStore = require('../ExampleXMLStore');

module.exports = {
  name: 'author-example',
  configure: function(config) {
    // Use the default Texture package
    config.import(TexturePackage);
    // Define XML Store
    config.setXMLStore(ExampleXMLStore);
  }
};
<|endoftext|>


<commit_before>$(document).ready(function(){
	// Resize select and input
	resizeSection();
	resizeNumber();
	resizeDate();

	$(window).resize(function() {
		resizeSection();
		resizeNumber();
		resizeDate();
	});

	// Fetching and displaying stories
	getNews();

	// onchange of input fields
	$('#section').change(function(){
		resizeSection();
		getNews();
	});
	$('#number').bind('keyup input paste', function(){
		resizeNumber();
        if ($(this).val() != ''){
            getNews();
        }

		var first_option = $('#date option:first-child').text();
		var last_letter = first_option.substr(first_option.length - 1);

		if (last_letter == 's' && $(this).val() == 1) {
			$('#date option').each(function() {
				var removed_s = $(this).text().slice(0, -1);
				$(this).text($(this).text().slice(0, -1));
			})
		}
		else if (last_letter != 's' && $(this).val() != 1) {
			$('#date option').each(function() {
				old_text = $(this).text();
				$(this).text(old_text + 's');
			})
		}
	});
	$('#date').change(function(){
		resizeDate();
		getNews();
	});
});

function getNews(){
	$('.news').html('');

	amount = $('#number').val();
	scope = $('#date option:selected').val();
	section = $('#section option:selected').val();
	// keyword $('#keyword').val()
	keyword = '';

	try {
		keyword = $.trim(keyword);
		validate(amount, scope, section, keyword);

		// make asynchronous request
		getGuardianNews(amount, scope, section, keyword);
	} catch (e) {
        ajax.abort(); // using global variable containing current ajax request
		$('.news').html('
Please assure your input is correct (' + e + ')
')
		return;
	}
}

function handleGuardianNews(news){
	str = '
';
	$.each(news, function(index, story) {
		headline = story[0];
		link = story[1];
		date = story[2];

		str += '
';
		str += '
' + headline + '
';
		str += '
';
		str += '

';
		// str += '<time datetime="' + date.toJSON() + '"> ' + date.f('d MMM') + '</time> // ';
		str += 'Full article';
		str += '
';
		str += '
';
	});
	str += '
';
	$('.news').html(str);

	initializeLinkListeners();
}

function initializeLinkListeners() {
	var articles = $('.headline').next('article');
	articles.hide();

	$('.headline').click(function() {
		var headline = $(this);
		var article = headline.next('article');

		article.slideToggle({
			duration: 400,
			easing: 'easeInOutCirc'
		});

		articles.not(article).slideUp({
			duration: 400,
			easing: 'easeInOutCirc'
		});

		if (! headline.hasClass('loaded')) {
			getSummary(headline);
			headline.addClass('loaded');
		}
	})
}

function initializeTryAgain () {
    $('.try-again').click(function(event) {
        event.preventDefault();

        if ($(this).hasClass('try-again--news')) {
            getNews();
        } else if ($(this).hasClass('try-again--summary')) {
            getSummary($(this).parent().parent().parent().prev('h2'));
        }
    });
}

function resizeSection() {
	option_val = $('#section > option:selected').text();
	$('#section-span').html(option_val);
	$('#section').width($('#section-span').width() + 3);
}

function resizeNumber() {
	$('#number-span').html($('#number').val());
	$('#number').css('width', $('#number-span').width() + 3);

	var font_size = $('body').css('font-size');
	console.log(font_size);

	if (font_size == '16px') {
		$('#number').css('height', '42px');
	}

	else if (font_size == '14px') {
		$('#number').css('height', '36px');
	}

	else if (font_size == '12px') {
		$('#number').css('height', '30px');
	}
}

function resizeDate() {
	option_val = $('#date > option:selected').val();
	$('#date-span').html(option_val);
	$('#date').width($('#date-span').width() + 3);
}

function validate(amount, scope, section, keyword) {
	// scopes = ['days', 'weeks', 'months'];
	// if (scopes.indexOf(scope) < 0) {
	// 	throw 'Invalid scope';
	// }

	if (isNaN(amount)) {
		throw 'Invalid amount of ' + $('#date option:selected').val();
	}

	// sections = ['world', 'uk-news', 'football', 'film', 'business', 'politics', 'technology'];
	// if (sections.indexOf(section) < 0) {
	//     throw 'Invalid section';
	// }
}

/**********************************AJAX***************************************/
// global variable containing the current AJAX request, needed if it must be aborted
ajax = null;

function getGuardianNews(amount, scope, section, keyword){
    today = new Date(); // dates are entered relatively, today is needed

    start_time = new Date();
    end_time = today;
    //section = section;

    // get correct start time
    switch (scope) {
        case 'days': start_time.setDate(today.getDate()-amount); break;
        case 'weeks': start_time.setDate(today.getDate()-amount*7); break;
        case 'months': start_time.setMonth(today.getMonth()-amount); break;
    }

    // make asynchronous ajax request, calls handle
    ajaxGuardian(start_time, end_time, section, keyword);
}

function ajaxGuardian(start_time, end_time, section, keyword){
    ajax = $.ajax({
        url: 'guardian_feeds.php',
        type: 'GET',
        dataType: 'json',
        data: {start_time: start_time.f('yyyy-MM-dd'), end_time: end_time.f('yyyy-MM-dd'), section: section, keyword: keyword},
        success: function(data, textStatus, xhr) {
            // replace JSON date format with JavaScript Date Objects
            $.each(data, function(index, story) {
                story[2] = new Date(story[2]);
            });

            // call handleGuardianNews function of main.js
            handleGuardianNews(data);
        },
        error: function(xhr, textStatus, errorThrown) {
            $('.news').html('
Couldn’t scoop the news for you… Try again
');
            initializeTryAgain();
            console.log('ERROR: ' + errorThrown);
        }
    });
}

function getSummary (object) {
    ajax = $.ajax({
        url: 'ots.php',
        type: 'GET',
        dataType: 'html',
        data: {to_sum: $(object).next('article').find('a.read-more').attr('href'), ratio: 10},
        success: function(data, textStatus, xhr) {
            data = $('
').html(data).text();
            if (data == 'null') {
                data = 'No summary found.'
            }
            $(object).next('article').find('.summary--content').slideUp(200, function(){
                $(object).next('article').find('.summary--content').html('
' + data + '
');
                $(object).next('article').find('.summary--content').find('a').attr('target', '_blank');
                object.addClass('loaded');
            }).slideDown(300);
        },
        error: function(xhr, textStatus, errorThrown) {
            $(object).next('article').find('.summary--content').html('
Couldn’t get summary…Try again
');
            initializeTryAgain();
            console.log('ERROR in getSummary');
            console.log(xhr);
        }
    });

}<commit_msg>Remove console log<commit_after>$(document).ready(function(){
	// Resize select and input
	resizeSection();
	resizeNumber();
	resizeDate();

	$(window).resize(function() {
		resizeSection();
		resizeNumber();
		resizeDate();
	});

	// Fetching and displaying stories
	getNews();

	// onchange of input fields
	$('#section').change(function(){
		resizeSection();
		getNews();
	});
	$('#number').bind('keyup input paste', function(){
		resizeNumber();
        if ($(this).val() != ''){
            getNews();
        }

		var first_option = $('#date option:first-child').text();
		var last_letter = first_option.substr(first_option.length - 1);

		if (last_letter == 's' && $(this).val() == 1) {
			$('#date option').each(function() {
				var removed_s = $(this).text().slice(0, -1);
				$(this).text($(this).text().slice(0, -1));
			})
		}
		else if (last_letter != 's' && $(this).val() != 1) {
			$('#date option').each(function() {
				old_text = $(this).text();
				$(this).text(old_text + 's');
			})
		}
	});
	$('#date').change(function(){
		resizeDate();
		getNews();
	});
});

function getNews(){
	$('.news').html('');

	amount = $('#number').val();
	scope = $('#date option:selected').val();
	section = $('#section option:selected').val();
	// keyword $('#keyword').val()
	keyword = '';

	try {
		keyword = $.trim(keyword);
		validate(amount, scope, section, keyword);

		// make asynchronous request
		getGuardianNews(amount, scope, section, keyword);
	} catch (e) {
        ajax.abort(); // using global variable containing current ajax request
		$('.news').html('
Please assure your input is correct (' + e + ')
')
		return;
	}
}

function handleGuardianNews(news){
	str = '
';
	$.each(news, function(index, story) {
		headline = story[0];
		link = story[1];
		date = story[2];

		str += '
';
		str += '
' + headline + '
';
		str += '
';
		str += '

';
		// str += '<time datetime="' + date.toJSON() + '"> ' + date.f('d MMM') + '</time> // ';
		str += 'Full article';
		str += '
';
		str += '
';
	});
	str += '
';
	$('.news').html(str);

	initializeLinkListeners();
}

function initializeLinkListeners() {
	var articles = $('.headline').next('article');
	articles.hide();

	$('.headline').click(function() {
		var headline = $(this);
		var article = headline.next('article');

		article.slideToggle({
			duration: 400,
			easing: 'easeInOutCirc'
		});

		articles.not(article).slideUp({
			duration: 400,
			easing: 'easeInOutCirc'
		});

		if (! headline.hasClass('loaded')) {
			getSummary(headline);
			headline.addClass('loaded');
		}
	})
}

function initializeTryAgain () {
    $('.try-again').click(function(event) {
        event.preventDefault();

        if ($(this).hasClass('try-again--news')) {
            getNews();
        } else if ($(this).hasClass('try-again--summary')) {
            getSummary($(this).parent().parent().parent().prev('h2'));
        }
    });
}

function resizeSection() {
	option_val = $('#section > option:selected').text();
	$('#section-span').html(option_val);
	$('#section').width($('#section-span').width() + 3);
}

function resizeNumber() {
	$('#number-span').html($('#number').val());
	$('#number').css('width', $('#number-span').width() + 3);

	var font_size = $('body').css('font-size');

	if (font_size == '16px') {
		$('#number').css('height', '42px');
	}

	else if (font_size == '14px') {
		$('#number').css('height', '36px');
	}

	else if (font_size == '12px') {
		$('#number').css('height', '30px');
	}
}

function resizeDate() {
	option_val = $('#date > option:selected').val();
	$('#date-span').html(option_val);
	$('#date').width($('#date-span').width() + 3);
}

function validate(amount, scope, section, keyword) {
	// scopes = ['days', 'weeks', 'months'];
	// if (scopes.indexOf(scope) < 0) {
	// 	throw 'Invalid scope';
	// }

	if (isNaN(amount)) {
		throw 'Invalid amount of ' + $('#date option:selected').val();
	}

	// sections = ['world', 'uk-news', 'football', 'film', 'business', 'politics', 'technology'];
	// if (sections.indexOf(section) < 0) {
	//     throw 'Invalid section';
	// }
}

/**********************************AJAX***************************************/
// global variable containing the current AJAX request, needed if it must be aborted
ajax = null;

function getGuardianNews(amount, scope, section, keyword){
    today = new Date(); // dates are entered relatively, today is needed

    start_time = new Date();
    end_time = today;
    //section = section;

    // get correct start time
    switch (scope) {
        case 'days': start_time.setDate(today.getDate()-amount); break;
        case 'weeks': start_time.setDate(today.getDate()-amount*7); break;
        case 'months': start_time.setMonth(today.getMonth()-amount); break;
    }

    // make asynchronous ajax request, calls handle
    ajaxGuardian(start_time, end_time, section, keyword);
}

function ajaxGuardian(start_time, end_time, section, keyword){
    ajax = $.ajax({
        url: 'guardian_feeds.php',
        type: 'GET',
        dataType: 'json',
        data: {start_time: start_time.f('yyyy-MM-dd'), end_time: end_time.f('yyyy-MM-dd'), section: section, keyword: keyword},
        success: function(data, textStatus, xhr) {
            // replace JSON date format with JavaScript Date Objects
            $.each(data, function(index, story) {
                story[2] = new Date(story[2]);
            });

            // call handleGuardianNews function of main.js
            handleGuardianNews(data);
        },
        error: function(xhr, textStatus, errorThrown) {
            $('.news').html('
Couldn’t scoop the news for you… Try again
');
            initializeTryAgain();
            console.log('ERROR: ' + errorThrown);
        }
    });
}

function getSummary (object) {
    ajax = $.ajax({
        url: 'ots.php',
        type: 'GET',
        dataType: 'html',
        data: {to_sum: $(object).next('article').find('a.read-more').attr('href'), ratio: 10},
        success: function(data, textStatus, xhr) {
            data = $('
').html(data).text();
            if (data == 'null') {
                data = 'No summary found.'
            }
            $(object).next('article').find('.summary--content').slideUp(200, function(){
                $(object).next('article').find('.summary--content').html('
' + data + '
');
                $(object).next('article').find('.summary--content').find('a').attr('target', '_blank');
                object.addClass('loaded');
            }).slideDown(300);
        },
        error: function(xhr, textStatus, errorThrown) {
            $(object).next('article').find('.summary--content').html('
Couldn’t get summary…Try again
');
            initializeTryAgain();
            console.log('ERROR in getSummary');
            console.log(xhr);
        }
    });

}<|endoftext|>


<commit_before>//
// templates-bb.js
//
// Javascript templating library using Backbone.
//
// ============================================================================
//
// MODEL - SIMPLE
// MODEL - DOUBLE PASS
// COLLECTION
// READY
//
// ============================================================================
//

(function() {

	TemplatesBB = {};

	///////////////////////////////////////////////////////////////////////////
	// $MODEL - SIMPLE

	TemplatesBB.TemplateModel = Backbone.Model.extend({
		defaults: {
			name: null, // Required
			markup: null, // Required
			localization: null, // Optional
			html: null
		},
		initialize: function() {
			// Reset html if attributes change
			this.on("change:markup", function() { this.set("html", null); });
			this.on("change:localization", function() { this.set("html", null); });
		},
		// Build html out of template and template data
		build: function(data) {
			// Build the html only if required
			if (_.isNull(this.get("html"))) {
				// Create template using the markup
				// Combine localization and data object
				// Execute the templating with the resulting object
				// Set result as html
				this.set("html", _.template(this.get("markup"))(_.extend(this.get("localization"), data)));
			}
		},
		// Render the template -> build it if necessary and return element's html
		render: function(data) {
			this.build(data);
			return this.get("html");
		}
	});

	///////////////////////////////////////////////////////////////////////////
	// $MODEL - DOUBLE PASS

	TemplatesBB.DoublePassTemplateModel =  TemplatesBB.TemplateModel.extend({
		// Build html out of template and template data
		build: function(data) {
			// Build the html only if required
			if (_.isNull(this.get("html"))) {
				// First pass: the data
				// Create template using the markup
				// Execute the templating with the data object
				var template = _.template(this.get("markup"))(data);

				// Adapt template settings to localization pass
				_.templateSettings = {
					interpolate: /\<\@\=(.+?)\@\>/gim,
					evaluate: /\<\@(.+?)\@\>/gim
				};

				// Second pass: the localization
				// Create template using the result of the data pass
				// Execute the templating with the localization object
				// Set result as html
				this.set("html", _.template(template)(this.get("localization")));

				// Reset template settings
				_.templateSettings = {
					interpolate: /\<\%\=(.+?)\%\>/gim,
					evaluate: /\<\%(.+?)\%\>/gim
				};
			}
		}
	});

	///////////////////////////////////////////////////////////////////////////
	// $COLLECTION

	TemplatesBB.TemplateCollection = Backbone.Collection.extend({
		baseURL: null, // Required
		lang: "en",
		isReady: false,
		model: function(attrs, options) {
			// Choose which model to instanciate according to the data
			if (!_.isUndefined(attrs.double_pass) && attrs.double_pass) {
				return new  TemplatesBB.DoublePassTemplateModel(attrs, options);
			}

			return new  TemplatesBB.TemplateModel(attrs, options);
		},
		// Initialize parameters
		initialize: function(args, options) {
			// Extend/Overwrite the parameters with the options passed in arguments
			_.extend(this, options);
		},
		// Parse the data returned by fetch()
		parse: function(data) {
			// Make sure valid data is returned
			if (!_.isUndefined(data) && data.length >= 1) {
				// Loop through templates
				for (var index in data) {
					// Markup was returned
					if (!_.isUndefined(data[index].markup)) {
						// Decode markup
						data[index].markup = $("<div/>").html(data[index].markup).text();
					}
					// Localization doesn't need to be decoded
				}
			}

			return data;
		},
		// Fetching markup and localization duplicates templates, this merges them
		merge: function(callback) {
			// Templates are already ready, no need for merging
			if (this.isReady) {
				return;
			}

			// No templates mean they are ready
			if (this.models.length === 0) {
				this.isReady = true;
			}
			else {
				// Get template names and avoid duplicates
				var names = _.uniq(this.pluck("name"));

				// Loop through available names
				for (var index in names) {
					// Get the templates matching the name
					var models = this.where({ name: names[index]});

					// Merge them if they are two
					if (2 === models.length) {
						this.isReady = true;

						// 1 has markup -> set 0's markup with 1's markup and get rid of 1
						if (!_.isNull(models[1].get("markup"))) {
							models[0].set("markup", models[1].get("markup"));
							this.remove(models[1]);
						}
						// 1 has localization -> set 0's localization with 1's localization and get rid of 1
						if (!_.isNull(models[1].get("localization"))) {
							models[0].set("localization", models[1].get("localization"));
							this.remove(models[1]);
						}
					}
				}
			}

			// Templates are ready, execute callback
			if (this.isReady) {
				if (_.isFunction(callback)) {
					callback();
				}
			}
		},
		// Fetch the markup only and try to merge it once it's received
		fetchMarkup: function(callback) {
			var that = this;
			this.isReady = false;
			this.url = this.baseURL + "/html.json";
			this.fetch({ add: true }).done(function() { that.merge(callback); });
		},
		// Fetch the localization only and try to merge it once it's received
		fetchLocalization: function(callback) {
			var that = this;
			this.isReady = false;
			this.url = this.baseURL + "/" + this.lang + ".json";
			this.fetch({ add: true }).done(function() { that.merge(callback); });
		},
		// Fetch both markup and localization
		fetchAll: function(callback) {
			this.fetchMarkup(callback);
			this.fetchLocalization(callback);
		},
		// Change all templates' language (fetch and apply)
		setLanguage: function(lang, callback) {
			var that = this;
			this.lang = lang;
			this.fetchLocalization(callback);
		}
	});

	///////////////////////////////////////////////////////////////////////////
	// $READY

	TemplatesBB.ready = function(holder, options, callback) {
		// Make sure templates are loaded
		if (!_.isUndefined(holder.templates) && holder.templates.isReady) {
			// Templates have already been fetched...
			if (_.isFunction(callback)) {
				callback();
			}
		}
		else {
			holder.templates = new  TemplatesBB.TemplateCollection(null, options);
			holder.templates.fetchAll(function() {
				// All the templates have been fetched, let's get to business...
				if (_.isFunction(callback)) {
					callback();
				}
			});
		}
	};

}).call(this);<commit_msg>Add dataSource<commit_after>//
// templates-bb.js
//
// Javascript templating library using Backbone.
//
// ============================================================================
//
// MODEL - SIMPLE
// MODEL - DOUBLE PASS
// COLLECTION
// READY
//
// ============================================================================
//

(function() {

	TemplatesBB = {};

	///////////////////////////////////////////////////////////////////////////
	// $MODEL - SIMPLE

	TemplatesBB.TemplateModel = Backbone.Model.extend({
		defaults: {
			name: null, // Required
			dataSource: null, // Required
			markup: null, // Required
			localization: null, // Optional
			html: null
		},
		initialize: function() {
			// Reset html if attributes change
			this.on("change:markup", function() { this.set("html", null); });
			this.on("change:localization", function() { this.set("html", null); });
		},
		// Build html out of template and template data
		build: function(data) {
			// Build the html only if required
			if (_.isNull(this.get("html"))) {
				// Create template using the markup
				// Combine localization and data object
				// Execute the templating with the resulting object
				// Set result as html
				this.set("html", _.template(this.get("markup"))(_.extend(this.get("localization"), data)));
			}
		},
		// Render the template -> build it if necessary and return element's html
		render: function(data) {
			this.build(_.extend(!_.isNull(this.get("dataSource")) ? this.get("dataSource").getData() : {}, data));
			return this.get("html");
		}
	});

	///////////////////////////////////////////////////////////////////////////
	// $MODEL - DOUBLE PASS

	TemplatesBB.DoublePassTemplateModel =  TemplatesBB.TemplateModel.extend({
		// Build html out of template and template data
		build: function(data) {
			// Build the html only if required
			if (_.isNull(this.get("html"))) {
				// First pass: the data
				// Create template using the markup
				// Execute the templating with the data object
				var template = _.template(this.get("markup"))(data);

				// Adapt template settings to localization pass
				_.templateSettings = {
					interpolate: /\<\@\=(.+?)\@\>/gim,
					evaluate: /\<\@(.+?)\@\>/gim
				};

				// Second pass: the localization
				// Create template using the result of the data pass
				// Execute the templating with the localization object
				// Set result as html
				this.set("html", _.template(template)(this.get("localization")));

				// Reset template settings
				_.templateSettings = {
					interpolate: /\<\%\=(.+?)\%\>/gim,
					evaluate: /\<\%(.+?)\%\>/gim
				};
			}
		}
	});

	///////////////////////////////////////////////////////////////////////////
	// $COLLECTION

	TemplatesBB.TemplateCollection = Backbone.Collection.extend({
		baseURL: null, // Required
		lang: "en",
		isReady: false,
		model: function(attrs, options) {
			// Choose which model to instanciate according to the data
			if (!_.isUndefined(attrs.double_pass) && attrs.double_pass) {
				return new  TemplatesBB.DoublePassTemplateModel(attrs, options);
			}

			return new  TemplatesBB.TemplateModel(attrs, options);
		},
		// Initialize parameters
		initialize: function(args, options) {
			// Extend/Overwrite the parameters with the options passed in arguments
			_.extend(this, options);
		},
		// Parse the data returned by fetch()
		parse: function(data) {
			// Make sure valid data is returned
			if (!_.isUndefined(data) && data.length >= 1) {
				// Loop through templates
				for (var index in data) {
					// Markup was returned
					if (!_.isUndefined(data[index].markup)) {
						// Decode markup
						data[index].markup = $("<div/>").html(data[index].markup).text();
					}
					// Localization doesn't need to be decoded
				}
			}

			return data;
		},
		// Fetching markup and localization duplicates templates, this merges them
		merge: function(callback) {
			// Templates are already ready, no need for merging
			if (this.isReady) {
				return;
			}

			// No templates mean they are ready
			if (this.models.length === 0) {
				this.isReady = true;
			}
			else {
				// Get template names and avoid duplicates
				var names = _.uniq(this.pluck("name"));

				// Loop through available names
				for (var index in names) {
					// Get the templates matching the name
					var models = this.where({ name: names[index]});

					// Merge them if they are two
					if (2 === models.length) {
						this.isReady = true;

						// 1 has markup -> set 0's markup with 1's markup and get rid of 1
						if (!_.isNull(models[1].get("markup"))) {
							models[0].set("markup", models[1].get("markup"));
							this.remove(models[1]);
						}
						// 1 has localization -> set 0's localization with 1's localization and get rid of 1
						if (!_.isNull(models[1].get("localization"))) {
							models[0].set("localization", models[1].get("localization"));
							this.remove(models[1]);
						}
					}
				}
			}

			// Templates are ready, execute callback
			if (this.isReady) {
				if (_.isFunction(callback)) {
					callback();
				}
			}
		},
		// Fetch the markup only and try to merge it once it's received
		fetchMarkup: function(callback) {
			var that = this;
			this.isReady = false;
			this.url = this.baseURL + "/html.json";
			this.fetch({ add: true }).done(function() { that.merge(callback); });
		},
		// Fetch the localization only and try to merge it once it's received
		fetchLocalization: function(callback) {
			var that = this;
			this.isReady = false;
			this.url = this.baseURL + "/" + this.lang + ".json";
			this.fetch({ add: true }).done(function() { that.merge(callback); });
		},
		// Fetch both markup and localization
		fetchAll: function(callback) {
			this.fetchMarkup(callback);
			this.fetchLocalization(callback);
		},
		// Change all templates' language (fetch and apply)
		setLanguage: function(lang, callback) {
			var that = this;
			this.lang = lang;
			this.fetchLocalization(callback);
		}
	});

	///////////////////////////////////////////////////////////////////////////
	// $READY

	TemplatesBB.ready = function(holder, options, callback) {
		// Make sure templates are loaded
		if (!_.isUndefined(holder.templates) && holder.templates.isReady) {
			// Templates have already been fetched...
			if (_.isFunction(callback)) {
				callback();
			}
		}
		else {
			holder.templates = new  TemplatesBB.TemplateCollection(null, options);
			holder.templates.fetchAll(function() {
				// All the templates have been fetched, let's get to business...
				if (_.isFunction(callback)) {
					callback();
				}
			});
		}
	};

}).call(this);<|endoftext|>