<commit_before>import React, { Component } from 'react';
import { Map as IMap, Record } from 'immutable';

import { createRootUserDefinition } from 'dynamic-runtime';
import './Patcher.css';
import CreateNodeBox from './CreateNodeBox';
import NodePool from './NodePool';
import genUID from './uid';

const AppExtra = Record({
  uid: null,
  name: null,
  position: null, // {x, y}
  def: null,
  functionArguments: null,
});

const DefExtra = Record({
  viewOffset: null, // {x, y}
});

class Patcher extends Component {
  constructor(props) {
    super(props);

    this.rootDefinition = createRootUserDefinition();
    this.rootActivation = this.rootDefinition.activate(new Map(), () => {}, new Map());
    this.nodePool = new NodePool(); // TODO: this should probably be passed as a prop, but let's load directly for now

    // Positions in state are relative to patcher element
    this.state = {
      creatingNode: null,
      appExtra: new IMap(), // NativeApplication -> AppExtra
      defExtra: new IMap([[this.rootDefinition, new DefExtra({viewOffset: {x: 0, y: 0}})]])

      , // UserDefinition -> DefExtra
      selectedPort: null,
    };

    this.drag = null;

    this.rootElem = null;
    this.canvasElem = null;
    this.portElemMap = new Map(); // maps InPort or OutPort to DOM element representing the port
  }

  componentWillUnmount() {
    if (this.drag) {
      this.endDrag();
    }
  }

  componentDidMount() {
    this.updateCanvas();
  }

  componentDidUpdate() {
    this.updateCanvas();
  }

  eventRelativePosition(event) {
    return {
      x: event.pageX - this.rootElem.offsetLeft,
      y: event.pageY - this.rootElem.offsetTop,
    };
  }

  beginDrag(mouseEvent, target) {
    if (this.drag) {
      throw new Error('internal error');
    }

    document.addEventListener('mousemove', this.handleMouseMove);
    document.addEventListener('mouseup', this.handleMouseUp);

    const pos = this.eventRelativePosition(mouseEvent.nativeEvent);

    this.drag = {
      target,
      downPos: pos,
      lastPos: pos,
    };
  }

  endDrag() {
    document.removeEventListener('mousemove', this.handleMouseMove);
    document.removeEventListener('mouseup', this.handleMouseUp);
    this.drag = null;
  }

  handleDefinitionMouseDown = (definition, event) => {
    if (event.target === event.currentTarget) {
      this.beginDrag(event, {
        kind: 'definition',
        definition,
      });
      event.preventDefault();
    }
  }

  handleMouseMove = (e) => {
    if (!this.drag) {
      throw new Error('internal error');
    }

    const pos = this.eventRelativePosition(e);
    const dx = pos.x - this.drag.lastPos.x;
    const dy = pos.y - this.drag.lastPos.y;
    this.drag.lastPos = pos;

    if (this.drag.target.kind === 'definition') {
      const definition = this.drag.target.definition;
      this.setState((state) => ({...state, defExtra: state.defExtra.updateIn([definition, 'viewOffset'], vo => ({x: vo.x + dx, y: vo.y + dy}))}));
    }
  }

  handleMouseUp = (e) => {
    if (this.drag) {
      const pos = this.eventRelativePosition(e);

      const delta = Math.abs(pos.x - this.drag.downPos.x) + Math.abs(pos.y - this.drag.downPos.y);

      if (this.drag.target.kind === 'definition') {
        if (delta === 0) {
          // This was a click (no movement)
          if (this.state.creatingNode) {
            // If a box is already being shown, close it
            this.closeCreateNodeBox();
          } else {
            if (this.state.selectedPort) {
              this.setState({
                selectedPort: null,
              });
            } else {
              // If no box is shown, show it at the click position
              this.setState({
                creatingNode: {
                  boxPosition: pos,
                  definition: this.drag.target.definition,
                },
              });
            }
          }
        }
      }

      this.endDrag();
    }
  }

  closeCreateNodeBox() {
    this.setState({
      creatingNode: null,
    });
  }

  handleCreateNodeBoxSelect = (nodeName, nodeDef) => {
    if (!this.state.creatingNode) {
      throw new Error('internal error');
    }

    // Look up what definition this creation is happening within
    const definition = this.state.creatingNode.definition;

    // If the definition has function parameters, created sub-definitions for each one
    const functionArguments = new Map();
    if (nodeDef.functionParameters) {
      for (const n in nodeDef.functionParameters) {
        const subdef = definition.addContainedUserDefinition();
        this.setState(state => ({...state, defExtra: state.defExtra.set(subdef, new DefExtra({viewOffset: {x: 0, y: 0}}))}));
        functionArguments.set(n, subdef);
      }
    }

    const app = definition.addNativeApplication(nodeDef, functionArguments);

    this.setState((state) => {
      const boxPos = state.creatingNode.boxPosition;
      const viewOffset = state.defExtra.get(definition).viewOffset;
      const position = {
        x: boxPos.x - viewOffset.x,
        y: boxPos.y - viewOffset.y,
      };

      const appExtra = new AppExtra({
        uid: genUID(),
        name: nodeName,
        position,
        def: nodeDef,
        functionArguments,
      });

      return {...state, appExtra: state.appExtra.set(app, appExtra)};
    });

    this.closeCreateNodeBox();
  }

  handleCreateNodeBoxCancel = () => {
    this.closeCreateNodeBox();
  }

  handlePortClick = (portObj, isInput) => {
    this.setState((state) => {
      if (state.selectedPort) {
        // Attempt connection between selected port and this port
        let a = state.selectedPort;
        let b = {portObj, isInput};
        let invalid = false;
        if (a.isInput) {
          if (b.isInput) {
            invalid = true;
          } else {
            [a, b] = [b, a]; // swap
          }
        } else {
          if (b.isInput) {
            // all good
          } else {
            invalid = true;
          }
        }

        if (invalid) {
          return state; // ignore
        } else {
          a.portObj.containingDefinition.addConnection(a.portObj, b.portObj);

          // Clear selected port
          return {...state, selectedPort: null};
        }
      } else {
        // Set as selected port
        return {...state, selectedPort: {portObj, isInput}};
      }
    });
  }

  handlePortDoubleClick = (portObj) => {
    this.rootDefinition.disconnectPort(portObj);
    this.forceUpdate(); // since we don't keep connections in our own state, need to force to see update
  }

  formatPortStr(nodeId, isInput, portName) {
    return nodeId + '|' + isInput + '|' + portName;
  }

  handleRemoveNode = (nodeId) => {
    this.rootDefinition.removeNode(nodeId);
    this.setState((state) => {
      return { ...state, appExtra: state.appExtra.delete(nodeId)};
    });
  }

  updateCanvas() {
    this.canvasElem.width = this.canvasElem.offsetWidth;
    this.canvasElem.height = this.canvasElem.offsetHeight;

    const portElemConnectPos = (elem, isInput) => {
      let x = isInput ? 0 : elem.offsetWidth;
      let y = 0.5*elem.offsetHeight;

      let e = elem;
      while (e !== this.rootElem) {
        x += e.offsetLeft;
        y += e.offsetTop;
        e = e.offsetParent;
      }

      return {x, y};
    }

    const ctx = this.canvasElem.getContext('2d');

    ctx.clearRect(0, 0, this.canvasElem.width, this.canvasElem.height);

    ctx.strokeStyle = 'rgb(255, 255, 255)';
    ctx.lineWidth = 2;
    ctx.beginPath();
    for (const icxn of this.rootDefinition.connections) { // TODO: unhack this direct access
      const fromPortElem = this.portElemMap.get(icxn.outPort);
      const toPortElem = this.portElemMap.get(icxn.inPort);

      const fromPos = portElemConnectPos(fromPortElem);
      const toPos = portElemConnectPos(toPortElem, true);

      // Place cp1 right of fromPos, cp2 left of toPos
      const STRAIN_RELIEF = 100;
      const cp1x = fromPos.x +STRAIN_RELIEF;
      const cp2x = toPos.x - STRAIN_RELIEF;
      ctx.moveTo(fromPos.x, fromPos.y);
      ctx.bezierCurveTo(cp1x, fromPos.y, cp2x, toPos.y, toPos.x, toPos.y);
    }
    ctx.stroke();
  }

  renderPort(name, portObj, isInput) {
    const sp = this.state.selectedPort;
    const selected = sp && (sp.portObj === portObj);

    return (

 { this.handlePortClick(portObj, isInput); }} onDoubleClick={() => { this.handlePortDoubleClick(portObj); }} ref={el => { this.portElemMap.set(portObj, el); }} className={'Patcher_node-port' + (selected ? ' Patcher_node-port-selected' : '')}>{name}

    );
  }

  renderApplication(napp) {
    const appExtra = this.state.appExtra.get(napp);

    const inputPorts = [];
    const outputPorts = [];
    for (const [n, p] of napp.inPorts) {
      inputPorts.push({name: n, portObj: p});
    }
    for (const [n, p] of napp.outPorts) {
      outputPorts.push({name: n, portObj: p});
    }

    return (

{appExtra.name}
<button onClick={() => { this.handleRemoveNode(appExtra.uid); }}>✕</button>

{inputPorts.map(p => this.renderPort(p.name, p.portObj, true))}

{outputPorts.map(p => this.renderPort(p.name, p.portObj, false))}

        {(appExtra.functionArguments.size > 0) &&

            {[...appExtra.functionArguments.entries()].map(([n, subdef]) => (

                {this.renderDefinition(subdef)}

            ))}

        }

    );
  }

  renderDefinition(definition) {
    const viewOffset = this.state.defExtra.get(definition).viewOffset;

    return (

 { this.handleDefinitionMouseDown(definition, e); }}>

          {[...definition.nativeApplications].map(napp => this.renderApplication(napp))}

    );
  }

  render() {
    return (

 { this.rootElem = el; }}>
        {this.renderDefinition(this.rootDefinition)}
        <canvas ref={canvas => { this.canvasElem = canvas; }} />
        {this.state.creatingNode &&

<CreateNodeBox width={200} nodePool={this.nodePool} onSelect={this.handleCreateNodeBoxSelect} onCancel={this.handleCreateNodeBoxCancel} />

        }

    );
  }
}

export default Patcher;
<commit_msg>Patcher: Fix positioning in subdefs<commit_after>import React, { Component } from 'react';
import { Map as IMap, Record } from 'immutable';

import { createRootUserDefinition } from 'dynamic-runtime';
import './Patcher.css';
import CreateNodeBox from './CreateNodeBox';
import NodePool from './NodePool';
import genUID from './uid';

const AppExtra = Record({
  uid: null,
  name: null,
  position: null, // {x, y}
  def: null,
  functionArguments: null,
});

const DefExtra = Record({
  viewOffset: null, // {x, y}
});

class Patcher extends Component {
  constructor(props) {
    super(props);

    this.rootDefinition = createRootUserDefinition();
    this.rootActivation = this.rootDefinition.activate(new Map(), () => {}, new Map());
    this.nodePool = new NodePool(); // TODO: this should probably be passed as a prop, but let's load directly for now

    // Positions in state are relative to patcher element
    this.state = {
      creatingNode: null,
      appExtra: new IMap(), // NativeApplication -> AppExtra
      defExtra: new IMap([[this.rootDefinition, new DefExtra({viewOffset: {x: 0, y: 0}})]])

      , // UserDefinition -> DefExtra
      selectedPort: null,
    };

    this.drag = null;

    this.rootElem = null;
    this.canvasElem = null;

    // TODO: Can we avoid leaks/bookkeeping by making these WeakMaps?
    this.portElemMap = new Map(); // maps InPort or OutPort to DOM element representing the port
    this.defPositioningElemMap = new Map(); // maps UserDefinition to DOM element
  }

  componentWillUnmount() {
    if (this.drag) {
      this.endDrag();
    }
  }

  componentDidMount() {
    this.updateCanvas();
  }

  componentDidUpdate() {
    this.updateCanvas();
  }

  elemRelativePosition(elem) {
    let x = 0, y = 0;

    while (elem !== this.rootElem) {
      x += elem.offsetLeft;
      y += elem.offsetTop;
      elem = elem.offsetParent;
    }

    return {x, y};
  }

  eventRelativePosition(event) {
    return {
      x: event.pageX - this.rootElem.offsetLeft,
      y: event.pageY - this.rootElem.offsetTop,
    };
  }

  beginDrag(mouseEvent, target) {
    if (this.drag) {
      throw new Error('internal error');
    }

    document.addEventListener('mousemove', this.handleMouseMove);
    document.addEventListener('mouseup', this.handleMouseUp);

    const pos = this.eventRelativePosition(mouseEvent.nativeEvent);

    this.drag = {
      target,
      downPos: pos,
      lastPos: pos,
    };
  }

  endDrag() {
    document.removeEventListener('mousemove', this.handleMouseMove);
    document.removeEventListener('mouseup', this.handleMouseUp);
    this.drag = null;
  }

  handleDefinitionMouseDown = (definition, event) => {
    if (event.target === event.currentTarget) {
      this.beginDrag(event, {
        kind: 'definition',
        definition,
      });
      event.preventDefault();
    }
  }

  handleMouseMove = (e) => {
    if (!this.drag) {
      throw new Error('internal error');
    }

    const pos = this.eventRelativePosition(e);
    const dx = pos.x - this.drag.lastPos.x;
    const dy = pos.y - this.drag.lastPos.y;
    this.drag.lastPos = pos;

    if (this.drag.target.kind === 'definition') {
      const definition = this.drag.target.definition;
      this.setState((state) => ({...state, defExtra: state.defExtra.updateIn([definition, 'viewOffset'], vo => ({x: vo.x + dx, y: vo.y + dy}))}));
    }
  }

  handleMouseUp = (e) => {
    if (this.drag) {
      const pos = this.eventRelativePosition(e);

      const delta = Math.abs(pos.x - this.drag.downPos.x) + Math.abs(pos.y - this.drag.downPos.y);

      if (this.drag.target.kind === 'definition') {
        if (delta === 0) {
          // This was a click (no movement)
          if (this.state.creatingNode) {
            // If a box is already being shown, close it
            this.closeCreateNodeBox();
          } else {
            if (this.state.selectedPort) {
              this.setState({
                selectedPort: null,
              });
            } else {
              // If no box is shown, show it at the click position
              this.setState({
                creatingNode: {
                  boxPosition: pos,
                  definition: this.drag.target.definition,
                },
              });
            }
          }
        }
      }

      this.endDrag();
    }
  }

  closeCreateNodeBox() {
    this.setState({
      creatingNode: null,
    });
  }

  handleCreateNodeBoxSelect = (nodeName, nodeDef) => {
    if (!this.state.creatingNode) {
      throw new Error('internal error');
    }

    // Look up what definition this creation is happening within
    const definition = this.state.creatingNode.definition;

    // If the definition has function parameters, created sub-definitions for each one
    const functionArguments = new Map();
    if (nodeDef.functionParameters) {
      for (const n in nodeDef.functionParameters) {
        const subdef = definition.addContainedUserDefinition();
        this.setState(state => ({...state, defExtra: state.defExtra.set(subdef, new DefExtra({viewOffset: {x: 0, y: 0}}))}));
        functionArguments.set(n, subdef);
      }
    }

    const app = definition.addNativeApplication(nodeDef, functionArguments);

    this.setState((state) => {
      const boxPos = state.creatingNode.boxPosition;
      const offset = this.elemRelativePosition(this.defPositioningElemMap.get(definition));
      const position = {
        x: boxPos.x - offset.x,
        y: boxPos.y - offset.y,
      };

      const appExtra = new AppExtra({
        uid: genUID(),
        name: nodeName,
        position,
        def: nodeDef,
        functionArguments,
      });

      return {...state, appExtra: state.appExtra.set(app, appExtra)};
    });

    this.closeCreateNodeBox();
  }

  handleCreateNodeBoxCancel = () => {
    this.closeCreateNodeBox();
  }

  handlePortClick = (portObj, isInput) => {
    this.setState((state) => {
      if (state.selectedPort) {
        // Attempt connection between selected port and this port
        let a = state.selectedPort;
        let b = {portObj, isInput};
        let invalid = false;
        if (a.isInput) {
          if (b.isInput) {
            invalid = true;
          } else {
            [a, b] = [b, a]; // swap
          }
        } else {
          if (b.isInput) {
            // all good
          } else {
            invalid = true;
          }
        }

        if (invalid) {
          return state; // ignore
        } else {
          a.portObj.containingDefinition.addConnection(a.portObj, b.portObj);

          // Clear selected port
          return {...state, selectedPort: null};
        }
      } else {
        // Set as selected port
        return {...state, selectedPort: {portObj, isInput}};
      }
    });
  }

  handlePortDoubleClick = (portObj) => {
    this.rootDefinition.disconnectPort(portObj);
    this.forceUpdate(); // since we don't keep connections in our own state, need to force to see update
  }

  formatPortStr(nodeId, isInput, portName) {
    return nodeId + '|' + isInput + '|' + portName;
  }

  handleRemoveNode = (nodeId) => {
    this.rootDefinition.removeNode(nodeId);
    this.setState((state) => {
      return { ...state, appExtra: state.appExtra.delete(nodeId)};
    });
  }

  updateCanvas() {
    this.canvasElem.width = this.canvasElem.offsetWidth;
    this.canvasElem.height = this.canvasElem.offsetHeight;

    const portElemConnectPos = (elem, isInput) => {
      const pos = this.elemRelativePosition(elem);

      return {
        x: pos.x + (isInput ? 0 : elem.offsetWidth),
        y: pos.y + 0.5*elem.offsetHeight,
      };
    }

    const ctx = this.canvasElem.getContext('2d');

    ctx.clearRect(0, 0, this.canvasElem.width, this.canvasElem.height);

    ctx.strokeStyle = 'rgb(255, 255, 255)';
    ctx.lineWidth = 2;
    ctx.beginPath();
    for (const icxn of this.rootDefinition.connections) { // TODO: unhack this direct access
      const fromPortElem = this.portElemMap.get(icxn.outPort);
      const toPortElem = this.portElemMap.get(icxn.inPort);

      const fromPos = portElemConnectPos(fromPortElem);
      const toPos = portElemConnectPos(toPortElem, true);

      // Place cp1 right of fromPos, cp2 left of toPos
      const STRAIN_RELIEF = 100;
      const cp1x = fromPos.x +STRAIN_RELIEF;
      const cp2x = toPos.x - STRAIN_RELIEF;
      ctx.moveTo(fromPos.x, fromPos.y);
      ctx.bezierCurveTo(cp1x, fromPos.y, cp2x, toPos.y, toPos.x, toPos.y);
    }
    ctx.stroke();
  }

  renderPort(name, portObj, isInput) {
    const sp = this.state.selectedPort;
    const selected = sp && (sp.portObj === portObj);

    return (

 { this.handlePortClick(portObj, isInput); }} onDoubleClick={() => { this.handlePortDoubleClick(portObj); }} ref={el => { this.portElemMap.set(portObj, el); }} className={'Patcher_node-port' + (selected ? ' Patcher_node-port-selected' : '')}>{name}

    );
  }

  renderApplication(napp) {
    const appExtra = this.state.appExtra.get(napp);

    const inputPorts = [];
    const outputPorts = [];
    for (const [n, p] of napp.inPorts) {
      inputPorts.push({name: n, portObj: p});
    }
    for (const [n, p] of napp.outPorts) {
      outputPorts.push({name: n, portObj: p});
    }

    return (

{appExtra.name}
<button onClick={() => { this.handleRemoveNode(appExtra.uid); }}>✕</button>

{inputPorts.map(p => this.renderPort(p.name, p.portObj, true))}

{outputPorts.map(p => this.renderPort(p.name, p.portObj, false))}

        {(appExtra.functionArguments.size > 0) &&

            {[...appExtra.functionArguments.entries()].map(([n, subdef]) => (

                {this.renderDefinition(subdef)}

            ))}

        }

    );
  }

  renderDefinition(definition) {
    const viewOffset = this.state.defExtra.get(definition).viewOffset;

    return (

 { this.handleDefinitionMouseDown(definition, e); }}>

 { this.defPositioningElemMap.set(definition, el); }}>
          {[...definition.nativeApplications].map(napp => this.renderApplication(napp))}

    );
  }

  render() {
    return (

 { this.rootElem = el; }}>
        {this.renderDefinition(this.rootDefinition)}
        <canvas ref={canvas => { this.canvasElem = canvas; }} />
        {this.state.creatingNode &&

<CreateNodeBox width={200} nodePool={this.nodePool} onSelect={this.handleCreateNodeBoxSelect} onCancel={this.handleCreateNodeBoxCancel} />

        }

    );
  }
}

export default Patcher;
<|endoftext|>


<commit_before>var Promise = Em.RSVP.Promise;
var map = Em.ArrayPolyfills.map;
var filter = Em.ArrayPolyfills.filter;
var forEach = Em.ArrayPolyfills.forEach;

function cloneJson(json) {
	return JSON.parse(JSON.stringify(json));
}

/**
 * This class acts as a base adapter for synchronous storage forms. Specifically,
 * the {{link-to-class 'LocalStorageAdapter'}} and {{link-to-class 'MemoryAdapter'}}
 * inherit from this class. This class will perform all of the work of updating data
 * and maintaining data integrity, subclasses need only implement the
 * {{link-to-method 'EmberGraphAdapter' 'getDatabase'}} and
 * {{link-to-method 'EmberGraphAdapter' 'setDatabase'}} methods to create a
 * fully-functioning adapter. This class works with data as a single JSON object
 * that takes the following form:
 *
 * ```json
 * {
 *     "records": {
 *          "type_key": {
 *              "record_id": {
 *                  "attr1": "value",
 *                  "attr2": 5
 *              }
 *          }
 *     },
 *     "relationships": [{
 *             "t1": "type_key",
 *             "i1": "id",
 *             "n1": "relationship_name",
 *             "t2": "type_key",
 *             "i2": "id",
 *             "n2": "relationship_name",
 *     }]
 * }
 * ```
 *
 * If you can store the JSON data, then this adapter will ensure complete
 * database integrity, since everything is done is single transactions.
 * You may also override some of the hooks and methods if you wish to
 * customize how the adapter saves or retrieves data.
 *
 * TODO: Should relationships be an array instead? What's the cost of a `splice`?
 *
 * @class EmberGraphAdapter
 * @extends Adapter
 * @category abstract
 */
EG.EmberGraphAdapter = EG.Adapter.extend({

	/**
	 * @property serializer
	 * @type JSONSerializer
	 * @protected
	 * @final
	 */
	serializer: null,

	init: function() {
		this._super();
		this.set('serializer', EG.JSONSerializer.create({
			polymorphicRelationships: true
		}));
	},

	createRecord: function(record) {
		var json = this.serialize(record, { requestType: 'createRecord', recordType: record.get('typeKey') });
		return this.serverCreateRecord(record.get('typeKey'), json);
	},

	findRecord: function(typeKey, id) {
		return this.serverFindRecord(typeKey, id);
	},

	findMany: function(typeKey, ids) {
		return this.serverFindMany(typeKey, ids);
	},

	findAll: function(typeKey) {
		return this.serverFindAll(typeKey);
	},

	findQuery: function() {
		return Promise.reject('LocalStorageAdapter doesn\'t implement `findQuery` by default.');
	},

	updateRecord: function(record) {
		var changes = this.serialize(record, { requestType: 'updateRecord', recordType: record.get('typeKey') });
		return this.serverUpdateRecord(record.get('typeKey'), record.get('id'), changes);
	},

	deleteRecord: function(record) {
		return this.serverDeleteRecord(record.get('typeKey'), record.get('id'));
	},

	serialize: function(record, options) {
		return this.get('serializer').serialize(record, options);
	},

	deserialize: function(payload, options) {
		return this.get('serializer').deserialize(payload, options);
	},

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	/**
	 * @method serverCreateRecord
	 * @param {String} typeKey
	 * @param {JSON} json
	 * @return {Promise}
	 * @protected
	 */
	serverCreateRecord: function(typeKey, json) {
		var _this = this;

		return this.getDatabase().then(function(db) {
			var id = _this.generateIdForRecord(typeKey, json, db);
			db = _this.putRecordInDatabase(typeKey, id, json, db);
			return _this.setDatabase(db);
		});
	},

	/**
	 * @method serverFindRecord
	 * @param {String} typeKey
	 * @param {String} id
	 * @return {Promise}
	 * @protected
	 */
	serverFindRecord: function(typeKey, id) {
		var _this = this;

		return this.getDatabase().then(function(db) {
			if (Em.get(db, 'records.' + typeKey + '.' + id)) {
				var payload = {};
				payload[EG.String.pluralize(typeKey)] = [_this.getRecordFromDatabase(typeKey, id, db)];
				return payload;
			} else {
				throw { status: 404, typeKey: typeKey, id: id };
			}
		});
	},

	/**
	 * @method serverFindMany
	 * @param {String} typeKey
	 * @param {String[]} ids
	 * @return {Promise}
	 * @protected
	 */
	serverFindMany: function(typeKey, ids) {
		var _this = this;

		return this.getDatabase().then(function(db) {
			var records = map.call(ids, function(id) {
				if (Em.get(db, 'records.' + typeKey + '.' + id)) {
					return _this.getRecordFromDatabase(typeKey, id, db);
				} else {
					throw { status: 404, typeKey: typeKey, id: id };
				}
			});

			var payload = {};
			payload[EG.String.pluralize(typeKey)] = records;
			return payload;
		});
	},

	/**
	 * @method serverFindAll
	 * @param {String} typeKey
	 * @return {Promise}
	 * @protected
	 */
	serverFindAll: function(typeKey) {
		var _this = this;

		return this.getDatabase().then(function(db) {
			var records = map.call(Em.keys(db.records[typeKey] || {}), function(id) {
				return _this.getRecordFromDatabase(typeKey, id, db);
			});

			var payload = {};
			payload[EG.String.pluralize(typeKey)] = records;
			return payload;
		});
	},

	/**
	 * @method serverUpdateRecord
	 * @param {String} typeKey
	 * @param {String} id
	 * @param {JSON[]} changes
	 * @return {Promise}
	 * @protected
	 */
	serverUpdateRecord: function(typeKey, id, changes) {
		var _this = this;

		return this.getDatabase().then(function(db) {
			db = _this.applyChangesToDatabase(typeKey, id, changes, db);
			return _this.setDatabase(db);
		});
	},

	/**
	 * @method serverDeleteRecord
	 * @param {String} typeKey
	 * @param {String} id
	 * @return {Promise}
	 * @protected
	 */
	serverDeleteRecord: function(typeKey, id) {
		var _this = this;
		var payload = null;

		return this.getDatabase().then(function(db) {
			if (db.records[typeKey]) {
				delete db.records[typeKey][id];
			}

			db.relationships = filter.call(db.relationships, function(r) {
				return !((r.t1 === typeKey && r.i1 === id) || (r.t2 === typeKey && r.i2 === id));
			});

			return _this.setDatabase(db).then(function() {
				return {
					meta: {
						deletedRecords: [{ type: typeKey, id: id }]
					}
				};
			});
		});
	},

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	/**
	 * Return a copy of the database from the storage location in JSON form.
	 *
	 * @method getDatabase
	 * @return {Promise} Resolves to the DB JSON
	 * @protected
	 */
	getDatabase: EG.abstractMethod('getDatabase'),

	/**
	 * Store the updated version of the database in the storage location.
	 *
	 * @method setDatabase
	 * @param {JSON} db
	 * @return {Promise} Resolves or rejects based on saving success
	 * @protected
	 */
	setDatabase: EG.abstractMethod('saveDatabase'),

	/**
	 * @method generateIdForRecord
	 * @param {String} typeKey
	 * @param {JSON} json
	 * @param {JSON} db
	 * @return {String}
	 * @protected
	 */
	generateIdForRecord: function(typeKey, json, db) {
		return EG.generateUUID();
	},

	/**
	 * Builds the record from the database, combining the relationships and attributes.
	 * This assumes that the record actually exists.
	 *
	 * @method getRecordFromDatabase
	 * @param {String} typeKey
	 * @param {String} id
	 * @param {JSON} db
	 * @return {JSON}
	 * @private
	 */
	getRecordFromDatabase: function(typeKey, id, db) {
		var model = this.get('store').modelFor(typeKey);
		var json = cloneJson(db.records[typeKey][id]);
		json.id = id;
		json.links = {};

		forEach.call(db.relationships, function(relationship) {
			var meta;

			if (relationship.t1 === typeKey && relationship.i1 === id) {
				meta = model.metaForRelationship(relationship.n1);

				if (meta.kind === EG.Model.HAS_ONE_KEY) {
					json.links[relationship.n1] = { type: relationship.t2, id: relationship.i2 };
				} else {
					json.links[relationship.n1] = json.links[relationship.n1] || [];
					json.links[relationship.n1].push({ type: relationship.t2, id: relationship.i2 });
				}
			} else if (relationship.t2 === typeKey && relationship.i2 === id) {
				meta = model.metaForRelationship(relationship.n2);

				if (meta.kind === EG.Model.HAS_ONE_KEY) {
					json.links[relationship.n2] = { type: relationship.t1, id: relationship.i1 };
				} else {
					json.links[relationship.n2] = json.links[relationship.n2] || [];
					json.links[relationship.n2].push({ type: relationship.t1, id: relationship.i1 });
				}
			}
		});

		model.eachRelationship(function(name, meta) {
			if (!json.links[name]) {
				if (meta.kind === EG.Model.HAS_ONE_KEY) {
					json.links[name] = null;
				} else {
					json.links[name] = [];
				}
			}
		});

		return json;
	},

	/**
	 * Takes a serialized record and splits it into attributes
	 * and relationships, then puts it in the database. It will
	 * replace any existing record with the same type and ID.
	 *
	 * @method putRecordInDatabase
	 * @param {String} typeKey
	 * @param {String} id
	 * @param {JSON} json
	 * @param {JSON} db
	 * @return {JSON} The updated DB
	 * @private
	 */
	putRecordInDatabase: function(typeKey, id, json, db) {
		var model = this.get('store').modelFor(typeKey);

		db.records[typeKey] = db.records[typeKey] || {};
		db.records[typeKey][id] = {};

		model.eachAttribute(function(name, meta) {
			db.records[typeKey][id][name] = json[name];
		});

		model.eachRelationship(function(name, meta) {
			if (meta.kind === EG.Model.HAS_ONE_KEY) {
				if (json.links[name]) {
					var relationship = {
						t1: typeKey, i1: id, n1: name,
						t2: json.links[name].type, i2: json.links[name].id, n2: meta.inverse
					};

					db = this.addRelationshipToDatabase(relationship, db);
				}
			} else {
				forEach.call(json.links[name], function(value) {
					var relationship = {
						t1: typeKey, i1: id, n1: name,
						t2: value.type, i2: value.id, n2: meta.inverse
					};

					db = this.addRelationshipToDatabase(relationship, db);
				}, this);
			}
		}, this);

		return db;
	},

	/**
	 * Applies a list of changes for a given record to the database.
	 *
	 * @method applyChangesToDatabase
	 * @param {String} typeKey
	 * @param {String} id
	 * @param {JSON[]} changes
	 * @param {JSON} db
	 * @return {JSON} The updated DB
	 * @private
	 */
	applyChangesToDatabase: function(typeKey, id, changes, db) {
		var model = this.get('store').modelFor(typeKey);

		forEach.call(changes, function(change) {
			switch (change.op) {
				case 'replace':
					break;
				case 'add':
					break;
				case 'remove':
					break;
			}
		});
	},

	/**
	 * @method addRelationshipToDatabase
	 * @param {JSON} relationship
	 * @param {JSON} db
	 * @return {JSON} The updated DB
	 * @private
	 */
	addRelationshipToDatabase: function(relationship, db) {

	},

	removeRelationshipFromDatabase: function() {

	},

	/**
	 * Gets all of the relationships that connect to the record given.
	 *
	 * @method getRelationshipsFor
	 * @param {String} typeKey
	 * @param {String} id
	 * @param {String} name
	 * @param {JSON} db
	 * @return {JSON[]} Relationships
	 * @private
	 */
	getRelationshipsFor: function(typeKey, id, name, db) {
		return filter.call(db.relationships, function(relationship) {
			return ((relationship.t1 === typeKey && relationship.i1 === id && relationship.n1 === name) ||
				(relationship.t2 === typeKey && relationship.i2 === id && relationship.n2 === name));
		});
	}

});<commit_msg>More updates to adapter.<commit_after>var Promise = Em.RSVP.Promise;
var map = Em.ArrayPolyfills.map;
var filter = Em.ArrayPolyfills.filter;
var forEach = Em.ArrayPolyfills.forEach;

var ADD_OP_NAME_REGEX = /^\/links\/([^/]+)/i;
var REMOVE_OP_REGEX = /^\/links\/([^/]+)\/.+/i;

function getRelationshipNameFromChangePath(path, op) {
	return path.match(op === 'add' ? ADD_OP_NAME_REGEX : REMOVE_OP_REGEX)[1];
}

function cloneJson(json) {
	return JSON.parse(JSON.stringify(json));
}

/**
 * This class acts as a base adapter for synchronous storage forms. Specifically,
 * the {{link-to-class 'LocalStorageAdapter'}} and {{link-to-class 'MemoryAdapter'}}
 * inherit from this class. This class will perform all of the work of updating data
 * and maintaining data integrity, subclasses need only implement the
 * {{link-to-method 'EmberGraphAdapter' 'getDatabase'}} and
 * {{link-to-method 'EmberGraphAdapter' 'setDatabase'}} methods to create a
 * fully-functioning adapter. This class works with data as a single JSON object
 * that takes the following form:
 *
 * ```json
 * {
 *     "records": {
 *          "type_key": {
 *              "record_id": {
 *                  "attr1": "value",
 *                  "attr2": 5
 *              }
 *          }
 *     },
 *     "relationships": [{
 *             "t1": "type_key",
 *             "i1": "id",
 *             "n1": "relationship_name",
 *             "t2": "type_key",
 *             "i2": "id",
 *             "n2": "relationship_name",
 *     }]
 * }
 * ```
 *
 * If you can store the JSON data, then this adapter will ensure complete
 * database integrity, since everything is done is single transactions.
 * You may also override some of the hooks and methods if you wish to
 * customize how the adapter saves or retrieves data.
 *
 * TODO: Should relationships be an array instead? What's the cost of a `splice`?
 *
 * @class EmberGraphAdapter
 * @extends Adapter
 * @category abstract
 */
EG.EmberGraphAdapter = EG.Adapter.extend({

	/**
	 * @property serializer
	 * @type JSONSerializer
	 * @protected
	 * @final
	 */
	serializer: null,

	init: function() {
		this._super();
		this.set('serializer', EG.JSONSerializer.create({
			polymorphicRelationships: true
		}));
	},

	createRecord: function(record) {
		var json = this.serialize(record, { requestType: 'createRecord', recordType: record.get('typeKey') });
		return this.serverCreateRecord(record.get('typeKey'), json);
	},

	findRecord: function(typeKey, id) {
		return this.serverFindRecord(typeKey, id);
	},

	findMany: function(typeKey, ids) {
		return this.serverFindMany(typeKey, ids);
	},

	findAll: function(typeKey) {
		return this.serverFindAll(typeKey);
	},

	findQuery: function() {
		return Promise.reject('LocalStorageAdapter doesn\'t implement `findQuery` by default.');
	},

	updateRecord: function(record) {
		var changes = this.serialize(record, { requestType: 'updateRecord', recordType: record.get('typeKey') });
		return this.serverUpdateRecord(record.get('typeKey'), record.get('id'), changes);
	},

	deleteRecord: function(record) {
		return this.serverDeleteRecord(record.get('typeKey'), record.get('id'));
	},

	serialize: function(record, options) {
		return this.get('serializer').serialize(record, options);
	},

	deserialize: function(payload, options) {
		return this.get('serializer').deserialize(payload, options);
	},

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	/**
	 * @method serverCreateRecord
	 * @param {String} typeKey
	 * @param {JSON} json
	 * @return {Promise}
	 * @protected
	 */
	serverCreateRecord: function(typeKey, json) {
		var _this = this;

		return this.getDatabase().then(function(db) {
			var id = _this.generateIdForRecord(typeKey, json, db);
			db = _this.putRecordInDatabase(typeKey, id, json, db);
			return _this.setDatabase(db);
		});
	},

	/**
	 * @method serverFindRecord
	 * @param {String} typeKey
	 * @param {String} id
	 * @return {Promise}
	 * @protected
	 */
	serverFindRecord: function(typeKey, id) {
		var _this = this;

		return this.getDatabase().then(function(db) {
			if (Em.get(db, 'records.' + typeKey + '.' + id)) {
				var payload = {};
				payload[EG.String.pluralize(typeKey)] = [_this.getRecordFromDatabase(typeKey, id, db)];
				return payload;
			} else {
				throw { status: 404, typeKey: typeKey, id: id };
			}
		});
	},

	/**
	 * @method serverFindMany
	 * @param {String} typeKey
	 * @param {String[]} ids
	 * @return {Promise}
	 * @protected
	 */
	serverFindMany: function(typeKey, ids) {
		var _this = this;

		return this.getDatabase().then(function(db) {
			var records = map.call(ids, function(id) {
				if (Em.get(db, 'records.' + typeKey + '.' + id)) {
					return _this.getRecordFromDatabase(typeKey, id, db);
				} else {
					throw { status: 404, typeKey: typeKey, id: id };
				}
			});

			var payload = {};
			payload[EG.String.pluralize(typeKey)] = records;
			return payload;
		});
	},

	/**
	 * @method serverFindAll
	 * @param {String} typeKey
	 * @return {Promise}
	 * @protected
	 */
	serverFindAll: function(typeKey) {
		var _this = this;

		return this.getDatabase().then(function(db) {
			var records = map.call(Em.keys(db.records[typeKey] || {}), function(id) {
				return _this.getRecordFromDatabase(typeKey, id, db);
			});

			var payload = {};
			payload[EG.String.pluralize(typeKey)] = records;
			return payload;
		});
	},

	/**
	 * @method serverUpdateRecord
	 * @param {String} typeKey
	 * @param {String} id
	 * @param {JSON[]} changes
	 * @return {Promise}
	 * @protected
	 */
	serverUpdateRecord: function(typeKey, id, changes) {
		var _this = this;

		return this.getDatabase().then(function(db) {
			db = _this.applyChangesToDatabase(typeKey, id, changes, db);
			return _this.setDatabase(db);
		});
	},

	/**
	 * @method serverDeleteRecord
	 * @param {String} typeKey
	 * @param {String} id
	 * @return {Promise}
	 * @protected
	 */
	serverDeleteRecord: function(typeKey, id) {
		var _this = this;
		var payload = null;

		return this.getDatabase().then(function(db) {
			if (db.records[typeKey]) {
				delete db.records[typeKey][id];
			}

			db.relationships = filter.call(db.relationships, function(r) {
				return !((r.t1 === typeKey && r.i1 === id) || (r.t2 === typeKey && r.i2 === id));
			});

			return _this.setDatabase(db).then(function() {
				return {
					meta: {
						deletedRecords: [{ type: typeKey, id: id }]
					}
				};
			});
		});
	},

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	/**
	 * Return a copy of the database from the storage location in JSON form.
	 *
	 * @method getDatabase
	 * @return {Promise} Resolves to the DB JSON
	 * @protected
	 */
	getDatabase: EG.abstractMethod('getDatabase'),

	/**
	 * Store the updated version of the database in the storage location.
	 *
	 * @method setDatabase
	 * @param {JSON} db
	 * @return {Promise} Resolves or rejects based on saving success
	 * @protected
	 */
	setDatabase: EG.abstractMethod('saveDatabase'),

	/**
	 * @method generateIdForRecord
	 * @param {String} typeKey
	 * @param {JSON} json
	 * @param {JSON} db
	 * @return {String}
	 * @protected
	 */
	generateIdForRecord: function(typeKey, json, db) {
		return EG.generateUUID();
	},

	/**
	 * Builds the record from the database, combining the relationships and attributes.
	 * This assumes that the record actually exists.
	 *
	 * @method getRecordFromDatabase
	 * @param {String} typeKey
	 * @param {String} id
	 * @param {JSON} db
	 * @return {JSON}
	 * @private
	 */
	getRecordFromDatabase: function(typeKey, id, db) {
		var model = this.get('store').modelFor(typeKey);
		var json = cloneJson(db.records[typeKey][id]);
		json.id = id;
		json.links = {};

		forEach.call(db.relationships, function(relationship) {
			var meta;

			if (relationship.t1 === typeKey && relationship.i1 === id) {
				meta = model.metaForRelationship(relationship.n1);

				if (meta.kind === EG.Model.HAS_ONE_KEY) {
					json.links[relationship.n1] = { type: relationship.t2, id: relationship.i2 };
				} else {
					json.links[relationship.n1] = json.links[relationship.n1] || [];
					json.links[relationship.n1].push({ type: relationship.t2, id: relationship.i2 });
				}
			} else if (relationship.t2 === typeKey && relationship.i2 === id) {
				meta = model.metaForRelationship(relationship.n2);

				if (meta.kind === EG.Model.HAS_ONE_KEY) {
					json.links[relationship.n2] = { type: relationship.t1, id: relationship.i1 };
				} else {
					json.links[relationship.n2] = json.links[relationship.n2] || [];
					json.links[relationship.n2].push({ type: relationship.t1, id: relationship.i1 });
				}
			}
		});

		model.eachRelationship(function(name, meta) {
			if (!json.links[name]) {
				if (meta.kind === EG.Model.HAS_ONE_KEY) {
					json.links[name] = null;
				} else {
					json.links[name] = [];
				}
			}
		});

		return json;
	},

	/**
	 * Takes a serialized record and splits it into attributes
	 * and relationships, then puts it in the database. It will
	 * replace any existing record with the same type and ID.
	 *
	 * @method putRecordInDatabase
	 * @param {String} typeKey
	 * @param {String} id
	 * @param {JSON} json
	 * @param {JSON} db
	 * @return {JSON} The updated DB
	 * @private
	 */
	putRecordInDatabase: function(typeKey, id, json, db) {
		var model = this.get('store').modelFor(typeKey);

		db.records[typeKey] = db.records[typeKey] || {};
		db.records[typeKey][id] = {};

		model.eachAttribute(function(name, meta) {
			db.records[typeKey][id][name] = json[name];
		});

		model.eachRelationship(function(name, meta) {
			if (meta.kind === EG.Model.HAS_ONE_KEY) {
				if (json.links[name]) {
					var relationship = {
						t1: typeKey, i1: id, n1: name,
						t2: json.links[name].type, i2: json.links[name].id, n2: meta.inverse
					};

					db = this.addRelationshipToDatabase(relationship, db);
				}
			} else {
				forEach.call(json.links[name], function(value) {
					var relationship = {
						t1: typeKey, i1: id, n1: name,
						t2: value.type, i2: value.id, n2: meta.inverse
					};

					db = this.addRelationshipToDatabase(relationship, db);
				}, this);
			}
		}, this);

		return db;
	},

	/**
	 * Applies a list of changes for a given record to the database.
	 *
	 * @method applyChangesToDatabase
	 * @param {String} typeKey
	 * @param {String} id
	 * @param {JSON[]} changes
	 * @param {JSON} db
	 * @return {JSON} The updated DB
	 * @private
	 */
	applyChangesToDatabase: function(typeKey, id, changes, db) {
		var model = this.get('store').modelFor(typeKey);

		forEach.call(changes, function(change) {
			switch (change.op) {
				case 'replace':
					if (EG.String.startsWith(change.path, '/links/')) {
						var hasOneName = change.path.substring('/links/'.length);

						if (change.value === null) {
							db = this.clearHasOneRelationshipInDatabase(typeKey, id, hasOneName, db);
						} else {
							var hasOneMeta = model.metaForRelationship(hasOneName);

							var replacementRelationship = {
								t1: typeKey, i1: id, n1: hasOneName,
								t2: change.value.type, i2: change.value.id, n2: hasOneMeta.inverse
							};

							db = this.setHasOneRelationshipInDatabase(replacementRelationship, db);
						}
					} else {
						var attrName = change.path.substring('/'.length);
						db.records[typeKey][id][attrName] = change.value;
					}
					break;
				case 'add':
				case 'remove':
					var hasManyName = getRelationshipNameFromChangePath(change.path, change.op);
					var hasManyMeta = model.metaForRelationship(hasManyName);

					var relationship = {
						t1: typeKey, i1: id, n1: hasManyName,
						t2: change.value.type, i2: change.value.id, n2: hasManyMeta.inverse
					};

					if (change.op === 'add') {
						db = this.addHasManyRelationshipToDatabase(relationship, db);
					} else {
						db = this.removeHasManyRelationshipFromDatabase(relationship, db);
					}
					break;
			}
		}, this);
	},

	/**
	 * @method addRelationshipToDatabase
	 * @param {JSON} relationship
	 * @param {JSON} db
	 * @return {JSON} The updated DB
	 * @private
	 */
	addHasManyRelationshipToDatabase: function(relationship, db) {

	},

	removeHasManyRelationshipFromDatabase: function(relationship, db) {

	},

	setHasOneRelationshipInDatabase: function(relationship, db) {

	},

	clearHasOneRelationshipInDatabase: function(typeKey, id, name, db) {

	},

	/**
	 * Gets all of the relationships that connect to the record given.
	 *
	 * @method getRelationshipsFor
	 * @param {String} typeKey
	 * @param {String} id
	 * @param {String} name
	 * @param {JSON} db
	 * @return {JSON[]} Relationships
	 * @private
	 */
	getRelationshipsFor: function(typeKey, id, name, db) {
		return filter.call(db.relationships, function(relationship) {
			return ((relationship.t1 === typeKey && relationship.i1 === id && relationship.n1 === name) ||
				(relationship.t2 === typeKey && relationship.i2 === id && relationship.n2 === name));
		});
	}

});<|endoftext|>


<commit_before>import {promisifyAll} from 'bluebird'

import appConfig      from '../../config/appConfig'

const {
    debug, isProduction, paths
} = appConfig

const fsPromised        = promisifyAll(require('fs'))

// default for 'develpment'
const DEFAULT           = {js: 'app.js', css: 'app.css'}

const APP_JS_PATTERN    = /^app-\w+\.js$/
const APP_CSS_PATTERN   = /^app-\w+\.css$/

const getAssetFilenamesAsync = async forceReadDir => {
    if (!isProduction && !forceReadDir) {
        return DEFAULT
    }

    try {
        const buildDirFiles = await fsPromised.readdirAsync(paths.build)

        return {
            js:     buildDirFiles.find(filename => APP_JS_PATTERN.test(filename)),
            css:    buildDirFiles.find(filename => APP_CSS_PATTERN.test(filename))
        }
    }
    catch (err) {
        if (debug) {
            console.log('[getAssetFilenamesAsync->catch]', err)
        }
        return DEFAULT
    }
}

let appAssetFilenameCache = null
const getAssetFilenamesCachedAsync = async forceReadDir => {
    if (appAssetFilenameCache) return appAssetFilenameCache

    appAssetFilenameCache = await getAssetFilenamesAsync(forceReadDir)

    return appAssetFilenameCache
}

export default getAssetFilenamesCachedAsync
<commit_msg>* removed gulp leftover tasks * added static task   * clean:static   * static:copyBuildFiles * refactored getAssetPathsAsync (now in /utils) * added /stack/static/getStaticIndexHtml({css, js})<commit_after>// refactor: shove into utils/lib!?
import {promisifyAll} from 'bluebird'

import appConfig      from '../../config/appConfig'

const {
    debug, isProduction, paths
} = appConfig

const fsPromised        = promisifyAll(require('fs'))

// default for 'develpment'
const DEFAULT           = {js: 'app.js', css: 'app.css'}

const APP_JS_PATTERN    = /^app-\w+\.js$/
const APP_CSS_PATTERN   = /^app-\w+\.css$/

const getAssetFilenamesAsync = async forceReadDir => {
    if (!isProduction && !forceReadDir) {
        return DEFAULT
    }

    try {
        const buildDirFiles = await fsPromised.readdirAsync(paths.build)

        return {
            js:     buildDirFiles.find(filename => APP_JS_PATTERN.test(filename)),
            css:    buildDirFiles.find(filename => APP_CSS_PATTERN.test(filename))
        }
    }
    catch (err) {
        if (debug) {
            console.log('[getAssetFilenamesAsync->catch]', err)
        }
        return DEFAULT
    }
}

let appAssetFilenameCache = null
const getAssetFilenamesCachedAsync = async forceReadDir => {
    if (appAssetFilenameCache) return appAssetFilenameCache

    appAssetFilenameCache = await getAssetFilenamesAsync(forceReadDir)

    return appAssetFilenameCache
}

export default getAssetFilenamesCachedAsync
<|endoftext|>


<commit_before>var PIXI = require('pixi.js/bin/pixi.dev.js');
var { vec2, Utils: { ARRAY_TYPE } } = require('p2');
var FloatRange = require('toxiclibsjs/lib/toxi/util/datatypes/FloatRange');
var ColorGradient = require('toxiclibsjs/lib/toxi/color/ColorGradient');
var animate = require('app/util/animate');

module.exports = (function() {
  'use strict';

  const ARPEGGIATOR_COLORS = require('app/data/arp-colors.json');
  const MAX_RADIUS = ARPEGGIATOR_COLORS.centerRadius;
  const SAFE_ZONE = 1.0 - ARPEGGIATOR_COLORS.safeZone;
  const SHADOW_RANGE = new FloatRange(0.18, 0);
  const CENTER_RANGE = new FloatRange(0, MAX_RADIUS);

  var distanceGradients = {};

  /**
   * Creates a new arpeggiator sliver.
   * @param {PIXI.DisplayObjectContainer} container - The PIXI container for this sliver.
   * @param {PIXI.Polygon} polygon - The PIXI polygon for this sliver.
   * @param {number} depth - The depth of this sliver.
   * @param {array} colorSets - Array of colors for this sliver.
   * @param {Boolean} hasLeftShadow - Does this sliver have a left shadow?
   * @param {Boolean} hasRightShadow - Does this sliver have a right shadow?
   * @param {number} highlight - The highlight for this sliver.
   * @constructor
   */
  return function Sliver(container, polygon, depth, colorSets, hasLeftShadow, hasRightShadow, highlight) {
    console.log(depth);

    if (depth === 3) {
      const SHADOW_LENGTH = 15;
    } else if (depth === 2) {
      const SHADOW_LENGTH = 30;
    } else if (depth === 1) {
      const SHADOW_LENGTH = 45;
    } else {
      const SHADOW_LENGTH = 0;
    }

    const [
      COLOR_A,
      COLOR_B,
      COLOR_C,
      COLOR_D,
      CENTER_COLOR
    ] = colorSets;

    const WINDOW_RANGE = new FloatRange(0, 360);

    const DEPTH_GRADIENT = new ColorGradient();
    DEPTH_GRADIENT.addColorAt(WINDOW_RANGE.getAt(0.0), COLOR_A);
    DEPTH_GRADIENT.addColorAt(WINDOW_RANGE.getAt(0.25), COLOR_B);
    DEPTH_GRADIENT.addColorAt(WINDOW_RANGE.getAt(0.5), COLOR_C);
    DEPTH_GRADIENT.addColorAt(WINDOW_RANGE.getAt(0.75), COLOR_D);
    DEPTH_GRADIENT.addColorAt(WINDOW_RANGE.getAt(1.0), COLOR_A);

    const COLORS = DEPTH_GRADIENT.calcGradient(0, 360).colors;

    var color;

    var startVec = vec2.create();
    var endVec = vec2.create();
    var outputVec = vec2.create();

    var center = new ARRAY_TYPE(2);
    center[0] = polygon.points[0];
    center[1] = polygon.points[1];

    var gfx = new PIXI.Graphics();
    container.addChild(gfx);

    var mask = new PIXI.Graphics();
    container.addChild(mask);
    container.mask = mask;

    var shadow = new PIXI.Graphics();

    gfx.addChild(shadow);

    // Init.
    updateCenterPoint(center[0], center[1]);

    /**
     * Render the shadow for each sliver.
     * @param {number} movement - Shadow movement.
     * @param {number} x1 - x pos 1 of the shadow.
     * @param {number} y1 - y pos 1 of the shadow.
     * @param {number} x2 - x pos 2 of the shadow.
     * @param {number} y2 - y pos 2 of the shadow.
     * @param {Boolean} reverse - Is it reversed?
     * @param {number} opacity - Opacity of the sliver.
     */
    function renderShadow(movement, x1, y1, x2, y2, reverse, opacity) {
      vec2.set(startVec, x1, y1);
      vec2.set(endVec, x2, y2);

      vec2.subtract(outputVec, endVec, startVec);
      vec2.normalize(outputVec, outputVec);
      vec2.scale(outputVec, outputVec, movement);
      vec2.rotate(outputVec, outputVec, (Math.PI / 2) * (reverse ? -1 : 1));

      vec2.add(startVec, outputVec, startVec);
      vec2.add(endVec, outputVec, endVec);

      shadow.lineStyle(1, 0x000000, opacity);
      shadow.moveTo(startVec[0], startVec[1]);
      shadow.lineTo(endVec[0], endVec[1]);
    }

    /**
     * The distance from the center influences the gradient colors.
     * @param {number} degrees = Gradient degrees.
     * @param {number} distance = Distance from the view center.
     */
    function getDistanceGradientColors(degrees, distance) {
      if (distanceGradients[degrees] && distanceGradients[degrees][distance]) {
        return distanceGradients[degrees][distance];
      }

      var distanceGradient = new ColorGradient();
      distanceGradient.addColorAt(CENTER_RANGE.getAt(0), COLORS[degrees]);
      if (SAFE_ZONE < 1.0) {
        distanceGradient.addColorAt(CENTER_RANGE.getAt(SAFE_ZONE), CENTER_COLOR);
      }
      distanceGradient.addColorAt(CENTER_RANGE.getAt(1.0), CENTER_COLOR);

      var dColors = distanceGradient.calcGradient(0, MAX_RADIUS + 1).colors;
      distanceGradients[degrees] = distanceGradients[degrees] || {};
      distanceGradients[degrees][distance] = dColors;

      return dColors;
    }

    /**
     * Update the sliver color.
     * @param {number} degrees = Gradient degrees.
     * @param {number} distance = Distance from the view center.
     */
    function updateColor(degrees, distance) {
      if (distance >= MAX_RADIUS) {
        color = COLORS[degrees].toInt();
        return;
      }

      var dColors = getDistanceGradientColors(degrees, distance);
      color = dColors[MAX_RADIUS-distance].toInt();
    }

    /**
     * Render sliver on RAF.
     */
    function render() {
      polygon.points[0] = center[0];
      polygon.points[1] = center[1];

      gfx.clear();
      gfx.beginFill(color);
      gfx.drawShape(polygon);
      mask.clear();
      mask.beginFill(color);
      mask.drawShape(polygon);

      shadow.clear();

      if (hasLeftShadow) {
        drawShadow(polygon.points[0], polygon.points[1], polygon.points[4], polygon.points[5], true);
      }

      if (hasRightShadow) {
        drawShadow(polygon.points[0], polygon.points[1], polygon.points[2], polygon.points[3]);
      }

      if (highlight > 0) {
        gfx.beginFill(0xffffff, highlight);
        gfx.drawShape(polygon);
      }
    }

    /**
     * Draw the sliver shadow.
     * @param {number} x1 = x position 1.
     * @param {number} y1 = y position 1.
     * @param {number} x2 = x position 2.
     * @param {number} y2 = y position 2.
     * @param {Boolean} reverse = reverse or not?
     */
    function drawShadow(x1, y1, x2, y2, reverse) {
      for (let i = 0; i < SHADOW_LENGTH; i++) {
        renderShadow(i, x1, y1, x2, y2, reverse, SHADOW_RANGE.getAt(1 - i / (SHADOW_LENGTH-1)));
      }
    }

    /**
     * Update the center point of the arpeggiator sliver.
     * @param {number} x = The x position.
     * @param {number} y = The y position.
     */
    function updateCenterPoint(x, y) {
      center[0] = x;
      center[1] = y;

      var angle = Math.atan2(-y, x);
      var degrees = angle * 180 / Math.PI;
      degrees = (degrees + 360) % 360;

      var distance = ~~Math.abs(Math.sqrt((x * x) + (y * y)));

      updateColor(~~degrees, distance);
      render();
    }

    /**
     * Update the sliver highlight.
     * @param {number} h = highlight.
     */
    function updateHighlight(h) {
      highlight = h;
      render();
    }

    /**
     * Pulse the highlight color.
     */
    var tweenInfo = { highlight: 0.2 };
    var tweenTarget = { highlight: 0, onUpdate: onUpdate };
    function pulse() {
      tweenInfo.highlight = 0.2;
      animate.to(tweenInfo, 0.5, tweenTarget);
    }

    /**
     * On update, update the highlight color.
     */
    function onUpdate() {
      updateHighlight(tweenInfo.highlight);
    }

    return {
      render: render,
      updateCenterPoint: updateCenterPoint,
      updateHighlight: updateHighlight,
      pulse: pulse
    };
  };
})();
<commit_msg>remove console logs<commit_after>var PIXI = require('pixi.js/bin/pixi.dev.js');
var { vec2, Utils: { ARRAY_TYPE } } = require('p2');
var FloatRange = require('toxiclibsjs/lib/toxi/util/datatypes/FloatRange');
var ColorGradient = require('toxiclibsjs/lib/toxi/color/ColorGradient');
var animate = require('app/util/animate');

module.exports = (function() {
  'use strict';

  const ARPEGGIATOR_COLORS = require('app/data/arp-colors.json');
  const MAX_RADIUS = ARPEGGIATOR_COLORS.centerRadius;
  const SAFE_ZONE = 1.0 - ARPEGGIATOR_COLORS.safeZone;
  const SHADOW_RANGE = new FloatRange(0.18, 0);
  const CENTER_RANGE = new FloatRange(0, MAX_RADIUS);

  var distanceGradients = {};

  /**
   * Creates a new arpeggiator sliver.
   * @param {PIXI.DisplayObjectContainer} container - The PIXI container for this sliver.
   * @param {PIXI.Polygon} polygon - The PIXI polygon for this sliver.
   * @param {number} depth - The depth of this sliver.
   * @param {array} colorSets - Array of colors for this sliver.
   * @param {Boolean} hasLeftShadow - Does this sliver have a left shadow?
   * @param {Boolean} hasRightShadow - Does this sliver have a right shadow?
   * @param {number} highlight - The highlight for this sliver.
   * @constructor
   */
  return function Sliver(container, polygon, depth, colorSets, hasLeftShadow, hasRightShadow, highlight) {

    if (depth === 3) {
      const SHADOW_LENGTH = 15;
    } else if (depth === 2) {
      const SHADOW_LENGTH = 30;
    } else if (depth === 1) {
      const SHADOW_LENGTH = 45;
    } else {
      const SHADOW_LENGTH = 0;
    }

    const [
      COLOR_A,
      COLOR_B,
      COLOR_C,
      COLOR_D,
      CENTER_COLOR
    ] = colorSets;

    const WINDOW_RANGE = new FloatRange(0, 360);

    const DEPTH_GRADIENT = new ColorGradient();
    DEPTH_GRADIENT.addColorAt(WINDOW_RANGE.getAt(0.0), COLOR_A);
    DEPTH_GRADIENT.addColorAt(WINDOW_RANGE.getAt(0.25), COLOR_B);
    DEPTH_GRADIENT.addColorAt(WINDOW_RANGE.getAt(0.5), COLOR_C);
    DEPTH_GRADIENT.addColorAt(WINDOW_RANGE.getAt(0.75), COLOR_D);
    DEPTH_GRADIENT.addColorAt(WINDOW_RANGE.getAt(1.0), COLOR_A);

    const COLORS = DEPTH_GRADIENT.calcGradient(0, 360).colors;

    var color;

    var startVec = vec2.create();
    var endVec = vec2.create();
    var outputVec = vec2.create();

    var center = new ARRAY_TYPE(2);
    center[0] = polygon.points[0];
    center[1] = polygon.points[1];

    var gfx = new PIXI.Graphics();
    container.addChild(gfx);

    var mask = new PIXI.Graphics();
    container.addChild(mask);
    container.mask = mask;

    var shadow = new PIXI.Graphics();

    gfx.addChild(shadow);

    // Init.
    updateCenterPoint(center[0], center[1]);

    /**
     * Render the shadow for each sliver.
     * @param {number} movement - Shadow movement.
     * @param {number} x1 - x pos 1 of the shadow.
     * @param {number} y1 - y pos 1 of the shadow.
     * @param {number} x2 - x pos 2 of the shadow.
     * @param {number} y2 - y pos 2 of the shadow.
     * @param {Boolean} reverse - Is it reversed?
     * @param {number} opacity - Opacity of the sliver.
     */
    function renderShadow(movement, x1, y1, x2, y2, reverse, opacity) {
      vec2.set(startVec, x1, y1);
      vec2.set(endVec, x2, y2);

      vec2.subtract(outputVec, endVec, startVec);
      vec2.normalize(outputVec, outputVec);
      vec2.scale(outputVec, outputVec, movement);
      vec2.rotate(outputVec, outputVec, (Math.PI / 2) * (reverse ? -1 : 1));

      vec2.add(startVec, outputVec, startVec);
      vec2.add(endVec, outputVec, endVec);

      shadow.lineStyle(1, 0x000000, opacity);
      shadow.moveTo(startVec[0], startVec[1]);
      shadow.lineTo(endVec[0], endVec[1]);
    }

    /**
     * The distance from the center influences the gradient colors.
     * @param {number} degrees = Gradient degrees.
     * @param {number} distance = Distance from the view center.
     */
    function getDistanceGradientColors(degrees, distance) {
      if (distanceGradients[degrees] && distanceGradients[degrees][distance]) {
        return distanceGradients[degrees][distance];
      }

      var distanceGradient = new ColorGradient();
      distanceGradient.addColorAt(CENTER_RANGE.getAt(0), COLORS[degrees]);
      if (SAFE_ZONE < 1.0) {
        distanceGradient.addColorAt(CENTER_RANGE.getAt(SAFE_ZONE), CENTER_COLOR);
      }
      distanceGradient.addColorAt(CENTER_RANGE.getAt(1.0), CENTER_COLOR);

      var dColors = distanceGradient.calcGradient(0, MAX_RADIUS + 1).colors;
      distanceGradients[degrees] = distanceGradients[degrees] || {};
      distanceGradients[degrees][distance] = dColors;

      return dColors;
    }

    /**
     * Update the sliver color.
     * @param {number} degrees = Gradient degrees.
     * @param {number} distance = Distance from the view center.
     */
    function updateColor(degrees, distance) {
      if (distance >= MAX_RADIUS) {
        color = COLORS[degrees].toInt();
        return;
      }

      var dColors = getDistanceGradientColors(degrees, distance);
      color = dColors[MAX_RADIUS-distance].toInt();
    }

    /**
     * Render sliver on RAF.
     */
    function render() {
      polygon.points[0] = center[0];
      polygon.points[1] = center[1];

      gfx.clear();
      gfx.beginFill(color);
      gfx.drawShape(polygon);
      mask.clear();
      mask.beginFill(color);
      mask.drawShape(polygon);

      shadow.clear();

      if (hasLeftShadow) {
        drawShadow(polygon.points[0], polygon.points[1], polygon.points[4], polygon.points[5], true);
      }

      if (hasRightShadow) {
        drawShadow(polygon.points[0], polygon.points[1], polygon.points[2], polygon.points[3]);
      }

      if (highlight > 0) {
        gfx.beginFill(0xffffff, highlight);
        gfx.drawShape(polygon);
      }
    }

    /**
     * Draw the sliver shadow.
     * @param {number} x1 = x position 1.
     * @param {number} y1 = y position 1.
     * @param {number} x2 = x position 2.
     * @param {number} y2 = y position 2.
     * @param {Boolean} reverse = reverse or not?
     */
    function drawShadow(x1, y1, x2, y2, reverse) {
      for (let i = 0; i < SHADOW_LENGTH; i++) {
        renderShadow(i, x1, y1, x2, y2, reverse, SHADOW_RANGE.getAt(1 - i / (SHADOW_LENGTH-1)));
      }
    }

    /**
     * Update the center point of the arpeggiator sliver.
     * @param {number} x = The x position.
     * @param {number} y = The y position.
     */
    function updateCenterPoint(x, y) {
      center[0] = x;
      center[1] = y;

      var angle = Math.atan2(-y, x);
      var degrees = angle * 180 / Math.PI;
      degrees = (degrees + 360) % 360;

      var distance = ~~Math.abs(Math.sqrt((x * x) + (y * y)));

      updateColor(~~degrees, distance);
      render();
    }

    /**
     * Update the sliver highlight.
     * @param {number} h = highlight.
     */
    function updateHighlight(h) {
      highlight = h;
      render();
    }

    /**
     * Pulse the highlight color.
     */
    var tweenInfo = { highlight: 0.2 };
    var tweenTarget = { highlight: 0, onUpdate: onUpdate };
    function pulse() {
      tweenInfo.highlight = 0.2;
      animate.to(tweenInfo, 0.5, tweenTarget);
    }

    /**
     * On update, update the highlight color.
     */
    function onUpdate() {
      updateHighlight(tweenInfo.highlight);
    }

    return {
      render: render,
      updateCenterPoint: updateCenterPoint,
      updateHighlight: updateHighlight,
      pulse: pulse
    };
  };
})();
<|endoftext|>


<commit_before>'use strict';
module.exports = {
  up: function(queryInterface, Sequelize) {
    return queryInterface.createTable('Activities', {
      id: {
        type: Sequelize.INTEGER,
        allowNull: false,
        unique: true,
        autoIncrement:true,
        primaryKey: true
      },
      name: {
        type: Sequelize.STRING,
        allowNull: false
      },
      description: {
        type: Sequelize.STRING
      },
      tags: {
        type: Sequelize.ARRAY(Sequelize.TEXT)
      },
      address: {
        type: Sequelize.STRING
      },
      latitude: {
        type: Sequelize.DECIMAL
      },
      longitude: {
        type: Sequelize.DECIMAL
      },
      temporary: {
        type: Sequelize.BOOLEAN
      },
      date_start: {
        type: Sequelize.DATE
      },
      date_end: {
        type: Sequelize.DATE
      },
      opening_hours: {
        type: Sequelize.ARRAY(Sequelize.INTEGER)
      },
      type: {
        type:   Sequelize.ENUM,
      },
      source: {
        type:   Sequelize.STRING,
      },
      idSource: {
        type:   Sequelize.DECIMAL,
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: function(queryInterface, Sequelize) {
    return queryInterface.dropTable('Activities');
  }
};
<commit_msg>Fix ENUM<commit_after>'use strict';
module.exports = {
  up: function(queryInterface, Sequelize) {
    return queryInterface.createTable('Activities', {
      id: {
        type: Sequelize.INTEGER,
        allowNull: false,
        unique: true,
        autoIncrement:true,
        primaryKey: true
      },
      name: {
        type: Sequelize.STRING,
        allowNull: false
      },
      description: {
        type: Sequelize.STRING
      },
      tags: {
        type: Sequelize.ARRAY(Sequelize.TEXT)
      },
      address: {
        type: Sequelize.STRING
      },
      latitude: {
        type: Sequelize.DECIMAL
      },
      longitude: {
        type: Sequelize.DECIMAL
      },
      temporary: {
        type: Sequelize.BOOLEAN
      },
      date_start: {
        type: Sequelize.DATE
      },
      date_end: {
        type: Sequelize.DATE
      },
      opening_hours: {
        type: Sequelize.ARRAY(Sequelize.INTEGER)
      },
      type: {
        type:   Sequelize.ENUM,
        values: ['PATRIMOINE_CULTUREL', "RESTAURATION", "DEGUSTATION", "COMMERCE_ET_SERVICE", "HEBERGEMENT_LOCATIF", "HEBERGEMENT_COLLECTIF", "HOTELLERIE"],
      },
      source: {
        type:   Sequelize.STRING,
      },
      idSource: {
        type:   Sequelize.DECIMAL,
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: function(queryInterface, Sequelize) {
    return queryInterface.dropTable('Activities');
  }
};
<|endoftext|>


<commit_before>// ==UserScript==
// @name         CheckItForMe
// @version      0.20
// @match        https://scrap.tf/raffles
// @match        https://scrap.tf/raffles/ending
// @require      https://code.jquery.com/jquery-2.2.4.min.js#sha256=BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=
// @updateURL    https://raw.githubusercontent.com/GuilloOme/CheckThisForMe/master/checkItForMe.js
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    var RELOAD_DELAY = 10,
        ENTERING_DELAY = 2.5;

    var todoRaffleList = [],
        raffleIndex = 0,
        interval = randomInterval(RELOAD_DELAY);

    $(document).ready(function() {
        console.info('Bot: Started');

        if(parseInt($('.user-notices-count').html()) > 0){
            console.info('Bot: There is new message(s)!');
            $('link[rel*="icon"]').remove();
            // green icon
            $('head').append('<link href="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG0QYXBtEGTQbRBk8G0QZPBtEGTwbRBk8G0QZPBtEGTwbRBkkG0QYKAAAAAAAAAAAAAAAAAAAAAAAAAAAG0QYQBtEG5wbRBv8G0Qb/BtEG/wbRBv8G0Qb/BtEG/wbRBv8G0Qb/BtEGwAXRBQAAAAAAAAAAAAAAAAAAAAAABtEGOQbRBv/J9cn/z/bP/6bvpv9/53//nO2c/53tnf/u/O7/l+yX/wbRBvkF0QUHAAAAAAAAAAAAAAAAAAAAAAbRBjoG0Qb/2/jb/yjXKP8G0Qb/EdMR/0zeTP9C3EL/6vvq/6TupP8G0Qb5BdEFCAAAAAAAAAAAAAAAAAAAAAAG0QY6BtEG/9v42/8l1yX/BtEG/xHTEf9M3kz/Nto2/7Xxtf+k7qT/BtEG+QXRBQgAAAAAAAAAAAAAAAAAAAAABtEGOgbRBv/b+Nv/jeqN/xjUGP8S0xL/TN5M/zfaN//F9MX/pO6k/wbRBvkF0QUIAAAAAAAAAAAAAAAAAAAAAAbRBjoG0Qb/2/jb//////+B6IH/FNQU/8X0xf+9873/9v32/6TupP8G0Qb5BdEFCAAAAAAAAAAAAAAAAAAAAAAG0QY6BtEG/9v42///////6vvq/w/TD//z/fP///////////+k7qT/BtEG+QXRBQgAAAAAAAAAAAAAAAAAAAAABtEGOQbRBv/T99P/+f75//n++f+b7Zv/9f31//n++f/5/vn/nu2e/wbRBvkF0QUIAAAAAAAAAAAAAAAAAAAAAAbRBhMG0QbtB9EH/wjRCP8I0Qj/CNEI/wjRCP8I0Qj/CNEI/wfRB/8G0QbHBdEFAQAAAAAAAAAAAAAAAAAAAAAAAAAABtEGHwbRBlsG0QZdBtEGXQbRBl0G0QZdBtEGXQbRBl0G0QZWBtEGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAA4AcAAMADAADAAwAAwAMAAMADAADAAwAAwAMAAMADAADAAwAAwAMAAOAHAAD//wAA//8AAA==" rel="icon" type="image/x-icon">');
            //warning icon: $('head').append('<link href="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAAAAAADAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABwAAAAcAAAADAAAAmwAHCcIACQvCAAkLwgAJC8IACQvCAAkLwgAJDMIACQzCAAkMwgAJDMIACQzCAAkMwgAJDMIACArDAAAAmwAAAGsAbIf4AMz//wDM//8AzP//AMz//wDM//8Auun/AK/b/wDM//8AzP//AMz//wDM//8AzP//AGyH+QAAAGsAAAAZAA0QtgC24/8AzP//AMz//wDM//8AzP//Ai86/wMLDf8AwvP/AMz//wDM//8AzP//ALfk/wAOEbgAAAAZAAAAAAAAAFAAT2LqAMz//wDM//8AzP//AMz//wF/n/8CYXn/AMv+/wDM//8AzP//AMz//wBRZesAAABSAAAAAAAAAAAAAAAIAAIDngCcw/8AzP//AMz//wDM//8AeZj/AHSR/wDM//8AzP//AMz//wCfx/8AAwShAAAACQAAAAAAAAAAAAAAAAAAADgAMDzYAMr8/wDM//8AzP//ACQt/wAbIv8AzP//AMz//wDL/f8ANEHbAAAAOwAAAAAAAAAAAAAAAAAAAAAAAAABAAAAfQB9nP4AzP//AMz//wAcI/8AFBj/AMz//wDM//8AgqP+AAAAggAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACMAGB7DAL/v/wDM//8AFBn/AA0Q/wDL/v8AwvL/ABwjyAAAACcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXwBgePMAy/7/AA0Q/wAHCf8Ayfv/AGaA9gAAAGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEABwisAKvV/wAPE/8ACgz/AK7Z/wAKDLIAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARgBCUuIAn8f/AJzD/wBLXecAAABNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAGRAI+y/wCZv/8AAQKbAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALwAlLtAALTnWAAAANgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABvAAAAeQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAACAAQAAwAMAAMADAADgBwAA8A8AAPAPAAD4HwAA+B8AAPw/AAD8PwAA/n8AAA==" rel="icon" type="image/x-icon">');
        }

        scanHash();
    });

    function scanHash() {
        var hash = window.location.hash,
            url = window.location.href,
            baseUrl = /https:\/\/scrap.tf\/raffles/;

        if (url.match(baseUrl) && isThereNewRaffles()) {
            scanRaffles();
        } else {
            console.info('Bot: Nothing to do, waiting ' + (interval / 1000) + ' sec before reloading…');

            setTimeout(function() {
                location.reload();
            }, interval);
        }

    }

    function scanRaffles() {
        //console.info('Bot: Loading all the raffles…');

        $.when(loadAllRaffles()).then(function() {
            var activePanel = $('div.panel');
            $(activePanel[activePanel.length - 1]).find('div.panel-raffle').each(function(id, item) {
                if ($(item).css('opacity') === '1') {
                    todoRaffleList.push($(item).find('div.raffle-name > a').attr('href'));
                }
            });

            if (todoRaffleList.length > 0) {
                //console.info('Bot: Start entering raffles.');

                $.when(enterRaffles()).then(function() {
                    //console.info('Bot: Done entering raffles, reloading…');
                    location.reload();
                });
            } else {
                console.info('Bot: No raffle to enter, waiting ' + (interval / 1000) + ' sec before reloading…');
                setTimeout(function() {
                    location.reload();
                }, interval);
            }
        });

    }

    function enterRaffles() {
        var deferred = jQuery.Deferred();

        function joinRaffle(url) {

            var id, hash;

            ScrapTF.Raffles.EnterRaffle = function(idArg, hashArg) {
                id = idArg;
                hash = hashArg;
            }

            $.get(url, function(responseData) {

                var request,
                    raffleDeferred = jQuery.Deferred(),
                    raffleKey = $(responseData).find("#raffle-key").val(),
                    enterButton = $(responseData).find('button#raffle-enter');

                if (enterButton.length > 0 && $(responseData).find('button#raffle-enter>i18n').html() === 'Enter Raffle') {
                    console.info('Bot: Entrering raffle: ' + (raffleIndex + 1) + '/' + todoRaffleList.length);

                    $(responseData).find('button#raffle-enter').click();

                    request = {
                        raffle: id,
                        captcha: '',
                        rafflekey: raffleKey,
                        password: '',
                        hash: hash
                    };

                    ScrapTF.Ajax('viewraffle/EnterRaffle', {
                        raffle: id,
                        captcha: '',
                        rafflekey: raffleKey,
                        password: '',
                        hash: hash
                    }, function() {
                        console.info('Bot: Done entering raffle');

                        raffleDeferred.resolve();
                    }, function() {
                        console.warn('Bot: Error when entering raffle: ' + (raffleIndex + 1) + '/' + todoRaffleList.length);

                        raffleDeferred.resolve();
                    });

                } else {
                    console.warn('Bot: Can\'t enter raffle: ' + (raffleIndex + 1) + '/' + todoRaffleList.length);
                    raffleDeferred.resolve();
                }

                $.when(raffleDeferred.promise()).then(function() {
                    raffleIndex++;
                    if (raffleIndex < todoRaffleList.length) {
                        setTimeout(function() {
                            joinRaffle(todoRaffleList[raffleIndex]);
                        }, randomInterval(ENTERING_DELAY));
                    } else {
                        deferred.resolve();
                    }
                });

            });

            /* var currentChildFrame = window.open(url);

             $(currentChildFrame.document).ready(function() {
             var waitInterval;
             console.log('Bot: Entrering raffle: ' + (raffleIndex + 1) + '/' + todoRaffleList.length);

             waitInterval = setInterval(function() {
             console.log('Bot: Waiting confirmation…');

             if ($(currentChildFrame.document).find('button#raffle-enter>i18n').html() === 'Leave Raffle' || $(currentChildFrame.document).find('div.alert-error').length > 0) {

             console.log('Bot: Closing raffle window');

             currentChildFrame.close();

             raffleIndex++;
             if (raffleIndex < todoRaffleList.length) {
             setTimeout(function() {
             joinRaffle(todoRaffleList[raffleIndex]);
             }, randomInterval(ENTERING_DELAY));
             } else {
             deferred.resolve();
             }

             clearInterval(waitInterval);

             } else if ($(currentChildFrame.document).find('button#raffle-enter>i18n').html() === 'Enter Raffle') {

             $(currentChildFrame.document).find('button#raffle-enter').trigger('click');

             }
             }, randomInterval(ENTERING_DELAY));

             });*/

        }

        joinRaffle(todoRaffleList[raffleIndex]);

        return deferred.promise();
    }

    function isThereNewRaffles() {

        var value = $('div.panel-body>div.text-center>i18n>var').text(),
            ar = value.match(/[0-9]+/gi),
            raffleToEnterNumber = (parseInt(ar[1]) - parseInt(ar[0]));

        console.info('Bot: ' + value + ', There is ' + raffleToEnterNumber + ' raffle(s) to enter.');

        return (ar.length > 1 && raffleToEnterNumber > 0);
    }

    function loadAllRaffles() {
        var deferred = jQuery.Deferred();

        var loadInterval = setInterval(function() {

            if (!ScrapTF.Raffles.Pagination.isDone) {
                ScrapTF.Raffles.Pagination.LoadNext();
            } else {
                deferred.resolve();
                clearInterval(loadInterval);
            }

        }, randomInterval());

        return deferred.promise();

    }

    function randomInterval(delay) {
        if (!delay) {
            delay = 0.5;
        }
        return Math.floor(delay * 1000) + Math.floor(Math.random() * (delay * 1000));
    }

})
();
<commit_msg>make the script stop if there is captcha<commit_after>// ==UserScript==
// @name         CheckItForMe
// @version      0.21
// @match        https://scrap.tf/raffles
// @match        https://scrap.tf/raffles/ending
// @require      https://code.jquery.com/jquery-2.2.4.min.js#sha256=BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=
// @updateURL    https://raw.githubusercontent.com/GuilloOme/CheckThisForMe/master/checkItForMe.js
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    var RELOAD_DELAY = 10,
        ENTERING_DELAY = 2.5;

    var todoRaffleList = [],
        raffleIndex = 0,
        interval = randomInterval(RELOAD_DELAY);

    $(document).ready(function() {
        console.info('Bot: Started');

        if(parseInt($('.user-notices-count').html()) > 0){
            console.info('Bot: There is new message(s)!');
            showIcon('Ok');
        }

        scanHash();
    });

    function scanHash() {
        var hash = window.location.hash,
            url = window.location.href,
            baseUrl = /https:\/\/scrap.tf\/raffles/;

        if (url.match(baseUrl) && isThereNewRaffles()) {
            scanRaffles();
        } else {
            console.info('Bot: Nothing to do, waiting ' + (interval / 1000) + ' sec before reloading…');

            setTimeout(function() {
                location.reload();
            }, interval);
        }

    }

    function scanRaffles() {
        //console.info('Bot: Loading all the raffles…');

        $.when(loadAllRaffles()).then(function() {
            var activePanel = $('div.panel');
            $(activePanel[activePanel.length - 1]).find('div.panel-raffle').each(function(id, item) {
                if ($(item).css('opacity') === '1') {
                    todoRaffleList.push($(item).find('div.raffle-name > a').attr('href'));
                }
            });

            if (todoRaffleList.length > 0) {
                //console.info('Bot: Start entering raffles.');

                $.when(enterRaffles()).then(function() {
                    //console.info('Bot: Done entering raffles, reloading…');
                    location.reload();
                });
            } else {
                console.info('Bot: No raffle to enter, waiting ' + (interval / 1000) + ' sec before reloading…');
                setTimeout(function() {
                    location.reload();
                }, interval);
            }
        });

    }

    function enterRaffles() {
        var deferred = jQuery.Deferred();

        function joinRaffle(url) {

            var id, hash;

            ScrapTF.Raffles.EnterRaffle = function(idArg, hashArg) {
                id = idArg;
                hash = hashArg;
            }

            $.get(url, function(responseData) {

                var request,
                    raffleDeferred = jQuery.Deferred(),
                    raffleKey = $(responseData).find("#raffle-key").val(),
                    enterButton = $(responseData).find('button#raffle-enter');

                if (enterButton.length > 0 && $(responseData).find('button#raffle-enter>i18n').html() === 'Enter Raffle') {
                    console.info('Bot: Entrering raffle: ' + (raffleIndex + 1) + '/' + todoRaffleList.length);

                    $(responseData).find('button#raffle-enter').click();

                    request = {
                        raffle: id,
                        captcha: '',
                        rafflekey: raffleKey,
                        password: '',
                        hash: hash
                    };

                    ScrapTF.Ajax('viewraffle/EnterRaffle', {
                        raffle: id,
                        captcha: '',
                        rafflekey: raffleKey,
                        password: '',
                        hash: hash
                    }, function() {
                        console.info('Bot: Done entering raffle');

                        raffleDeferred.resolve();
                    }, function(data) {
                        console.warn('Bot: Error when entering raffle: ' + (raffleIndex + 1) + '/' + todoRaffleList.length);

                        if (data.captcha) {
                            showIcon('Warning');
                            raffleDeferred.reject();
                        }else{
                            raffleDeferred.resolve();
                        }
                    });

                } else {
                    console.warn('Bot: Can\'t enter raffle: ' + (raffleIndex + 1) + '/' + todoRaffleList.length);
                    raffleDeferred.resolve();
                }

                $.when(raffleDeferred.promise()).then(function() {
                    raffleIndex++;
                    if (raffleIndex < todoRaffleList.length) {
                        setTimeout(function() {
                            joinRaffle(todoRaffleList[raffleIndex]);
                        }, randomInterval(ENTERING_DELAY));
                    } else {
                        deferred.resolve();
                    }
                });

            });

        }

        joinRaffle(todoRaffleList[raffleIndex]);

        return deferred.promise();
    }

    function isThereNewRaffles() {

        var value = $('div.panel-body>div.text-center>i18n>var').text(),
            ar = value.match(/[0-9]+/gi),
            raffleToEnterNumber = (parseInt(ar[1]) - parseInt(ar[0]));

        console.info('Bot: ' + value + ', There is ' + raffleToEnterNumber + ' raffle(s) to enter.');

        return (ar.length > 1 && raffleToEnterNumber > 0);
    }

    function loadAllRaffles() {
        var deferred = jQuery.Deferred();

        var loadInterval = setInterval(function() {

            if (!ScrapTF.Raffles.Pagination.isDone) {
                ScrapTF.Raffles.Pagination.LoadNext();
            } else {
                deferred.resolve();
                clearInterval(loadInterval);
            }

        }, randomInterval());

        return deferred.promise();

    }

    function randomInterval(delay) {
        if (!delay) {
            delay = 0.5;
        }
        return Math.floor(delay * 1000) + Math.floor(Math.random() * (delay * 1000));
    }

    function showIcon(iconType){
        var iconLink ='';

        if(iconType === 'Ok'){
            iconLink = '<link href="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG0QYXBtEGTQbRBk8G0QZPBtEGTwbRBk8G0QZPBtEGTwbRBkkG0QYKAAAAAAAAAAAAAAAAAAAAAAAAAAAG0QYQBtEG5wbRBv8G0Qb/BtEG/wbRBv8G0Qb/BtEG/wbRBv8G0Qb/BtEGwAXRBQAAAAAAAAAAAAAAAAAAAAAABtEGOQbRBv/J9cn/z/bP/6bvpv9/53//nO2c/53tnf/u/O7/l+yX/wbRBvkF0QUHAAAAAAAAAAAAAAAAAAAAAAbRBjoG0Qb/2/jb/yjXKP8G0Qb/EdMR/0zeTP9C3EL/6vvq/6TupP8G0Qb5BdEFCAAAAAAAAAAAAAAAAAAAAAAG0QY6BtEG/9v42/8l1yX/BtEG/xHTEf9M3kz/Nto2/7Xxtf+k7qT/BtEG+QXRBQgAAAAAAAAAAAAAAAAAAAAABtEGOgbRBv/b+Nv/jeqN/xjUGP8S0xL/TN5M/zfaN//F9MX/pO6k/wbRBvkF0QUIAAAAAAAAAAAAAAAAAAAAAAbRBjoG0Qb/2/jb//////+B6IH/FNQU/8X0xf+9873/9v32/6TupP8G0Qb5BdEFCAAAAAAAAAAAAAAAAAAAAAAG0QY6BtEG/9v42///////6vvq/w/TD//z/fP///////////+k7qT/BtEG+QXRBQgAAAAAAAAAAAAAAAAAAAAABtEGOQbRBv/T99P/+f75//n++f+b7Zv/9f31//n++f/5/vn/nu2e/wbRBvkF0QUIAAAAAAAAAAAAAAAAAAAAAAbRBhMG0QbtB9EH/wjRCP8I0Qj/CNEI/wjRCP8I0Qj/CNEI/wfRB/8G0QbHBdEFAQAAAAAAAAAAAAAAAAAAAAAAAAAABtEGHwbRBlsG0QZdBtEGXQbRBl0G0QZdBtEGXQbRBl0G0QZWBtEGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAA4AcAAMADAADAAwAAwAMAAMADAADAAwAAwAMAAMADAADAAwAAwAMAAOAHAAD//wAA//8AAA==" rel="icon" type="image/x-icon">';
        } else if(iconType === 'Warning'){
            iconLink = '<link href="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAAAAAADAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABwAAAAcAAAADAAAAmwAHCcIACQvCAAkLwgAJC8IACQvCAAkLwgAJDMIACQzCAAkMwgAJDMIACQzCAAkMwgAJDMIACArDAAAAmwAAAGsAbIf4AMz//wDM//8AzP//AMz//wDM//8Auun/AK/b/wDM//8AzP//AMz//wDM//8AzP//AGyH+QAAAGsAAAAZAA0QtgC24/8AzP//AMz//wDM//8AzP//Ai86/wMLDf8AwvP/AMz//wDM//8AzP//ALfk/wAOEbgAAAAZAAAAAAAAAFAAT2LqAMz//wDM//8AzP//AMz//wF/n/8CYXn/AMv+/wDM//8AzP//AMz//wBRZesAAABSAAAAAAAAAAAAAAAIAAIDngCcw/8AzP//AMz//wDM//8AeZj/AHSR/wDM//8AzP//AMz//wCfx/8AAwShAAAACQAAAAAAAAAAAAAAAAAAADgAMDzYAMr8/wDM//8AzP//ACQt/wAbIv8AzP//AMz//wDL/f8ANEHbAAAAOwAAAAAAAAAAAAAAAAAAAAAAAAABAAAAfQB9nP4AzP//AMz//wAcI/8AFBj/AMz//wDM//8AgqP+AAAAggAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACMAGB7DAL/v/wDM//8AFBn/AA0Q/wDL/v8AwvL/ABwjyAAAACcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXwBgePMAy/7/AA0Q/wAHCf8Ayfv/AGaA9gAAAGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEABwisAKvV/wAPE/8ACgz/AK7Z/wAKDLIAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARgBCUuIAn8f/AJzD/wBLXecAAABNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAGRAI+y/wCZv/8AAQKbAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALwAlLtAALTnWAAAANgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABvAAAAeQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAACAAQAAwAMAAMADAADgBwAA8A8AAPAPAAD4HwAA+B8AAPw/AAD8PwAA/n8AAA==" rel="icon" type="image/x-icon">';
        }

        $('link[rel*="icon"]').remove();
        $('head').append(iconLink);

    }

})
();
<|endoftext|>


<commit_before>
var $ = document.querySelector.bind(document)

var state = {
  theme,
  raw,
  content,
  compiler,
  html: '',
  markdown: '',
  toc: '',
  interval: null,
  ms: 1000,
}

chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
  if (req.message === 'reload') {
    location.reload(true)
  }
  else if (req.message === 'theme') {
    state.theme = req.theme
    m.redraw()
  }
  else if (req.message === 'raw') {
    state.raw = req.raw
    m.redraw()
  }
  else if (req.message === 'autoreload') {
    clearInterval(state.interval)
  }
})

var oncreate = {
  markdown: () => {
    scroll.body()
  },
  html: () => {
    scroll.body()

    if (state.content.toc && !state.toc) {
      state.toc = toc()
      m.redraw()
    }

    setTimeout(() => Prism.highlightAll(), 20)

    anchors()
  },
  toc: () => {
    scroll.toc()
  }
}

function mount () {
  $('pre').style.display = 'none'
  var md = $('pre').innerText

  m.mount($('body'), {
    oninit: () => {
      state.markdown = md
      chrome.runtime.sendMessage({
        message: 'markdown',
        compiler: state.compiler,
        markdown: state.markdown
      }, (res) => {
        state.html = state.content.emoji ? emojinator(res.html) : res.html
        m.redraw()
      })
    },
    view: () => {
      var dom = []

      if (state.raw) {
        dom.push(m('pre#_markdown', {oncreate: oncreate.markdown}, state.markdown))
        $('body').classList.remove('_toc-left', '_toc-right')
      }
      else {
        if (state.theme) {
          dom.push(m('link#_theme', {
            rel: 'stylesheet', type: 'text/css',
            href: state.theme.url,
          }))
        }
        if (state.html) {
          dom.push(m('#_html', {oncreate: oncreate.html,
            class: /github(-dark)?/.test(state.theme.name) ? 'markdown-body' : 'markdown-theme'},
            m.trust(state.html)
          ))
          if (state.content.toc && state.toc) {
            dom.push(m('#_toc', {oncreate: oncreate.toc},
              m.trust(state.toc)
            ))
            $('body').classList.add('_toc-left')
          }
          if (state.content.mathjax) {
            dom.push(m('script', {type: 'text/x-mathjax-config'}, mathjax))
            dom.push(m('script', {
              src: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js'
            }))
          }
        }
      }

      return (dom.length ? dom : m('div'))
    }
  })
}

var scroll = (() => {
  function race (done) {
    var images = Array.from(document.querySelectorAll('img'))
    if (!images.length) {
      done()
    }
    var loaded = 0
    images.forEach((img) => {
      img.addEventListener('load', () => {
        if (++loaded === images.length) {
          done()
        }
      }, {once: true})
    })
    setTimeout(done, 100)
  }
  function debounce (container, done) {
    var listener = /body/i.test(container.nodeName) ? window : container
    var timeout = null
    listener.addEventListener('scroll', () => {
      clearTimeout(timeout)
      timeout = setTimeout(done, 100)
    })
  }
  function listen (container, prefix) {
    var key = prefix + location.origin + location.pathname
    try {
      container.scrollTop = parseInt(localStorage.getItem(key))
      debounce(container, () => {
        localStorage.setItem(key, container.scrollTop)
      })
    }
    catch (err) {
      chrome.storage.local.get(key, (res) => {
        container.scrollTop = parseInt(res[key])
      })
      debounce(container, () => {
        chrome.storage.local.set({[key]: container.scrollTop})
      })
    }
  }
  return {
    body: () => {
      var loaded
      race(() => {
        if (!loaded) {
          loaded = true
          if (state.content.scroll) {
            listen($('body'), 'md-')
          }
          else if (location.hash && $(location.hash)) {
            $('body').scrollTop = $(location.hash).offsetTop
          }
        }
      })
    },
    toc: () => {
      listen($('#_toc'), 'md-toc-')
    }
  }
})()

function anchors () {
  Array.from($('#_html').childNodes)
  .filter((node) => /h[1-6]/i.test(node.tagName))
  .forEach((node) => {
    var a = document.createElement('a')
    a.className = 'anchor'
    a.name = node.id
    a.href = '#' + node.id
    a.innerHTML = ''
    node.prepend(a)
  })
}

var toc = (
  link = (header) => '' + header.title + '') =>
  Array.from($('#_html').childNodes)
  .filter((node) => /h[1-6]/i.test(node.tagName))
  .map((node) => ({
    id: node.getAttribute('id'),
    level: parseInt(node.tagName.replace('H', '')),
    title: node.innerText
  }))
  .reduce((html, header) => {
    html += '
'.repeat(header.level)
    html += link(header)
    html += '
'.repeat(header.level)
    return html
  }, '')

if (document.readyState === 'complete') {
  mount()
}
else {
  var timeout = setInterval(() => {
    if (document.readyState === 'complete') {
      clearInterval(timeout)
      mount()
    }
  }, 0)
}

if (state.content.autoreload) {
  ;(() => {
    var initial = ''

    var xhr = new XMLHttpRequest()
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (!initial) {
          initial = xhr.responseText
        }
        else if (initial !== xhr.responseText) {
          location.reload(true)
        }
      }
    }

    var get = () => {
      xhr.open('GET', location.href + '?preventCache=' + Date.now(), true)
      try {
        xhr.send()
      }
      catch (err) {
        console.error(err)
        clearInterval(state.interval)
      }
    }

    get()
    state.interval = setInterval(get, state.ms)
  })()
}

var mathjax = `
  // TeX-AMS_HTML
  MathJax.Hub.Config({
    jax: [
      'input/TeX',
      'output/HTML-CSS',
      'output/PreviewHTML',
    ],
    extensions: [
      'tex2jax.js',
      'AssistiveMML.js',
      'a11y/accessibility-menu.js',
    ],
    TeX: {
      extensions: [
        'AMSmath.js',
        'AMSsymbols.js',
        'noErrors.js',
        'noUndefined.js',
      ]
    },
    tex2jax: {
      inlineMath: [
        ['$', '$'],
        ['\\\\(', '\\\\)'],
      ],
      displayMath: [
        ['$$', '$$'],
        ['\\\\[', '\\\\]'],
      ],
      processEscapes: true
    },
    showMathMenu: false,
    showProcessingMessages: false,
    messageStyle: 'none',
    skipStartupTypeset: true, // disable initial rendering
    positionToHash: false
  })
  // set specific container to render, can be delayed too
  MathJax.Hub.Queue(
    ['Typeset', MathJax.Hub, '_html']
  )
`
<commit_msg>Escape tag delimiters in toc<commit_after>
var $ = document.querySelector.bind(document)

var state = {
  theme,
  raw,
  content,
  compiler,
  html: '',
  markdown: '',
  toc: '',
  interval: null,
  ms: 1000,
}

chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
  if (req.message === 'reload') {
    location.reload(true)
  }
  else if (req.message === 'theme') {
    state.theme = req.theme
    m.redraw()
  }
  else if (req.message === 'raw') {
    state.raw = req.raw
    m.redraw()
  }
  else if (req.message === 'autoreload') {
    clearInterval(state.interval)
  }
})

var oncreate = {
  markdown: () => {
    scroll.body()
  },
  html: () => {
    scroll.body()

    if (state.content.toc && !state.toc) {
      state.toc = toc()
      m.redraw()
    }

    setTimeout(() => Prism.highlightAll(), 20)

    anchors()
  },
  toc: () => {
    scroll.toc()
  }
}

function mount () {
  $('pre').style.display = 'none'
  var md = $('pre').innerText

  m.mount($('body'), {
    oninit: () => {
      state.markdown = md
      chrome.runtime.sendMessage({
        message: 'markdown',
        compiler: state.compiler,
        markdown: state.markdown
      }, (res) => {
        state.html = state.content.emoji ? emojinator(res.html) : res.html
        m.redraw()
      })
    },
    view: () => {
      var dom = []

      if (state.raw) {
        dom.push(m('pre#_markdown', {oncreate: oncreate.markdown}, state.markdown))
        $('body').classList.remove('_toc-left', '_toc-right')
      }
      else {
        if (state.theme) {
          dom.push(m('link#_theme', {
            rel: 'stylesheet', type: 'text/css',
            href: state.theme.url,
          }))
        }
        if (state.html) {
          dom.push(m('#_html', {oncreate: oncreate.html,
            class: /github(-dark)?/.test(state.theme.name) ? 'markdown-body' : 'markdown-theme'},
            m.trust(state.html)
          ))
          if (state.content.toc && state.toc) {
            dom.push(m('#_toc', {oncreate: oncreate.toc},
              m.trust(state.toc)
            ))
            $('body').classList.add('_toc-left')
          }
          if (state.content.mathjax) {
            dom.push(m('script', {type: 'text/x-mathjax-config'}, mathjax))
            dom.push(m('script', {
              src: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js'
            }))
          }
        }
      }

      return (dom.length ? dom : m('div'))
    }
  })
}

var scroll = (() => {
  function race (done) {
    var images = Array.from(document.querySelectorAll('img'))
    if (!images.length) {
      done()
    }
    var loaded = 0
    images.forEach((img) => {
      img.addEventListener('load', () => {
        if (++loaded === images.length) {
          done()
        }
      }, {once: true})
    })
    setTimeout(done, 100)
  }
  function debounce (container, done) {
    var listener = /body/i.test(container.nodeName) ? window : container
    var timeout = null
    listener.addEventListener('scroll', () => {
      clearTimeout(timeout)
      timeout = setTimeout(done, 100)
    })
  }
  function listen (container, prefix) {
    var key = prefix + location.origin + location.pathname
    try {
      container.scrollTop = parseInt(localStorage.getItem(key))
      debounce(container, () => {
        localStorage.setItem(key, container.scrollTop)
      })
    }
    catch (err) {
      chrome.storage.local.get(key, (res) => {
        container.scrollTop = parseInt(res[key])
      })
      debounce(container, () => {
        chrome.storage.local.set({[key]: container.scrollTop})
      })
    }
  }
  return {
    body: () => {
      var loaded
      race(() => {
        if (!loaded) {
          loaded = true
          if (state.content.scroll) {
            listen($('body'), 'md-')
          }
          else if (location.hash && $(location.hash)) {
            $('body').scrollTop = $(location.hash).offsetTop
          }
        }
      })
    },
    toc: () => {
      listen($('#_toc'), 'md-toc-')
    }
  }
})()

function anchors () {
  Array.from($('#_html').childNodes)
  .filter((node) => /h[1-6]/i.test(node.tagName))
  .forEach((node) => {
    var a = document.createElement('a')
    a.className = 'anchor'
    a.name = node.id
    a.href = '#' + node.id
    a.innerHTML = ''
    node.prepend(a)
  })
}

var toc = (
  link = (header) => '' + header.title + '') =>
  Array.from($('#_html').childNodes)
  .filter((node) => /h[1-6]/i.test(node.tagName))
  .map((node) => ({
    id: node.getAttribute('id'),
    level: parseInt(node.tagName.replace('H', '')),
    title: node.innerText.replace(/</g, '<').replace(/>/g, '>')
  }))
  .reduce((html, header) => {
    html += '
'.repeat(header.level)
    html += link(header)
    html += '
'.repeat(header.level)
    return html
  }, '')

if (document.readyState === 'complete') {
  mount()
}
else {
  var timeout = setInterval(() => {
    if (document.readyState === 'complete') {
      clearInterval(timeout)
      mount()
    }
  }, 0)
}

if (state.content.autoreload) {
  ;(() => {
    var initial = ''

    var xhr = new XMLHttpRequest()
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (!initial) {
          initial = xhr.responseText
        }
        else if (initial !== xhr.responseText) {
          location.reload(true)
        }
      }
    }

    var get = () => {
      xhr.open('GET', location.href + '?preventCache=' + Date.now(), true)
      try {
        xhr.send()
      }
      catch (err) {
        console.error(err)
        clearInterval(state.interval)
      }
    }

    get()
    state.interval = setInterval(get, state.ms)
  })()
}

var mathjax = `
  // TeX-AMS_HTML
  MathJax.Hub.Config({
    jax: [
      'input/TeX',
      'output/HTML-CSS',
      'output/PreviewHTML',
    ],
    extensions: [
      'tex2jax.js',
      'AssistiveMML.js',
      'a11y/accessibility-menu.js',
    ],
    TeX: {
      extensions: [
        'AMSmath.js',
        'AMSsymbols.js',
        'noErrors.js',
        'noUndefined.js',
      ]
    },
    tex2jax: {
      inlineMath: [
        ['$', '$'],
        ['\\\\(', '\\\\)'],
      ],
      displayMath: [
        ['$$', '$$'],
        ['\\\\[', '\\\\]'],
      ],
      processEscapes: true
    },
    showMathMenu: false,
    showProcessingMessages: false,
    messageStyle: 'none',
    skipStartupTypeset: true, // disable initial rendering
    positionToHash: false
  })
  // set specific container to render, can be delayed too
  MathJax.Hub.Queue(
    ['Typeset', MathJax.Hub, '_html']
  )
`
<|endoftext|>