<commit_before>/*
 * It provides a abstraction layer over native javascript Websocket.
 *
 * Provide additional functionality like if connection is close, open
 * it again and process the buffered requests
 *
 *
 * Usage:
 *
 * `BinarySocket.init()` to initiate the connection
 * `BinarySocket.send({contracts_for : 1})` to send message to server
 */
function BinarySocketClass() {
    'use strict';

    var binarySocket,
        bufferedSends = [],
        manualClosed = false,
        events = {},
        authorized = false,
        timeouts = {},
        req_number = 0,
        socketUrl;
        if(window.location.host == 'www.binary.com'){
          socketUrl = "wss://ws.binaryws.com/websockets/v3";
        } else{
          socketUrl = "wss://"+window.location.host+"/websockets/v3";
        }

    if (page.language()) {
        socketUrl += '?l=' + page.language();
    }

    var clearTimeouts = function(){
        for(var k in timeouts){
            if(timeouts.hasOwnProperty(k)){
                clearTimeout(timeouts[k]);
                delete timeouts[k];
            }
        }
    };

    var status = function () {
        return binarySocket && binarySocket.readyState;
    };

    var isReady = function () {
        return binarySocket && binarySocket.readyState === 1;
    };

    var isClose = function () {
        return !binarySocket || binarySocket.readyState === 2 || binarySocket.readyState === 3;
    };

    var sendBufferedSends = function () {
        while (bufferedSends.length > 0) {
            binarySocket.send(JSON.stringify(bufferedSends.shift()));
        }
    };

    var send = function(data) {
        if (isClose()) {
            bufferedSends.push(data);
            init(1);
        } else if (isReady()) {
            if(!data.hasOwnProperty('passthrough') && !data.hasOwnProperty('verify_email')){
                data.passthrough = {};
            }
            // temporary check
            if(data.contracts_for || data.proposal){
                data.passthrough.req_number = ++req_number;
                timeouts[req_number] = setTimeout(function(){
                    if(typeof reloadPage === 'function' && data.contracts_for){
                        alert("The server didn't respond to the request:\n\n"+JSON.stringify(data)+"\n\n");
                        reloadPage();
                    }
                    else{
                        $('.price_container').hide();
                    }
                }, 7*1000);
            }

            binarySocket.send(JSON.stringify(data));
        } else {
            bufferedSends.push(data);
        }
    };

    var init = function (es) {
        if(!es){
            events = {};
        }
        if(typeof es === 'object'){
            bufferedSends = [];
            manualClosed = false;
            events = es;
            clearTimeouts();
        }

        if(isClose()){
            binarySocket = new WebSocket(socketUrl);
        }

        binarySocket.onopen = function (){
            var loginToken = getCookieItem('login');
            if(loginToken && !authorized) {
                binarySocket.send(JSON.stringify({authorize: loginToken}));
            }
            else {
                sendBufferedSends();
            }

            if(typeof events.onopen === 'function'){
                events.onopen();
            }

            if(isReady()=== true){
                if (clock_started === false) {
                    page.header.start_clock_ws();
                }
            }
        };

        binarySocket.onmessage = function (msg){
            var response = JSON.parse(msg.data);
            if (response) {
                if(response.hasOwnProperty('echo_req') && response.echo_req.hasOwnProperty('passthrough') && response.echo_req.passthrough.hasOwnProperty('req_number')){
                    clearInterval(timeouts[response.echo_req.passthrough.req_number]);
                    delete timeouts[response.echo_req.passthrough.req_number];
                }
                var type = response.msg_type;
                if (type === 'authorize') {
                    authorized = true;
                    TUser.set(response.authorize);
                    if(typeof events.onauth === 'function'){
                        events.onauth();
                    }
                    send({balance:1, subscribe: 1});
                    sendBufferedSends();
                } else if (type === 'balance') {
                    ViewBalanceUI.updateBalances(response.balance);
                } else if (type === 'time') {
                    page.header.time_counter(response);
                } else if (type === 'logout') {
                    page.header.do_logout(response);
                } else if (type === 'error') {
                    if(response.error.code === 'RateLimit') {
                        $('#ratelimit-error-message')
                            .css('display', 'block')
                            .on('click', '#ratelimit-refresh-link', function () {
                                window.location.reload();
                            });
                    }
                }

                if(typeof events.onmessage === 'function'){
                    events.onmessage(msg);
                }
            }
        };

        binarySocket.onclose = function (e) {
            authorized = false;
            clearTimeouts();

            if(!manualClosed){
                init(1);
            }
            if(typeof events.onclose === 'function'){
                events.onclose();
            }
        };

        binarySocket.onerror = function (error) {
            console.log('socket error', error);
        };
    };

    var close = function () {
        manualClosed = true;
        bufferedSends = [];
        events = {};
        if (binarySocket) {
            binarySocket.close();
        }
    };

    var clear = function(){
        bufferedSends = [];
        manualClosed = false;
        events = {};
    };

    return {
        init: init,
        send: send,
        close: close,
        socket: function () { return binarySocket; },
        clear: clear,
        clearTimeouts: clearTimeouts
    };

}

var BinarySocket = new BinarySocketClass();
<commit_msg>temp logs<commit_after>/*
 * It provides a abstraction layer over native javascript Websocket.
 *
 * Provide additional functionality like if connection is close, open
 * it again and process the buffered requests
 *
 *
 * Usage:
 *
 * `BinarySocket.init()` to initiate the connection
 * `BinarySocket.send({contracts_for : 1})` to send message to server
 */
function BinarySocketClass() {
    'use strict';

    var binarySocket,
        bufferedSends = [],
        manualClosed = false,
        events = {},
        authorized = false,
        timeouts = {},
        req_number = 0,
        socketUrl;
        if(window.location.host == 'www.binary.com'){
          socketUrl = "wss://ws.binaryws.com/websockets/v3";
        } else{
          socketUrl = "wss://"+window.location.host+"/websockets/v3";
        }

    if (page.language()) {
        socketUrl += '?l=' + page.language();
    }

    var clearTimeouts = function(){
        for(var k in timeouts){
            if(timeouts.hasOwnProperty(k)){
                clearTimeout(timeouts[k]);
                delete timeouts[k];
            }
        }
    };

    var status = function () {
        return binarySocket && binarySocket.readyState;
    };

    var isReady = function () {
        return binarySocket && binarySocket.readyState === 1;
    };

    var isClose = function () {
        return !binarySocket || binarySocket.readyState === 2 || binarySocket.readyState === 3;
    };

    var sendBufferedSends = function () {
        while (bufferedSends.length > 0) {
            binarySocket.send(JSON.stringify(bufferedSends.shift()));
        }
    };

    var send = function(data) {
        console.log(data, 'send');

        if (isClose()) {
            bufferedSends.push(data);
            init(1);
        } else if (isReady()) {
            if(!data.hasOwnProperty('passthrough') && !data.hasOwnProperty('verify_email')){
                data.passthrough = {};
            }
            // temporary check
            if(data.contracts_for || data.proposal){
                data.passthrough.req_number = ++req_number;
                timeouts[req_number] = setTimeout(function(){
                    if(typeof reloadPage === 'function' && data.contracts_for){
                        alert("The server didn't respond to the request:\n\n"+JSON.stringify(data)+"\n\n");
                        reloadPage();
                    }
                    else{
                        $('.price_container').hide();
                    }
                }, 7*1000);
            }

            binarySocket.send(JSON.stringify(data));
        } else {
            bufferedSends.push(data);
        }
    };

    var init = function (es) {
        if(!es){
            events = {};
        }
        if(typeof es === 'object'){
            bufferedSends = [];
            manualClosed = false;
            events = es;
            clearTimeouts();
        }

        if(isClose()){
            binarySocket = new WebSocket(socketUrl);
        }

        binarySocket.onopen = function (){
            var loginToken = getCookieItem('login');
            if(loginToken && !authorized) {
                binarySocket.send(JSON.stringify({authorize: loginToken}));
            }
            else {
                sendBufferedSends();
            }

            if(typeof events.onopen === 'function'){
                events.onopen();
            }

            if(isReady()=== true){
                if (clock_started === false) {
                    page.header.start_clock_ws();
                }
            }
        };

        binarySocket.onmessage = function (msg){
            console.log(msg, 'onmessage');
            var response = JSON.parse(msg.data);
            if (response) {
                if(response.hasOwnProperty('echo_req') && response.echo_req.hasOwnProperty('passthrough') && response.echo_req.passthrough.hasOwnProperty('req_number')){
                    clearInterval(timeouts[response.echo_req.passthrough.req_number]);
                    delete timeouts[response.echo_req.passthrough.req_number];
                }
                var type = response.msg_type;
                if (type === 'authorize') {
                    authorized = true;
                    TUser.set(response.authorize);
                    if(typeof events.onauth === 'function'){
                        events.onauth();
                    }
                    send({balance:1, subscribe: 1});
                    sendBufferedSends();
                } else if (type === 'balance') {
                    ViewBalanceUI.updateBalances(response.balance);
                } else if (type === 'time') {
                    page.header.time_counter(response);
                } else if (type === 'logout') {
                    page.header.do_logout(response);
                } else if (type === 'error') {
                    if(response.error.code === 'RateLimit') {
                        $('#ratelimit-error-message')
                            .css('display', 'block')
                            .on('click', '#ratelimit-refresh-link', function () {
                                window.location.reload();
                            });
                    }
                }

                if(typeof events.onmessage === 'function'){
                    events.onmessage(msg);
                }
            }
        };

        binarySocket.onclose = function (e) {
            authorized = false;
            clearTimeouts();

            if(!manualClosed){
                init(1);
            }
            if(typeof events.onclose === 'function'){
                events.onclose();
            }
        };

        binarySocket.onerror = function (error) {
            console.log('socket error', error);
        };
    };

    var close = function () {
        manualClosed = true;
        bufferedSends = [];
        events = {};
        if (binarySocket) {
            binarySocket.close();
        }
    };

    var clear = function(){
        bufferedSends = [];
        manualClosed = false;
        events = {};
    };

    return {
        init: init,
        send: send,
        close: close,
        socket: function () { return binarySocket; },
        clear: clear,
        clearTimeouts: clearTimeouts
    };

}

var BinarySocket = new BinarySocketClass();
<|endoftext|>


<commit_before>'use strict';

var clear     = require('es5-ext/lib/Array/prototype/clear')
  , d         = require('es5-ext/lib/Object/descriptor')
  , isEmpty   = require('es5-ext/lib/Object/is-empty')
  , contains  = require('es5-ext/lib/String/prototype/contains')
  , ee        = require('event-emitter')
  , nextTick  = require('next-tick')
  , now       = require('time-uuid/lib/time')
  , idSplit   = require('./id-split')

  , defineProperties = Object.defineProperties, keys = Object.keys

  , current = null, history = {}, objects, getObject
  , Signal, signals, commit, emit;

commit = function () {
	var signal;
	if (!current) return;
	signal = current;
	current = null;
	if (isEmpty(signal)) {
		delete signals[signal._sourceId + ':' + signal._stamp];
		return;
	}
	emit(signal);
};

Signal = function () {
	defineProperties(this, {
		_stamp: d(now()),
		_order: d([])
	});
	signals[this._sourceId + ':' + this._stamp] = this;
	nextTick(commit);
};
Signal.prototype._sourceId = '0';

signals = module.exports = ee(function (obj, value) {
	var id = obj._id_;
	if (!current) current = new Signal();
	objects[id] = obj;
	if (!current.hasOwnProperty(id)) {
		if (!history[id]) history[id] = [];
		history[id].unshift(current);
	} else {
		current._order.splice(current._order.indexOf(id), 1);
	}
	current._order.push(id);
	current[id] = value;
});

defineProperties(signals, {
	_emit: d(emit = function (signal) {
		var importId = signal._importSourceId || null;
		signal._order.forEach(function (id) {
			var i, l, current, obj, value;
			value = signal[id];
			obj = getObject(id);
			signals.emit(id, obj, value, signal);
			if (value && value._id_) signals.emit('*' + value._id_, obj, signal);
			id = idSplit(id);
			l = id.length;
			if (l === 1) return;
			current = id[0];
			for (i = 1; i < l; ++i) {
				signals.emit(current + '*', obj, value, signal);
				current += ':' + id[i];
			}
			signals.emit('*', obj, value, signal, importId);
		});
	}),
	rollback: d(function () {
		if (!current) return;
		keys(current).forEach(function (id) {
			var previous, his = history[id];
			if (his[0] !== current) {
				his.splice(his.indexOf(current), 1);
				return;
			}
			his.shift();
			previous = his[0] && his[0][id];
			if (contains.call(id, ':')) objects[id].$setValue(previous);
			else objects[id].$proto(previous);
			delete current[id];
		});
		clear.call(current._order);
	}),
	history: d(history),
	sourceId: d.gs(function () { return Signal.prototype._sourceId; },
		function (value) { Signal.prototype._sourceId = String(value); })
});

objects = require('./objects');
getObject = objects._get;
<commit_msg>Unify listener signature<commit_after>'use strict';

var clear     = require('es5-ext/lib/Array/prototype/clear')
  , d         = require('es5-ext/lib/Object/descriptor')
  , isEmpty   = require('es5-ext/lib/Object/is-empty')
  , contains  = require('es5-ext/lib/String/prototype/contains')
  , ee        = require('event-emitter')
  , nextTick  = require('next-tick')
  , now       = require('time-uuid/lib/time')
  , idSplit   = require('./id-split')

  , defineProperties = Object.defineProperties, keys = Object.keys

  , current = null, history = {}, objects, getObject
  , Signal, signals, commit, emit;

commit = function () {
	var signal;
	if (!current) return;
	signal = current;
	current = null;
	if (isEmpty(signal)) {
		delete signals[signal._sourceId + ':' + signal._stamp];
		return;
	}
	emit(signal);
};

Signal = function () {
	defineProperties(this, {
		_stamp: d(now()),
		_order: d([])
	});
	signals[this._sourceId + ':' + this._stamp] = this;
	nextTick(commit);
};
Signal.prototype._sourceId = '0';

signals = module.exports = ee(function (obj, value) {
	var id = obj._id_;
	if (!current) current = new Signal();
	objects[id] = obj;
	if (!current.hasOwnProperty(id)) {
		if (!history[id]) history[id] = [];
		history[id].unshift(current);
	} else {
		current._order.splice(current._order.indexOf(id), 1);
	}
	current._order.push(id);
	current[id] = value;
});

defineProperties(signals, {
	_emit: d(emit = function (signal) {
		var importId = signal._importSourceId || null;
		signal._order.forEach(function (id) {
			var i, l, current, obj, value;
			value = signal[id];
			obj = getObject(id);
			signals.emit(id, obj, value, signal, importId);
			if (value && value._id_) {
				signals.emit('*' + value._id_, obj, signal, importId);
			}
			id = idSplit(id);
			l = id.length;
			if (l === 1) return;
			current = id[0];
			for (i = 1; i < l; ++i) {
				signals.emit(current + '*', obj, value, signal, importId);
				current += ':' + id[i];
			}
			signals.emit('*', obj, value, signal, importId);
		});
	}),
	rollback: d(function () {
		if (!current) return;
		keys(current).forEach(function (id) {
			var previous, his = history[id];
			if (his[0] !== current) {
				his.splice(his.indexOf(current), 1);
				return;
			}
			his.shift();
			previous = his[0] && his[0][id];
			if (contains.call(id, ':')) objects[id].$setValue(previous);
			else objects[id].$proto(previous);
			delete current[id];
		});
		clear.call(current._order);
	}),
	history: d(history),
	sourceId: d.gs(function () { return Signal.prototype._sourceId; },
		function (value) { Signal.prototype._sourceId = String(value); })
});

objects = require('./objects');
getObject = objects._get;
<|endoftext|>


<commit_before>import React from 'react';

import CodeMirror from 'react-codemirror';
import CM from 'codemirror';

const Rx = require('@reactivex/rxjs');

import { updateCellSource } from '../../actions';

import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/hint/anyword-hint';

// Hint picker
const pick = (cm, handle) => handle.pick();

function goLineUpOrEmit(editor) {
  const cursor = editor.getCursor();
  if (cursor.line === 0 && cursor.ch === 0 && !editor.somethingSelected()) {
    CM.signal(editor, 'topBoundary');
  } else {
    editor.execCommand('goLineUp');
  }
}

function goLineDownOrEmit(editor) {
  const cursor = editor.getCursor();
  const lastLineNumber = editor.lastLine();
  const lastLine = editor.getLine(lastLineNumber);
  if (cursor.line === lastLineNumber &&
      cursor.ch === lastLine.length &&
      !editor.somethingSelected()) {
    CM.signal(editor, 'bottomBoundary');
  } else {
    editor.execCommand('goLineDown');
  }
}

CM.keyMap.basic.Up = 'goLineUpOrEmit';
CM.keyMap.basic.Down = 'goLineDownOrEmit';
CM.commands.goLineUpOrEmit = goLineUpOrEmit;
CM.commands.goLineDownOrEmit = goLineDownOrEmit;

export default class Editor extends React.Component {
  static propTypes = {
    id: React.PropTypes.string,
    input: React.PropTypes.any,
    getCompletions: React.PropTypes.any,
    language: React.PropTypes.string,
    lineNumbers: React.PropTypes.bool,
    lineWrapping: React.PropTypes.bool,
    onChange: React.PropTypes.func,
    theme: React.PropTypes.string,
    cmtheme: React.PropTypes.string,
    focused: React.PropTypes.bool,
    focusAbove: React.PropTypes.func,
    focusBelow: React.PropTypes.func,
  };

  static contextTypes = {
    dispatch: React.PropTypes.func,
  };

  static defaultProps = {
    language: 'python',
    lineNumbers: false,
    cmtheme: 'composition',
    focused: false,
  };

  constructor(props) {
    super(props);
    this.state = {
      source: this.props.input,
    };
    this.onChange = this.onChange.bind(this);

    this.hint = this.completions.bind(this);
    this.hint.async = true;

    // Remember the name of the theme that's applied so that when it changes we
    // can force codemirror to remeasure.
    this.theme = null;
  }

  componentDidMount() {
    // On first load, if focused, set codemirror to focus
    if (this.props.focused) {
      this.refs.codemirror.focus();
    }

    function formChangeObject(cm, change) {
      return {
        cm,
        change,
      };
    }

    const cm = this.refs.codemirror.getCodeMirror();
    cm.on('topBoundary', this.props.focusAbove);
    cm.on('bottomBoundary', this.props.focusBelow);

    const inputEvents = Rx.Observable.fromEvent(cm,
      'change', formChangeObject)
      .filter(x => x.change.origin === '+input');

    // TODO: The subscription created here needs to be cleaned up when the cell
    //       is deleted
    inputEvents
      .debounceTime(20)
      // Pass through changes that aren't newlines
      .filter(event => event.change.text.length === 1 ||
                       event.change.text.length === 2 &&
                       !(event.change.text[0] === '' && event.change.text[1] === '')
      )
      // Pass through only partial tokens that are composed of words
      .filter(event => {
        const editor = event.cm;
        const tokenRange = editor.findWordAt(editor.getCursor());
        const token = editor.getRange(tokenRange.anchor, tokenRange.head);
        return /(\w|\.)/.test(token);
      })
      .subscribe(event => {
        if (!event.cm.state.completionActive) {
          event.cm.execCommand('autocomplete');
        }
      });
  }

  componentWillReceiveProps(nextProps) {
    this.setState({
      source: nextProps.input,
    });
  }

  componentDidUpdate(prevProps) {
    if (this.props.focused && prevProps.focused !== this.props.focused) {
      this.refs.codemirror.focus();
    } else if (!this.props.focused && prevProps.focused !== this.props.focused) {
      const cm = this.refs.codemirror.getCodeMirror();
      cm.getInputField().blur();
    }

    if (this.theme !== this.props.theme) {
      this.theme = this.props.theme;
      this.refs.codemirror.getCodeMirror().refresh();
    }
  }

  onChange(text) {
    if (this.props.onChange) {
      this.props.onChange(text);
    } else {
      this.setState({
        source: text,
      });
      this.context.dispatch(updateCellSource(this.props.id, text));
    }
  }

  completions(editor, callback) {
    const cursor = editor.getCursor();
    this.props.getCompletions(editor.getValue(), cursor.ch).then(results => callback({
      list: results.matches,
      from: {
        line: cursor.line,
        ch: results.cursor_start,
      },
      to: {
        line: cursor.line,
        ch: results.cursor_end,
      },
    }));
  }

  render() {
    const options = {
      mode: this.props.language,
      lineNumbers: this.props.lineNumbers,
      lineWrapping: this.props.lineWrapping,
      theme: this.props.cmtheme,
      autofocus: false,
      hintOptions: {
        hint: this.hint,
        completeSingle: false, // In automatic autocomplete mode we don't want override
        extraKeys: {
          Right: pick,
        },
      },
      extraKeys: {
        'Ctrl-Space': 'autocomplete',
      },
    };
    return (

        <CodeMirror
          value={this.state.source}
          ref="codemirror"
          className="cell_cm"
          options={options}
          onChange={this.onChange}
        />

    );
  }
}
<commit_msg>Fix bug where completion attempt would happen regardless of MD cell<commit_after>import React from 'react';

import CodeMirror from 'react-codemirror';
import CM from 'codemirror';

const Rx = require('@reactivex/rxjs');

import { updateCellSource } from '../../actions';

import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/hint/anyword-hint';

// Hint picker
const pick = (cm, handle) => handle.pick();

function goLineUpOrEmit(editor) {
  const cursor = editor.getCursor();
  if (cursor.line === 0 && cursor.ch === 0 && !editor.somethingSelected()) {
    CM.signal(editor, 'topBoundary');
  } else {
    editor.execCommand('goLineUp');
  }
}

function goLineDownOrEmit(editor) {
  const cursor = editor.getCursor();
  const lastLineNumber = editor.lastLine();
  const lastLine = editor.getLine(lastLineNumber);
  if (cursor.line === lastLineNumber &&
      cursor.ch === lastLine.length &&
      !editor.somethingSelected()) {
    CM.signal(editor, 'bottomBoundary');
  } else {
    editor.execCommand('goLineDown');
  }
}

CM.keyMap.basic.Up = 'goLineUpOrEmit';
CM.keyMap.basic.Down = 'goLineDownOrEmit';
CM.commands.goLineUpOrEmit = goLineUpOrEmit;
CM.commands.goLineDownOrEmit = goLineDownOrEmit;

export default class Editor extends React.Component {
  static propTypes = {
    id: React.PropTypes.string,
    input: React.PropTypes.any,
    getCompletions: React.PropTypes.any,
    language: React.PropTypes.string,
    lineNumbers: React.PropTypes.bool,
    lineWrapping: React.PropTypes.bool,
    onChange: React.PropTypes.func,
    theme: React.PropTypes.string,
    cmtheme: React.PropTypes.string,
    focused: React.PropTypes.bool,
    focusAbove: React.PropTypes.func,
    focusBelow: React.PropTypes.func,
  };

  static contextTypes = {
    dispatch: React.PropTypes.func,
  };

  static defaultProps = {
    language: 'python',
    lineNumbers: false,
    cmtheme: 'composition',
    focused: false,
  };

  constructor(props) {
    super(props);
    this.state = {
      source: this.props.input,
    };
    this.onChange = this.onChange.bind(this);

    this.hint = this.completions.bind(this);
    this.hint.async = true;

    // Remember the name of the theme that's applied so that when it changes we
    // can force codemirror to remeasure.
    this.theme = null;
  }

  componentDidMount() {
    // On first load, if focused, set codemirror to focus
    if (this.props.focused) {
      this.refs.codemirror.focus();
    }

    function formChangeObject(cm, change) {
      return {
        cm,
        change,
      };
    }

    const cm = this.refs.codemirror.getCodeMirror();
    cm.on('topBoundary', this.props.focusAbove);
    cm.on('bottomBoundary', this.props.focusBelow);

    const inputEvents = Rx.Observable.fromEvent(cm,
      'change', formChangeObject)
      .filter(x => x.change.origin === '+input');

    // TODO: The subscription created here needs to be cleaned up when the cell
    //       is deleted
    inputEvents
      .debounceTime(20)
      // Pass through changes that aren't newlines
      .filter(event => event.change.text.length === 1 ||
                       event.change.text.length === 2 &&
                       !(event.change.text[0] === '' && event.change.text[1] === '')
      )
      // Pass through only partial tokens that are composed of words
      .filter(event => {
        const editor = event.cm;
        const tokenRange = editor.findWordAt(editor.getCursor());
        const token = editor.getRange(tokenRange.anchor, tokenRange.head);
        return /(\w|\.)/.test(token);
      })
      .subscribe(event => {
        if (!event.cm.state.completionActive) {
          event.cm.execCommand('autocomplete');
        }
      });
  }

  componentWillReceiveProps(nextProps) {
    this.setState({
      source: nextProps.input,
    });
  }

  componentDidUpdate(prevProps) {
    if (this.props.focused && prevProps.focused !== this.props.focused) {
      this.refs.codemirror.focus();
    } else if (!this.props.focused && prevProps.focused !== this.props.focused) {
      const cm = this.refs.codemirror.getCodeMirror();
      cm.getInputField().blur();
    }

    if (this.theme !== this.props.theme) {
      this.theme = this.props.theme;
      this.refs.codemirror.getCodeMirror().refresh();
    }
  }

  onChange(text) {
    if (this.props.onChange) {
      this.props.onChange(text);
    } else {
      this.setState({
        source: text,
      });
      this.context.dispatch(updateCellSource(this.props.id, text));
    }
  }

  completions(editor, callback) {
    const cursor = editor.getCursor();
    if (this.props.getCompletions) {
      this.props.getCompletions(editor.getValue(), cursor.ch).then(results => callback({
        list: results.matches,
        from: {
          line: cursor.line,
          ch: results.cursor_start,
        },
        to: {
          line: cursor.line,
          ch: results.cursor_end,
        },
      }));
    }
  }

  render() {
    const options = {
      mode: this.props.language,
      lineNumbers: this.props.lineNumbers,
      lineWrapping: this.props.lineWrapping,
      theme: this.props.cmtheme,
      autofocus: false,
      hintOptions: {
        hint: this.hint,
        completeSingle: false, // In automatic autocomplete mode we don't want override
        extraKeys: {
          Right: pick,
        },
      },
      extraKeys: {
        'Ctrl-Space': 'autocomplete',
      },
    };
    return (

        <CodeMirror
          value={this.state.source}
          ref="codemirror"
          className="cell_cm"
          options={options}
          onChange={this.onChange}
        />

    );
  }
}
<|endoftext|>


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

module.exports = {
    local: {
        dontReportSauceLabs: true
    },

    reporter: function(results, done) {
        var passed = results.failed === 0 && results.errors === 0;
        console.log('Final result: "{ passed: ' + passed + ' }"');

        if (!this.dontReportSauceLabs) {
            var userName = this.client.options.username;
            var accessKey = this.client.options.accessKey;
            var baseUrl = 'https://saucelabs.com/rest/v1/';

            console.log('Sending final result to Saucelabs...');

            if (userName && accessKey) {
                request
                    .put(baseUrl + userName + '/jobs/' + this.client.sessionId)
                    .send({ passed: true })
                    .auth(userName, accessKey)
                    .end(function(error, res){
                        if (error) {
                            console.log('ERROR sending verdict');
                            console.log(error);
                        } else {
                            console.log('Verdict sent to Sauce Labs, response:' +
                                res.res.statusMessage);
                        }
                        done();
                    });
            } else {
                console.log('Username or access key missing, username: ' + userName);
                done();
            }
        } else {
            console.log('Local testing. Not notifying Saucelabs.');
        }
    }
};
<commit_msg>Fix Sauce Labs username and key<commit_after>
var util = require('util');
var request = require('superagent');

module.exports = {
    local: {
        dontReportSauceLabs: true
    },

    reporter: function(results, done) {
        var passed = results.failed === 0 && results.errors === 0;
        console.log('Final result: "{ passed: ' + passed + ' }"');

        console.log(this);
        console.log(results);

        if (!this.dontReportSauceLabs) {
            var userName = this.test_settings.username;
            var accessKey = this.test_settings.access_key;
            var baseUrl = 'https://saucelabs.com/rest/v1/';

            console.log('Sending final result to Saucelabs...');

            if (userName && accessKey) {
                request
                    .put(baseUrl + userName + '/jobs/' + this.client.sessionId)
                    .send({ passed: true })
                    .auth(userName, accessKey)
                    .end(function(error, res){
                        if (error) {
                            console.log('ERROR sending verdict');
                            console.log(error);
                        } else {
                            console.log('Verdict sent to Sauce Labs, response:' +
                                res.res.statusMessage);
                        }
                        done();
                    });
            } else {
                console.log('Username or access key missing, username: ' + userName);
                done();
            }
        } else {
            console.log('Local testing. Not notifying Saucelabs.');
        }
    }
};
<|endoftext|>


<commit_before>//
// <credit> simple code editor for Coderdojo by @dingledojo </credit>
// free Opensource. free from from commercial favor. honor the credit.
//

var ftoken; //derived from the email address; used for folder name.
var email; //returned by G+ API
var rob;
if (!window.console) console = {log: function() {}}; //IE
var gpath = 'D:\\Hosting\\4994778\\html\\quickdojo\\pages\\';
var n = window.location.href;

if (n.indexOf("www.") > 0 ) {
n= n.replace("www.","");
window.location.href =n;
document.reload;
}

function cFolder () {
var ws = getUrlVars()["r"];
if (ws == "websummit"){
ftoken = "websummit";
email = "websummit";
var path= 'D:\\Hosting\\4994778\\html\\quickdojo\\pages\\' + ftoken;
showDivs();
var el = document.getElementById('loginok');
el.innerHTML = "" + ftoken + "";
toggleElement('loginok');

}
}

function logo(robo) {
toggleElement("logo");
document.getElementById('logo').title=robo;
}

function load() {
    var xmlhttp;
    xmlhttp=new XMLHttpRequest();
    xmlhttp.onreadystatechange=function() {
    if (xmlhttp.readyState==4 && xmlhttp.status==200){
    document.getElementById("code").value=xmlhttp.responseText;}
    }

    var fpath="http://quickdojo.com/pages/" + ftoken + "/" + document.getElementById('file').value; + "?t=" + Math.random();
    xmlhttp.open('GET', fpath, true);
    xmlhttp.send();

}

function publish() {
if (!ftoken) {
    if (localStorage.qdvisit)
    { localStorage.qdvisit=Number(localStorage.qdvisit)+1; }
    else { localStorage.qdvisit=1; }
    if (Number(localStorage.qdvisit) <= 99)
    {
    file =guid(file)+'.html';
    }
    else {alert('Login with Google for dedicated folders :D');}

var path= 'D:\\Hosting\\4994778\\html\\quickdojo\\pages\\';
var fpath='http://quickdojo.com/pages/' + file;
}//endif

if (ftoken) {
var file= document.getElementById('file').value;
var path= 'D:\\Hosting\\4994778\\html\\quickdojo\\pages\\' + ftoken + '\\';
var fpath='http://quickdojo.com/pages/' + ftoken + '/' + file;
}

if (file) {
var code= document.getElementById('code').value;

if (code.search(/phpinfo/i)!==-1){
code="
Here is Edward Bear,
coming downstairs now,
bump, bump, bump, bump,
on the back of his head,
behind Christopher Robin.
";
}

if (code == '') {
code="

";
}

if (code.search(/UTF/i) == -1){
code = "<meta charset='UTF-8'>\n" + code;
}

/* code = code+=""; */

$.ajax({
type: 'POST',
url:'http://quickdojo.com/createfp.php',
data:{
'gpath': gpath,
'file': path + file,
'code': code
},
success: function(){
openTab(fpath);
logo('You rock !');
}
}); //endajax

} //endif

} //endfn

function openTab(url)
{
//
// if ipad or iphone show in same window; otherwise open in a new window.
//
var ipad = /(iPhone|iPod|iPad).*AppleWebKit/i.test(navigator.userAgent);
var target='_self';
if (!ipad) {target='_blank';}
if (window.chrome) {target='_newtab';}

url= url + "?t=" + guid(); // Show non-cached version

var win=window.open(url, target);
win.focus();
}

function guid() {
return ((Math.random()*Math.pow(36,6) << 0).toString(36)).substr(-6);
}

function login(authResult) {
    if (authResult) {
      if (authResult['error'] == undefined){
        gapi.auth.setToken(authResult); // Store the returned token.
        getEmail();
        showDivs();                    // Trigger request to get the email address.
      } else {
        console.log('An error occurred');
      }
    } else {
      console.log('Empty authResult');  // Something went wrong
    }
  }

function getEmail(){
    // Load the oauth2 libraries to enable the userinfo methods.
    gapi.client.load('oauth2', 'v2', function() {
          var request = gapi.client.oauth2.userinfo.get();
          request.execute(getEmailCallback);
        });
  }

function getEmailCallback(obj) {
var el = document.getElementById('email');
var email = '';
if (obj['email'])
{
    email = obj['email'];
}
var n=email.indexOf("@");
ftoken=email.slice(0,n); //!important

var path= 'D:\\Hosting\\4994778\\html\\quickdojo\\pages\\' + ftoken;

$.ajax({
type: 'POST',
url:'http://quickdojo.com/mkdirp.php',
data:{
'dir': path
},

success: function(){
}

}); //endajax

var el = document.getElementById('loginok');
el.innerHTML = "" + ftoken + "";
toggleElement('loginok');

}

function logout() {
openTab('https://accounts.google.com/Logout');
location.reload();
}

function showDivs () {
toggleElement('signinButton');
toggleElement('filename');
toggleElement('load');
toggleElement('showDir');
}

function toggleElement(id) {
    var el = document.getElementById(id);
    if (el.getAttribute('class') == 'hide') {
      el.setAttribute('class', 'show');
    } else {
      el.setAttribute('class', 'hide');
    }
}

function showDir() {
path='http://quickdojo.com/pages/'+ftoken+'/';
url='http://quickdojo.com/dir.php?dir='+'D:\\Hosting\\4994778\\html\\quickdojo\\pages\\'+ftoken+'&path='+path;
$("#showpages").load(url).scrollTop(0).dialog({modal:true});
}//endfn

/* Return URL parameter; example: var rb = getUrlVars()["rb"]; */
function getUrlVars()
{var vars = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++)
    {
        hash = hashes[i].split('=');
        vars.push(hash[0]);
        vars[hash[0]] = hash[1];
    }
	if (hash[1]) {localStorage.ntuser = hash[1]}; /* Store visitor name for welcome series */
    return vars;
}
<commit_msg>Update quickdojo.js<commit_after>//
// <credit> simple code editor for Coderdojo by @dingledojo </credit>
// free Opensource. free from from commercial favor. honor the credit.
//

var ftoken; //derived from the email address; used for folder name.
var email; //returned by G+ API
var rob;
if (!window.console) console = {log: function() {}}; //IE
var gpath = 'D:\\Hosting\\'+ /*your domain number goes here*/+'\\html\\quickdojo\\pages\\';
var n = window.location.href;

if (n.indexOf("www.") > 0 ) {
n= n.replace("www.","");
window.location.href =n;
document.reload;
}

function cFolder () {
var ws = getUrlVars()["r"];
if (ws == "websummit"){
ftoken = "websummit";
email = "websummit";
var path= 'D:\\Hosting\\'+ /*your domain number goes here*/+'\\html\\quickdojo\\pages\\' + ftoken;
showDivs();
var el = document.getElementById('loginok');
el.innerHTML = "" + ftoken + "";
toggleElement('loginok');

}
}

function logo(robo) {
toggleElement("logo");
document.getElementById('logo').title=robo;
}

function load() {
    var xmlhttp;
    xmlhttp=new XMLHttpRequest();
    xmlhttp.onreadystatechange=function() {
    if (xmlhttp.readyState==4 && xmlhttp.status==200){
    document.getElementById("code").value=xmlhttp.responseText;}
    }

    var fpath="http://quickdojo.com/pages/" + ftoken + "/" + document.getElementById('file').value; + "?t=" + Math.random();
    xmlhttp.open('GET', fpath, true);
    xmlhttp.send();

}

function publish() {
if (!ftoken) {
    if (localStorage.qdvisit)
    { localStorage.qdvisit=Number(localStorage.qdvisit)+1; }
    else { localStorage.qdvisit=1; }
    if (Number(localStorage.qdvisit) <= 99)
    {
    file =guid(file)+'.html';
    }
    else {alert('Login with Google for dedicated folders :D');}

var path= 'D:\\Hosting\\'+ /*your domain number goes here*/+'\\html\\quickdojo\\pages\\';
var fpath='http://quickdojo.com/pages/' + file;
}//endif

if (ftoken) {
var file= document.getElementById('file').value;
var path= 'D:\\Hosting\\'+ /*your domain number goes here*/+'\\html\\quickdojo\\pages\\' + ftoken + '\\';
var fpath='http://quickdojo.com/pages/' + ftoken + '/' + file;
}

if (file) {
var code= document.getElementById('code').value;

if (code.search(/phpinfo/i)!==-1){
code="
Here is Edward Bear,
coming downstairs now,
bump, bump, bump, bump,
on the back of his head,
behind Christopher Robin.
";
}

if (code == '') {
code="

";
}

if (code.search(/UTF/i) == -1){
code = "<meta charset='UTF-8'>\n" + code;
}

/* code = code+=""; */

$.ajax({
type: 'POST',
url:'http://quickdojo.com/createfp.php',
data:{
'gpath': gpath,
'file': path + file,
'code': code
},
success: function(){
openTab(fpath);
logo('You rock !');
}
}); //endajax

} //endif

} //endfn

function openTab(url)
{
//
// if ipad or iphone show in same window; otherwise open in a new window.
//
var ipad = /(iPhone|iPod|iPad).*AppleWebKit/i.test(navigator.userAgent);
var target='_self';
if (!ipad) {target='_blank';}
if (window.chrome) {target='_newtab';}

url= url + "?t=" + guid(); // Show non-cached version

var win=window.open(url, target);
win.focus();
}

function guid() {
return ((Math.random()*Math.pow(36,6) << 0).toString(36)).substr(-6);
}

function login(authResult) {
    if (authResult) {
      if (authResult['error'] == undefined){
        gapi.auth.setToken(authResult); // Store the returned token.
        getEmail();
        showDivs();                    // Trigger request to get the email address.
      } else {
        console.log('An error occurred');
      }
    } else {
      console.log('Empty authResult');  // Something went wrong
    }
  }

function getEmail(){
    // Load the oauth2 libraries to enable the userinfo methods.
    gapi.client.load('oauth2', 'v2', function() {
          var request = gapi.client.oauth2.userinfo.get();
          request.execute(getEmailCallback);
        });
  }

function getEmailCallback(obj) {
var el = document.getElementById('email');
var email = '';
if (obj['email'])
{
    email = obj['email'];
}
var n=email.indexOf("@");
ftoken=email.slice(0,n); //!important

var path='D:\\Hosting\\'+ /*your domain number goes here*/+'\\html\\quickdojo\\pages\\' + ftoken;

$.ajax({
type: 'POST',
url:'http://quickdojo.com/mkdirp.php',
data:{
'dir': path
},

success: function(){
}

}); //endajax

var el = document.getElementById('loginok');
el.innerHTML = "" + ftoken + "";
toggleElement('loginok');

}

function logout() {
openTab('https://accounts.google.com/Logout');
location.reload();
}

function showDivs () {
toggleElement('signinButton');
toggleElement('filename');
toggleElement('load');
toggleElement('showDir');
}

function toggleElement(id) {
    var el = document.getElementById(id);
    if (el.getAttribute('class') == 'hide') {
      el.setAttribute('class', 'show');
    } else {
      el.setAttribute('class', 'hide');
    }
}

function showDir() {
path='http://quickdojo.com/pages/'+ftoken+'/';
url='http://quickdojo.com/dir.php?dir='+'D:\\Hosting\\'+ /*your domain number goes here*/+'\\html\\quickdojo\\pages\\'+ftoken+'&path='+path;
$("#showpages").load(url).scrollTop(0).dialog({modal:true});
}//endfn

/* Return URL parameter; example: var rb = getUrlVars()["rb"]; */
function getUrlVars()
{var vars = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++)
    {
        hash = hashes[i].split('=');
        vars.push(hash[0]);
        vars[hash[0]] = hash[1];
    }
	if (hash[1]) {localStorage.ntuser = hash[1]}; /* Store visitor name for welcome series */
    return vars;
}
<|endoftext|>


<commit_before>/**
 * @module Ink.UI.LazyLoad_1
 */

Ink.createModule('Ink.UI.LazyLoad', '1', ['Ink.UI.Common_1', 'Ink.Dom.Event_1', 'Ink.Dom.Element_1'], function(UICommon, InkEvent, InkElement) {
'use strict';

var LazyLoad = function(selector, options) {
    this._init(selector, options);
};

LazyLoad.prototype = {
    /**
     * @class Ink.UI.LazyLoad_1
     * @constructor
     */
    _init: function(selector) {
        this._rootElm = UICommon.elsOrSelector(selector, 'Ink.UI.LazyLoad root element')[0] || null;

        this._options = UICommon.options({
            item: ['String', '.lazyload-item'], // Use this to select and define what is to be considered an `item`.
            placeholder: ['String', null], // Placeholder value for items which are still outside the screen (in case they don't already have a value set)
            source: ['String', 'data-src'], // When an `item` is within the viewport, take the value it has in this attribute then set its `destination` attribute to it.
            destination: ['String', 'src'], // attribute which gets the value in `source` when the element is visible.
            delay: ['Number', 100], // Wait a few milliseconds before trying to load.
            delta: ['Number', 0], // Distance in px from the outside of the viewport. Elements touching within this "margin", items are considered to be inside even if they are outside the viewport limits. Can be negative if you want an element to be considered inside only when it is a certain distance into the viewport.
            image: ['Boolean', true], // Set to false to make this component do nothing to any elements and just give you the onInsideViewport callback.
            scrollElement: ['Element', window],
            touchEvents: ['Boolean', true],  // Subscribe to touch events in addition to scroll events. Useful in mobile safari because 'scroll' events aren't frequent enough.
            onInsideViewport: ['Function', false], // Called when an `item` is within the viewport. Receives `{ element }`
            onAfterAttributeChange: ['Function', false],  // (advanced) Called after `source` is copied over to `destination`. Receives `{ element }`
            autoInit: ['Boolean', true]  // Set to false if you want LazyLoad to do nothing until you call `reload()`
        }, arguments[1] || {}, this._rootElm);

        this._aData = [];
        this._hasEvents = false;

        if(this._options.autoInit) {
            this._activate();
        }
    },

    _activate: function()
    {
        this._getData();
        if(!this._hasEvents) {
            this._addEvents();
        }
        this._onScrollThrottled();
    },

    _getData: function()
    {
        var aElms = Ink.ss(this._options.item);
        var attr = null;
        for(var i=0, t=aElms.length; i < t; i++) {
            if (this._options.placeholder != null && !InkElement.hasAttribute(aElms[i], this._options.destination)) {
                aElms[i].setAttribute(this._options.destination, this._options.placeholder);
            }
            attr = aElms[i].getAttribute(this._options.source);
            if(attr !== null || !this._options.image) {
                this._aData.push({elm: aElms[i], original: attr});
            }
        }
    },

    _addEvents: function()
    {
        this._onScrollThrottled = InkEvent.throttle(Ink.bindEvent(this._onScroll, this), this._options.delay);
        if('ontouchmove' in document.documentElement && this._options.touchEvents) {
            InkEvent.observe(document.documentElement, 'touchmove', this._onScrollThrottled);
        }
        InkEvent.observe(this._options.scrollElement, 'scroll', this._onScrollThrottled);
        this._hasEvents = true;
    },

    _removeEvents: function() {
        if('ontouchmove' in document.documentElement && this._options.touchEvents) {
            InkEvent.stopObserving(document.documentElement, 'touchmove', this._onScrollThrottled);
        }
        InkEvent.stopObserving(this._options.scrollElement, 'scroll', this._onScrollThrottled);
        this._hasEvents = false;
    },

    _onScroll: function() {
        var curElm;

        for(var i=0; i < this._aData.length; i++) {
            curElm = this._aData[i];

            if(InkElement.inViewport(curElm.elm, { partial: true, margin: this._options.delta })) {
                this._elInViewport(curElm);
                if (this._options.image) {
                    /* [todo] a seemingly unrelated option creates a branch? Some of this belongs in another module. */
                    this._aData.splice(i, 1);
                    i -= 1;
                }
            }
        }

        if (this._aData.length === 0) {
            this._removeEvents();
        }
    },

    /**
     * Called when an element is detected inside the viewport
     *
     * @method _elInViewport
     * @param {LazyLoadInternalElementData} curElm
     * @private
     **/
    _elInViewport: function (curElm) {
        this._userCallback('onInsideViewport', { element: curElm.elm });

        if(this._options.image) {
            curElm.elm.setAttribute(this._options.destination, curElm.original);
            curElm.elm.removeAttribute(this._options.source);
        }

        this._userCallback('onAfterAttributeChange', { element: curElm.elm });
    },

    /**
     * Call a callback if it exists and its `typeof` is `"function"`.
     * @method _userCallback
     * @param name {String} Callback name in this._options.
     * @private
     **/
    _userCallback: function (name) {
        if (typeof this._options[name] === 'function') {
            this._options[name].apply(this, [].slice.call(arguments, 1));
        }
    },

    // API
    reload: function() {
        this._activate();
    },

    destroy: function() {
        if(this._hasEvents) {
            this._removeEvents();
        }
        UICommon.destroyComponent.call(this);
    }
};

return LazyLoad;

});
<commit_msg>documented LazyLoad<commit_after>/**
 * @module Ink.UI.LazyLoad_1
 */

Ink.createModule('Ink.UI.LazyLoad', '1', ['Ink.UI.Common_1', 'Ink.Dom.Event_1', 'Ink.Dom.Element_1'], function(UICommon, InkEvent, InkElement) {
'use strict';

var LazyLoad = function(selector, options) {
    this._init(selector, options);
};

LazyLoad.prototype = {
    /**
     * Stop the browser from loading a barrage of content at once.
     *
     * This delays the loading of images and other content until the
     * corresponding elements are visible in the browser viewport.
     *
     * This was created to load images later, but can be also used for
     * widgets which are slow to load and are only useful when on screen.
     *
     * This works through copying the `src` attribute into `data-src`,
     * and placing a `placeholder` string in the `src` attribute. Then,
     * when the element is on screen, the `data-src` attribute is copied
     * back to `src` and the content starts loading. You can use the options
     * below to change what attributes are involved in the exchange.
     *
     * You can also provide your `onInsideViewport` callback and use it to
     * start widgets which need javascript, such as an interactive map
     * or an animation.
     *
     * @class Ink.UI.LazyLoad_1
     * @constructor
     *
     * @param [rootElement] {String|DOMElement}
     *  The element which contains the lazily-loaded items.
     *
     * @param [options] Options object, containing:
     *
     * @param [options.item]='.lazyload-item'  {String}
     *  Use this to select and define what is to be considered an `item`.
     *
     * @param [options.placeholder]=null       {String}
     *  Placeholder value for items which are still outside the screen
     *  (in case they don't already have a value set)
     *
     * @param [options.source]='data-src'      {String}
     *  When an `item` is within the viewport, take the value it has
     *  in this attribute then set its `destination` attribute to it.
     *
     * @param [options.destination]='src'      {String}
     *  attribute which gets the value in `source` when the element is
     *  visible.
     *
     * @param [options.delay]=100              {Number}
     *  Wait a few milliseconds before trying to load.
     *
     * @param [options.delta]=0                {Number}
     *  Distance in px from the outside of the viewport. Elements
     *  touching within this "margin", items are considered to be inside
     *  even if they are outside the viewport limits. Can be negative if
     *  you want an element to be considered inside only when it is a
     *  certain distance into the viewport.
     *
     * @param [options.image]=true             {Boolean}
     *  Set to false to make this component do nothing to any elements
     *  and just give you the onInsideViewport callback.
     *
     * @param [options.scrollElement]=window   {Element}
     *  (advanced) What element is to be listened for the scroll event.
     *
     * @param [options.touchEvents]=true       {Boolean}
     *  Subscribe to touch events in addition to scroll events. Useful
     *  in mobile safari because 'scroll' events aren't frequent enough.
     *
     * @param [options.onInsideViewport]=false {Function}
     *  Called when an `item` is within the viewport. Receives `{ element }`
     *
     * @param [options.onAfterAttributeChange]=false {Function}
     *  (advanced) Called after `source` is copied over to
     *  `destination`. Receives `{ element }`
     *
     * @param [options.autoInit]=true         {Boolean}
     *  (advanced) Set to false if you want to start LazyLoad yourself
     *  with `reload()`
     */
    _init: function(selector) {
        this._rootElm = UICommon.elsOrSelector(selector, 'Ink.UI.LazyLoad root element')[0] || null;

        this._options = UICommon.options({
            item: ['String', '.lazyload-item'],
            placeholder: ['String', null],
            source: ['String', 'data-src'],
            destination: ['String', 'src'],
            delay: ['Number', 100],
            delta: ['Number', 0],
            image: ['Boolean', true],
            scrollElement: ['Element', window],
            touchEvents: ['Boolean', true],
            onInsideViewport: ['Function', false],
            onAfterAttributeChange: ['Function', false],
            autoInit: ['Boolean', true]
        }, arguments[1] || {}, this._rootElm);

        this._aData = [];
        this._hasEvents = false;

        if(this._options.autoInit) {
            this._activate();
        }
    },

    _activate: function()
    {
        this._getData();
        if(!this._hasEvents) {
            this._addEvents();
        }
        this._onScrollThrottled();
    },

    _getData: function()
    {
        var aElms = Ink.ss(this._options.item);
        var attr = null;
        for(var i=0, t=aElms.length; i < t; i++) {
            if (this._options.placeholder != null && !InkElement.hasAttribute(aElms[i], this._options.destination)) {
                aElms[i].setAttribute(this._options.destination, this._options.placeholder);
            }
            attr = aElms[i].getAttribute(this._options.source);
            if(attr !== null || !this._options.image) {
                this._aData.push({elm: aElms[i], original: attr});
            }
        }
    },

    _addEvents: function()
    {
        this._onScrollThrottled = InkEvent.throttle(Ink.bindEvent(this._onScroll, this), this._options.delay);
        if('ontouchmove' in document.documentElement && this._options.touchEvents) {
            InkEvent.observe(document.documentElement, 'touchmove', this._onScrollThrottled);
        }
        InkEvent.observe(this._options.scrollElement, 'scroll', this._onScrollThrottled);
        this._hasEvents = true;
    },

    _removeEvents: function() {
        if('ontouchmove' in document.documentElement && this._options.touchEvents) {
            InkEvent.stopObserving(document.documentElement, 'touchmove', this._onScrollThrottled);
        }
        InkEvent.stopObserving(this._options.scrollElement, 'scroll', this._onScrollThrottled);
        this._hasEvents = false;
    },

    _onScroll: function() {
        var curElm;

        for(var i=0; i < this._aData.length; i++) {
            curElm = this._aData[i];

            if(InkElement.inViewport(curElm.elm, { partial: true, margin: this._options.delta })) {
                this._elInViewport(curElm);
                if (this._options.image) {
                    /* [todo] a seemingly unrelated option creates a branch? Some of this belongs in another module. */
                    this._aData.splice(i, 1);
                    i -= 1;
                }
            }
        }

        if (this._aData.length === 0) {
            this._removeEvents();
        }
    },

    /**
     * Called when an element is detected inside the viewport
     *
     * @method _elInViewport
     * @param {LazyLoadInternalElementData} curElm
     * @private
     **/
    _elInViewport: function (curElm) {
        this._userCallback('onInsideViewport', { element: curElm.elm });

        if(this._options.image) {
            curElm.elm.setAttribute(this._options.destination, curElm.original);
            curElm.elm.removeAttribute(this._options.source);
        }

        this._userCallback('onAfterAttributeChange', { element: curElm.elm });
    },

    /**
     * Call a callback if it exists and its `typeof` is `"function"`.
     * @method _userCallback
     * @param name {String} Callback name in this._options.
     * @private
     **/
    _userCallback: function (name) {
        if (typeof this._options[name] === 'function') {
            this._options[name].apply(this, [].slice.call(arguments, 1));
        }
    },

    /**
     * Load or reload the component. Adding the 'scroll' event listener if
     * necessary and checks if anything needs to be lazily loaded now.
     *
     * You can use this if some elements may have appeared on screen but
     * no user scrolling happened.
     *
     * @method reload
     */
    reload: function() {
        this._activate();
    },

    /**
     * Destroy this component
     * @method destroy
     **/
    destroy: function() {
        if(this._hasEvents) {
            this._removeEvents();
        }
        UICommon.destroyComponent.call(this);
    }
};

return LazyLoad;

});
<|endoftext|>


<commit_before>/**
 * Implementation of manual annotator for image, video and audio
 *
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 23 Dec. 2018
 *
 */
function _via_manual_annotator(file, parent_html_element) {

  this.file = file;
  this.parent_container = parent_html_element;

  this.state = {};
  this.state.parent_width  = this.parent_container.clientWidth;
  this.state.parent_height = this.parent_container.clientHeight;
  this.state.content_natural_width = 0;
  this.state.content_natural_height = 0;
  this.state.content_width = 0;
  this.state.content_height = 0;
  this.state.content_scale = 1;
  this.state.content_duration = 0;

  this.config = {};
  this.config.layout = {};
  this.config.layout.content_fraction = 0.6;
  this.config.layout.control_padding = 5; // in pixel
  this.config.style = {};
  this.config.style.container = 'background-color:#212121; margin:0; padding:0; text-align:center;';
  this.config.style.content = 'border:none; margin: 0; padding:0;';
  this.config.style.control = 'border:none; margin: 0; padding:' + this.config.layout.control_padding + 'px;';

  //// initialize the container that will have content panel and control panel
  this.container = this.init_container();
  this.parent_container.appendChild(this.container);

  this.control = {};
  this.content = {};
  this.content_natural_width = 0;
  this.content_natural_height = 0;
  this.content_width = 0;
  this.content_height = 0;

  this.init_via_manual_annotator();
}

_via_manual_annotator.prototype.init_via_manual_annotator = function() {
  this.load_content(this.file).then( function(content_html_element) {
    this.content = content_html_element;

    this.init_control(this.content).then( function(control_html_element) {
      this.control = control_html_element;
      this.container.appendChild( this.content );
      this.container.appendChild( this.control );
    }.bind(this), function(err_status) {
      console.log('Control initialisation ' + err_status);
    }.bind(this));
  }.bind(this), function(err_status) {
    console.log('Content load ' + err_status + ' for uri [' + this.file.uri + ']');
  }.bind(this));
}

_via_manual_annotator.prototype.init_container = function() {
  var p = document.createElement('div');
  p.setAttribute('style', this.config.style.container);
  return p;
}

//
// Content Panel
//
_via_manual_annotator.prototype.load_content = function(file) {
  return new Promise( function(ok_callback, err_callback) {
    var el;
    if ( this.file.type === _via_file.prototype.FILE_TYPE.VIDEO ) {
      el = document.createElement('video');
      el.setAttribute('style', this.config.style.content);
      el.setAttribute('id', 'content');
      el.setAttribute('src', this.file.uri);
      el.setAttribute('autoplay', 'false');
      el.setAttribute('loop', 'false');
      el.setAttribute('controls', '');
      el.addEventListener('canplaythrough', function() {
        el.pause(); // debug
        this.state.content_natural_width = el.videoWidth;
        this.state.content_natural_height = el.videoHeight;
        this.state.content_duration = el.duration;

        var content_max_height = this.config.layout.content_fraction * this.state.parent_height;
        var cr = this.state.content_natural_width / this.state.content_natural_height;
        var ch = Math.floor( this.config.layout.content_fraction * this.state.parent_height );
        var cw = Math.floor( cr * ch );
        if ( cw > this.state.parent_width ) {
          // resize content by width because
          // content does not fit the available content panel
          cw = this.state.parent_width;
          ch = Math.floor( cw / cr );
        }
        this.state.content_width = cw;
        this.state.content_height = ch;
        el.setAttribute('width', this.state.content_width);
        el.setAttribute('height', this.state.content_height);
        ok_callback(el);
      }.bind(this));
      el.addEventListener('error', function() {
        err_callback('error');
      }.bind(this));
      el.addEventListener('abort', function() {
        err_callback('abort');
      }.bind(this));
    }
  }.bind(this));
}

//
// Control Panel
//
_via_manual_annotator.prototype.init_control = function(content) {
  return new Promise( function(ok_callback, err_callback) {
    var c = document.createElement('canvas');
    c.width = this.container.clientWidth - 2*this.config.layout.control_padding;
    c.height = this.state.parent_height - this.state.content_height  - 2*this.config.layout.control_padding;
    c.setAttribute('style', this.config.style.control);

    var ctx = c.getContext('2d');
    ctx.beginPath();
    ctx.strokeStyle = '#ffffff';
    ctx.fillStyle = '#ffffff';
    ctx.font = '12px sans';

    var char_width = ctx.measureText('M').width;
    var timepanel_x0 = char_width;
    var timepanel_y0 = c.height - 60;
    var timepanel_x1 = c.width - char_width;
    var timepanel_y1 = timepanel_y0;
    var timepanel_width = c.width - 2*char_width;
    var grating_height = char_width;

    // draw horizontal line
    ctx.moveTo(timepanel_x0, timepanel_y0);
    ctx.lineTo(timepanel_x1, timepanel_y1);

    // draw time gratings
    var grating = this.get_timepanel_gratings();
    var N = grating.time_gratings.length;
    var y0 = timepanel_y0;
    var y1 = timepanel_y0 + grating_height;
    for ( var i = 0; i < N; ++i ) {
      var x = Math.floor( timepanel_x0 + ( i * timepanel_width / (N-1) ) );
      ctx.moveTo(x, y0);
      ctx.lineTo(x, y1);
      ctx.fillText( String(grating.time_gratings[i]), x-char_width, y1+2*char_width );
    }
    ctx.fillText( grating.time_unit, timepanel_x0, y1+2*char_width );
    ctx.stroke();

    ok_callback(c);
  }.bind(this));
}

_via_manual_annotator.prototype.get_timepanel_gratings = function() {
  var duration = this.state.content_duration / 60;
  var d = { 'time_gratings':[], 'time_unit':'min' };
  if ( duration >= 30 && duration < 60 ) {
    d.time_gratings = [0,10,20,30,40,50,60];
  }
  if ( duration >= 6 && duration < 30 ) {
    d.time_gratings = [0,5,10,15,20,25,30];
  }
  if ( duration >= 1 && duration < 6 ) {
    d.time_gratings = [0,1,2,3,4,5,6];
  }
  if ( duration >= 0 && duration < 1 ) {
    d.time_gratings = [0,10,20,30,40,50,60];
    d.time_unit = 'sec';
  }
  return d;
}

_via_manual_annotator.prototype.init_time_panel = function(canvas) {

}

_via_manual_annotator.prototype.init_annotation_panel = function() {
}

_via_manual_annotator.prototype.init_toolbar_panel = function() {
}

_via_manual_annotator.prototype.init_preview_panel = function() {
}
<commit_msg>frame preview in controls are now visible<commit_after>/**
 * Implementation of manual annotator for image, video and audio
 *
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 23 Dec. 2018
 *
 */
function _via_manual_annotator(file, parent_html_element) {

  this.file = file;
  this.parent_container = parent_html_element;

  this.state = {};
  this.state.parent_width  = this.parent_container.clientWidth;
  this.state.parent_height = this.parent_container.clientHeight;
  this.state.content_natural_width = 0;
  this.state.content_natural_height = 0;
  this.state.content_width = 0;
  this.state.content_height = 0;
  this.state.content_aspect_ratio = 1;
  this.state.content_duration = 0;
  this.state.frame_preview = {'load_ongoing':false};

  this.config = {};
  this.config.layout = {};
  this.config.layout.content_fraction = 0.6;
  this.config.layout.control_padding = 5; // in pixel
  this.config.style = {};
  this.config.style.container = 'background-color:#212121; margin:0; padding:0; text-align:center;';
  this.config.style.content = 'border:none; margin: 0; padding:0;';
  this.config.style.control = 'border:none; margin: 0; padding:' + this.config.layout.control_padding + 'px;';

  //// initialize the container that will have content panel and control panel
  this.container = this.init_container();
  this.parent_container.appendChild(this.container);

  this.control = {};
  this.content = {};
  this.content_natural_width = 0;
  this.content_natural_height = 0;
  this.content_width = 0;
  this.content_height = 0;

  this.init_via_manual_annotator();
}

_via_manual_annotator.prototype.init_via_manual_annotator = function() {
  console.log('loading ...')
  this.load_content(this.file).then( function(content_html_element) {
    this.content = content_html_element;
    console.log('load done')

    this.init_control(this.content).then( function(control_html_element) {
      this.control = control_html_element;
      this.container.appendChild( this.content );
      this.container.appendChild( this.control );
    }.bind(this), function(err_status) {
      console.log('Control initialisation ' + err_status);
    }.bind(this));
  }.bind(this), function(err_status) {
    console.log('Content load ' + err_status + ' for uri [' + this.file.uri + ']');
  }.bind(this));
}

_via_manual_annotator.prototype.init_container = function() {
  var p = document.createElement('div');
  p.setAttribute('style', this.config.style.container);
  return p;
}

//
// Content Panel
//
_via_manual_annotator.prototype.load_content = function(file) {
  console.log(file)
  return new Promise( function(ok_callback, err_callback) {
    var el;
    if ( this.file.type === _via_file.prototype.FILE_TYPE.VIDEO ) {
      el = document.createElement('video');
      el.setAttribute('style', this.config.style.content);
      el.setAttribute('id', 'content');
      el.setAttribute('src', this.file.uri);
      el.setAttribute('autoplay', 'false');
      el.setAttribute('loop', 'false');
      el.setAttribute('controls', '');
      el.addEventListener('canplaythrough', function() {
        el.pause(); // debug
        this.state.content_natural_width = el.videoWidth;
        this.state.content_natural_height = el.videoHeight;
        this.state.content_duration = el.duration;

        var content_max_height = this.config.layout.content_fraction * this.state.parent_height;
        var cr = this.state.content_natural_width / this.state.content_natural_height;
        var ch = Math.floor( this.config.layout.content_fraction * this.state.parent_height );
        var cw = Math.floor( cr * ch );
        if ( cw > this.state.parent_width ) {
          // resize content by width because
          // content does not fit the available content panel
          cw = this.state.parent_width;
          ch = Math.floor( cw / cr );
        }
        this.state.content_width = cw;
        this.state.content_height = ch;
        this.state.content_aspect_ratio = ch/cw;
        el.setAttribute('width', this.state.content_width);
        el.setAttribute('height', this.state.content_height);
        ok_callback(el);
      }.bind(this));
      el.addEventListener('error', function() {
        err_callback('error');
      }.bind(this));
      el.addEventListener('abort', function() {
        err_callback('abort');
      }.bind(this));
    }
  }.bind(this));
}

//
// Control Panel
//
_via_manual_annotator.prototype.init_control = function(content) {
  return new Promise( function(ok_callback, err_callback) {
    var c = document.createElement('canvas');
    c.width = this.container.clientWidth - 2*this.config.layout.control_padding;
    c.height = this.state.parent_height - this.state.content_height  - 2*this.config.layout.control_padding;
    c.setAttribute('style', this.config.style.control);

    var ctx = c.getContext('2d', { alpha: false });
    ctx.beginPath();
    ctx.strokeStyle = '#ffffff';
    ctx.fillStyle = '#ffffff';
    ctx.font = '12px sans';
    ctx.lineWidth = 1;

    var char_width = ctx.measureText('M').width;
    var timepanel_x0 = char_width;
    var timepanel_y0 = c.height - 60;
    var timepanel_x1 = c.width - char_width;
    var timepanel_y1 = timepanel_y0;
    var timepanel_width = c.width - 2*char_width;
    var grating_height = char_width;

    // draw horizontal line
    ctx.moveTo(timepanel_x0, timepanel_y0);
    ctx.lineTo(timepanel_x1, timepanel_y1);

    // draw time gratings
    var grating = this.get_timepanel_gratings();
    var Ng = grating.time_gratings.length;
    var y0 = timepanel_y0;
    var y1 = timepanel_y0 + grating_height;
    for ( var i = 0; i < Ng; ++i ) {
      var x = Math.floor( timepanel_x0 + ( i * timepanel_width / (Ng-1) ) );
      ctx.moveTo(x, y0);
      ctx.lineTo(x, y1);
      ctx.fillText( String(grating.time_gratings[i]), x-char_width, y1+2*char_width );
    }
    ctx.fillText( grating.time_unit, timepanel_x0, y1+2*char_width );

    // draw frame preview
    var N = 10;
    var df = char_width;
    var fw = Math.floor( ( timepanel_width - (N - 1) * df ) / N );
    var fh = Math.floor(fw * this.state.content_aspect_ratio);
    console.log('N='+N+', df='+df+', fw='+fw+', fh='+fh);

    var frame_y0 = c.height - 120;
    var frame_y1 = frame_y0 + fh;
    this.state.frame_preview = { 'time':[], 'x':[], 'y':[], 'fw':fw, 'fh':fh, 'now':0 };
    for ( var i = 0; i < N; ++i ) {
      var xfmid = Math.floor(fw/2) + i*(df + fw);
      var ftime = Number.parseFloat(this.state.content_duration * xfmid / timepanel_width).toFixed(6);
      var fx0 = timepanel_x0 + xfmid - fw/2;
      var fy0 = frame_y0 - fh;
      ctx.moveTo(timepanel_x0 + xfmid, frame_y0);
      ctx.lineTo(timepanel_x0 + xfmid, frame_y1);
      ctx.rect(fx0, fy0, fw, fh);
      console.log('fx0='+fx0+', fy0='+fy0+', fw='+fw+', fh='+fh+', xfmid='+xfmid);
      this.state.frame_preview.time[i] = ftime;this.state.frame_preview.time[this.state.frame_preview.now]
      this.state.frame_preview.x[i]    = fx0;
      this.state.frame_preview.y[i]    = fy0;
    }
    ctx.stroke();

    // trigger loading of frame_preview panel
    this.state.frame_preview.load_ongoing = true;
    this.state.frame_preview.now = 0;
    this.content.pause();
    this.content.addEventListener('seeked', this.on_video_seek_done.bind(this));
    this.load_frame_preview();

    ok_callback(c);
  }.bind(this));
}

_via_manual_annotator.prototype.load_frame_preview = function() {
  if ( this.state.frame_preview.now < this.state.frame_preview.time.length ) {
    //this.content.pause();
    this.content.currentTime = this.state.frame_preview.time[this.state.frame_preview.now];
  } else {
    // stop frame_preview load process
    this.state.frame_preview.load_ongoing = false;
    this.content.removeEventListener('seeked', this.on_video_seek_done.bind(this));
  }
}

_via_manual_annotator.prototype.on_video_seek_done = function() {
  if ( this.state.frame_preview.load_ongoing ) {
    // draw the frame and trigger another seek
    var ctx = this.control.getContext('2d', { alpha: false });
    var fi = this.state.frame_preview.now;

    ctx.drawImage( this.content,
                   0, 0,
                   this.state.content_natural_width,
                   this.state.content_natural_height,
                   this.state.frame_preview.x[fi],
                   this.state.frame_preview.y[fi],
                   this.state.frame_preview.fw,
                   this.state.frame_preview.fh
                 );
    console.log('0,0,'+this.state.content_width+','+this.state.content_height+' : '+this.state.frame_preview.x[fi]+','+this.state.frame_preview.y[fi]+','+this.state.frame_preview.fw+','+this.state.frame_preview.fh)
    this.state.frame_preview.now = this.state.frame_preview.now + 1;
    this.load_frame_preview(); // keep loading remaining frames
  }
}

_via_manual_annotator.prototype.get_timepanel_gratings = function() {
  var duration = this.state.content_duration / 60;
  var d = { 'time_gratings':[], 'time_unit':'min' };
  if ( duration >= 30 && duration < 60 ) {
    d.time_gratings = [0,10,20,30,40,50,60];
  }
  if ( duration >= 6 && duration < 30 ) {
    d.time_gratings = [0,5,10,15,20,25,30];
  }
  if ( duration >= 1 && duration < 6 ) {
    d.time_gratings = [0,1,2,3,4,5,6];
  }
  if ( duration >= 0 && duration < 1 ) {
    d.time_gratings = [0,10,20,30,40,50,60];
    d.time_unit = 'sec';
  }
  return d;
}

_via_manual_annotator.prototype.init_time_panel = function(canvas) {

}

_via_manual_annotator.prototype.init_annotation_panel = function() {
}

_via_manual_annotator.prototype.init_toolbar_panel = function() {
}

_via_manual_annotator.prototype.init_preview_panel = function() {
}
<|endoftext|>


<commit_before>//  Background.js is a bit of a dumping ground for code which needs a permanent housing spot.
define([
    'background/commands',
    'background/collection/searchResults',
    'background/collection/streamItems',
    'background/collection/playlists',
    'background/model/player',
    'background/model/settings',
    'background/model/user',
    'background/model/buttons/nextButton',
    'background/model/buttons/previousButton',
    'background/model/buttons/playPauseButton',
    'background/model/buttons/playPauseButton',
    'background/model/buttons/shuffleButton',
    'background/model/buttons/repeatButton',
    'background/model/buttons/radioButton',
    'background/model/contextMenus',
    'background/model/clientError',
    'background/model/iconManager',
    'background/model/omnibox',
    'background/model/user',
    'background/view/clipboardView'
], function (Commands, SearchResults, StreamItems, Playlists, Player, Settings, User, NextButton, PreviousButton, PlayPauseButton, ShuffleButton, RepeatButton, RadioButton) {
    'use strict';

    window.clearResultsTimeout = null;
    var tenSeconds = 10000;

    //  The foreground has to be able to call this whenever a view opens.
    window.stopClearResultsTimer = function () {
        clearTimeout(window.clearResultsTimeout);
    };

    //  It's important to write this to the background page because the foreground gets destroyed so it couldn't possibly remember it.
    window.startClearResultsTimer = function () {
        //  Safe-guard against multiple setTimeouts, just incase.
        window.stopClearResultsTimer();

        window.clearResultsTimeout = setTimeout(function () {
            Settings.set('searchQuery', '');
            SearchResults.reset();
        }, tenSeconds);
    };

    //  I know this sucks. It's because of a 'bug' in chrome extensions where foreground can't reliably unsubscribe all of its events so the background has to be responsible for it. :(
    window.unbindViewEvents = function (foregroundViewType, foregroundBehaviorType) {
        var collectionsToUnbind = [StreamItems, SearchResults, Playlists];
        var allToUnbind = [Player, User, Settings, NextButton, PreviousButton, PlayPauseButton, ShuffleButton, RepeatButton, RadioButton];

        _.each(collectionsToUnbind, function (collectionToUnbind) {
            allToUnbind.push(collectionToUnbind);

            collectionToUnbind.each(function (modelToUnbind) {
                allToUnbind.push(modelToUnbind);
            });
        });

        _.each(allToUnbind, function (toUnbind) {

            var viewContexts = [];

            _.each(toUnbind._events, function (eventGroup) {

                _.each(_.toArray(eventGroup), function (event) {

                    if (event.ctx instanceof foregroundViewType || event.ctx instanceof foregroundBehaviorType) {
                        if (viewContexts.indexOf(event.ctx) === -1) {
                            viewContexts.push(event.ctx);
                        }
                    }

                });

            });

            _.each(viewContexts, function (viewContext) {
                viewContext.stopListening();
            });

            viewContexts.length = 0;
        });

    };

});<commit_msg>WOOP FIXED THE MEMORY LEAK v0.129 GO GO GO<commit_after>//  Background.js is a bit of a dumping ground for code which needs a permanent housing spot.
define([
    'background/commands',
    'background/collection/searchResults',
    'background/collection/streamItems',
    'background/collection/playlists',
    'background/model/player',
    'background/model/settings',
    'background/model/user',
    'background/model/buttons/nextButton',
    'background/model/buttons/previousButton',
    'background/model/buttons/playPauseButton',
    'background/model/buttons/playPauseButton',
    'background/model/buttons/shuffleButton',
    'background/model/buttons/repeatButton',
    'background/model/buttons/radioButton',
    'background/model/contextMenus',
    'background/model/clientError',
    'background/model/iconManager',
    'background/model/omnibox',
    'background/model/user',
    'background/view/clipboardView'
], function (Commands, SearchResults, StreamItems, Playlists, Player, Settings, User, NextButton, PreviousButton, PlayPauseButton, ShuffleButton, RepeatButton, RadioButton) {
    'use strict';

    window.clearResultsTimeout = null;
    var tenSeconds = 10000;

    //  The foreground has to be able to call this whenever a view opens.
    window.stopClearResultsTimer = function () {
        clearTimeout(window.clearResultsTimeout);
    };

    //  It's important to write this to the background page because the foreground gets destroyed so it couldn't possibly remember it.
    window.startClearResultsTimer = function () {
        //  Safe-guard against multiple setTimeouts, just incase.
        window.stopClearResultsTimer();

        window.clearResultsTimeout = setTimeout(function () {
            Settings.set('searchQuery', '');
            SearchResults.reset();
        }, tenSeconds);
    };

    //  I know this sucks. It's because of a 'bug' in chrome extensions where foreground can't reliably unsubscribe all of its events so the background has to be responsible for it. :(
    window.unbindViewEvents = function (foregroundViewType, foregroundBehaviorType) {
        var collectionsToUnbind = [Playlists, SearchResults, StreamItems];
        var allToUnbind = [Player, User, Settings, NextButton, PlayPauseButton, PreviousButton, RadioButton, RepeatButton, ShuffleButton];

        _.each(collectionsToUnbind, function (collectionToUnbind) {
            allToUnbind.push(collectionToUnbind);

            collectionToUnbind.each(function (modelToUnbind) {
                allToUnbind.push(modelToUnbind);

                //  Be sure to add every PlaylistItem in every Playlist.
                if (modelToUnbind.has('items')) {
                    modelToUnbind.get('items').each(function (playlistItem) {
                        allToUnbind.push(playlistItem);
                    });
                }
            });
        });

        var viewContexts = [];

        _.each(allToUnbind, function (toUnbind) {
            _.each(toUnbind._events, function(eventGroup) {
                _.each(_.toArray(eventGroup), function (event) {
                    if (event.ctx instanceof foregroundViewType || event.ctx instanceof foregroundBehaviorType) {
                        if (viewContexts.indexOf(event.ctx) === -1) {
                            viewContexts.push(event.ctx);
                        }
                    }
                });
            });
        });

        _.each(viewContexts, function (viewContext) {
            viewContext.stopListening();
        });

        viewContexts.length = 0;
    };

});<|endoftext|>


<commit_before>function smallestPowerOfTwoGreaterOrEqual(a){
  let ans = 1;
  while(ans<a) ans <<= 1;
  return ans;
};

 /**
 * @constructor
 */
function CanvasWrapper(canvas) {
  this.canvas = canvas;
  this.sizeX = canvas.width;
  this.sizeY = canvas.height;
  this.sizeXP = smallestPowerOfTwoGreaterOrEqual(this.sizeX);
  this.sizeYP = smallestPowerOfTwoGreaterOrEqual(this.sizeY);
  this.ratio = canvas.height/canvas.width;
  this.it = 0;
 //black default texture @TODO: copy texture from canvas
    var pixels = [];

		for ( var i = 0; i < this.sizeXP; i++) {
			for ( var j = 0; j < this.sizeYP; j++) {
				pixels.push(0, 0, 0, 255);
			}
		}

  let rawData = can_use_texture_float ? new Float32Array(pixels) : new Uint8Array(pixels);

  //framebuffers and textures
  this.textures = [];
  this.framebuffers = [];

  canvas['drawTo'] = this.drawTo.bind(this);
  canvas['cdyUpdate'] = this.copyTextureToCanvas.bind(this);

  for(let j = 0; j<2; j++) {
    this.textures[j] = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, this.textures[j]);
    gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); //TODO?

    //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.sizeXP, this.sizeYP, 0, gl.RGBA, gl.UNSIGNED_BYTE, rawData);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.sizeXP, this.sizeYP, 0, gl.RGBA, can_use_texture_float ? gl.FLOAT : gl.UNSIGNED_BYTE, rawData);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

    this.framebuffers[j] = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[j]);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.textures[j], 0);
  }

  this.shaderProgram = new ShaderProgram(gl, cgl_resources["copytexture_v"], cgl_resources["copytexture_f"]);
  var posBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);

	var vertices = new Float32Array([ -1, -1, 0, 1, -1, 0, -1, 1, 0, 1, 1, 0 ]);

	var aPosLoc = gl.getAttribLocation(this.shaderProgram.handle, "aPos");
	gl.enableVertexAttribArray(aPosLoc);

	var aTexLoc = gl.getAttribLocation(this.shaderProgram.handle, "aTexCoord");
	gl.enableVertexAttribArray(aTexLoc);

	var texCoords = new Float32Array([ 0, 0, 1, 0, 0, 1, 1, 1 ]);

	var texCoordOffset = vertices.byteLength;

	gl.bufferData(gl.ARRAY_BUFFER, texCoordOffset + texCoords.byteLength, gl.STATIC_DRAW);
	gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices);
	gl.bufferSubData(gl.ARRAY_BUFFER, texCoordOffset, texCoords);
	gl.vertexAttribPointer(aPosLoc, 3, gl.FLOAT, false, 0, 0);
	gl.vertexAttribPointer(aTexLoc, 2, gl.FLOAT, false, 0, texCoordOffset);

};

/** @type {Array.<WebGLTexture>} */
CanvasWrapper.prototype.textures;

/** @type {Array.<WebGLFramebuffer>} */
CanvasWrapper.prototype.framebuffers;

/** @type {number} */
CanvasWrapper.prototype.sizeX;

/** @type {number} */
CanvasWrapper.prototype.sizeY;

/** @type {number} */
CanvasWrapper.prototype.ratio;

/** @type {HTMLCanvasElement|Element} */
CanvasWrapper.prototype.canvas;

/** What is the current index of the rendered frame
 *@type {number} */
CanvasWrapper.prototype.it;

/** @type {ShaderProgram} */
CanvasWrapper.prototype.shaderProgram;

/**
 * runs a gl.bindTexture(  gl.TEXTURE_2D,...) for sampling purposes
 */
CanvasWrapper.prototype.bindTexture = function() {
  gl.bindTexture(gl.TEXTURE_2D, this.textures[this.it]);
};

/**
 * runs a gl.bindFramebuffer (required before rendering) and updates
 */
CanvasWrapper.prototype.bindFramebuffer = function() {
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[this.it^1]);
  this.it ^= 1;
};

CanvasWrapper.prototype.copyTextureToCanvas = function() {
  let context = this.canvas.getContext('2d');

  //Copy things from glcanvas to the cindyjs-canvas representing that canvas
  context.clearRect(0, 0, this.sizeX, this.sizeY);
  this.drawTo(context, 0, 0);
}

CanvasWrapper.prototype.drawTo = function(context, x, y) {
  //TODO: render texture this.textures[this.it] on glcanvas
  glcanvas.width  = this.sizeX;
  glcanvas.height = this.sizeY;
	gl.viewport(0, 0, this.sizeXP, this.sizeYP);

	this.shaderProgram.use(gl);

  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, this.textures[this.it]);
  this.shaderProgram.uniform["sampler"](0);

	gl.bindFramebuffer(gl.FRAMEBUFFER, null); //renders to glcanvas
	gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
	gl.flush();
  context.drawImage(glcanvas, x, y);
};

/**
 * sets pixel at absolute coordinate x and y to color; both on canvas and on this.textures[this.it]
 *
 */
CanvasWrapper.prototype.setPixel = function(x, y, color) {
    this.bindTexture();

    let color255 = [color[0]*255, color[1]*255, color[2]*255, 255];
    gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, 1, 1,
                     gl.RGBA, gl.UNSIGNED_BYTE,
                     new Uint8Array(color255));

    let context = this.canvas.getContext('2d');
    let id = context.createImageData(1,1); // only do this once per page
    id.data.d = color255;
    context.putImageData( id, x, y );
};
<commit_msg>crop image before copying (relevant for IE, not tested)<commit_after>function smallestPowerOfTwoGreaterOrEqual(a){
  let ans = 1;
  while(ans<a) ans <<= 1;
  return ans;
};

 /**
 * @constructor
 */
function CanvasWrapper(canvas) {
  this.canvas = canvas;
  this.sizeX = canvas.width;
  this.sizeY = canvas.height;
  this.sizeXP = smallestPowerOfTwoGreaterOrEqual(this.sizeX);
  this.sizeYP = smallestPowerOfTwoGreaterOrEqual(this.sizeY);
  this.ratio = canvas.height/canvas.width;
  this.it = 0;
 //black default texture @TODO: copy texture from canvas
    var pixels = [];

		for ( var i = 0; i < this.sizeXP; i++) {
			for ( var j = 0; j < this.sizeYP; j++) {
				pixels.push(0, 0, 0, 255);
			}
		}

  let rawData = can_use_texture_float ? new Float32Array(pixels) : new Uint8Array(pixels);

  //framebuffers and textures
  this.textures = [];
  this.framebuffers = [];

  canvas['drawTo'] = this.drawTo.bind(this);
  canvas['cdyUpdate'] = this.copyTextureToCanvas.bind(this);

  for(let j = 0; j<2; j++) {
    this.textures[j] = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, this.textures[j]);
    gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); //TODO?

    //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.sizeXP, this.sizeYP, 0, gl.RGBA, gl.UNSIGNED_BYTE, rawData);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.sizeXP, this.sizeYP, 0, gl.RGBA, can_use_texture_float ? gl.FLOAT : gl.UNSIGNED_BYTE, rawData);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

    this.framebuffers[j] = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[j]);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.textures[j], 0);
  }

  this.shaderProgram = new ShaderProgram(gl, cgl_resources["copytexture_v"], cgl_resources["copytexture_f"]);
  var posBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);

	var vertices = new Float32Array([ -1, -1, 0, 1, -1, 0, -1, 1, 0, 1, 1, 0 ]);

	var aPosLoc = gl.getAttribLocation(this.shaderProgram.handle, "aPos");
	gl.enableVertexAttribArray(aPosLoc);

	var aTexLoc = gl.getAttribLocation(this.shaderProgram.handle, "aTexCoord");
	gl.enableVertexAttribArray(aTexLoc);

	var texCoords = new Float32Array([ 0, 0, 1, 0, 0, 1, 1, 1 ]);

	var texCoordOffset = vertices.byteLength;

	gl.bufferData(gl.ARRAY_BUFFER, texCoordOffset + texCoords.byteLength, gl.STATIC_DRAW);
	gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices);
	gl.bufferSubData(gl.ARRAY_BUFFER, texCoordOffset, texCoords);
	gl.vertexAttribPointer(aPosLoc, 3, gl.FLOAT, false, 0, 0);
	gl.vertexAttribPointer(aTexLoc, 2, gl.FLOAT, false, 0, texCoordOffset);

};

/** @type {Array.<WebGLTexture>} */
CanvasWrapper.prototype.textures;

/** @type {Array.<WebGLFramebuffer>} */
CanvasWrapper.prototype.framebuffers;

/** @type {number} */
CanvasWrapper.prototype.sizeX;

/** @type {number} */
CanvasWrapper.prototype.sizeY;

/** @type {number} */
CanvasWrapper.prototype.ratio;

/** @type {HTMLCanvasElement|Element} */
CanvasWrapper.prototype.canvas;

/** What is the current index of the rendered frame
 *@type {number} */
CanvasWrapper.prototype.it;

/** @type {ShaderProgram} */
CanvasWrapper.prototype.shaderProgram;

/**
 * runs a gl.bindTexture(  gl.TEXTURE_2D,...) for sampling purposes
 */
CanvasWrapper.prototype.bindTexture = function() {
  gl.bindTexture(gl.TEXTURE_2D, this.textures[this.it]);
};

/**
 * runs a gl.bindFramebuffer (required before rendering) and updates
 */
CanvasWrapper.prototype.bindFramebuffer = function() {
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[this.it^1]);
  this.it ^= 1;
};

CanvasWrapper.prototype.copyTextureToCanvas = function() {
  let context = this.canvas.getContext('2d');

  //Copy things from glcanvas to the cindyjs-canvas representing that canvas
  context.clearRect(0, 0, this.sizeX, this.sizeY);
  this.drawTo(context, 0, 0);
}

CanvasWrapper.prototype.drawTo = function(context, x, y) {
  //TODO: render texture this.textures[this.it] on glcanvas
  glcanvas.width  = this.sizeX;
  glcanvas.height = this.sizeY;
	gl.viewport(0, 0, this.sizeXP, this.sizeYP);

	this.shaderProgram.use(gl);

  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, this.textures[this.it]);
  this.shaderProgram.uniform["sampler"](0);

	gl.bindFramebuffer(gl.FRAMEBUFFER, null); //renders to glcanvas
	gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
	gl.flush();
  context.drawImage(glcanvas, 0, 0, this.sizeX, this.sizeY, x, y, this.sizeX, this.sizeY);
};

/**
 * sets pixel at absolute coordinate x and y to color; both on canvas and on this.textures[this.it]
 *
 */
CanvasWrapper.prototype.setPixel = function(x, y, color) {
    this.bindTexture();

    let color255 = [color[0]*255, color[1]*255, color[2]*255, 255];
    gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, 1, 1,
                     gl.RGBA, gl.UNSIGNED_BYTE,
                     new Uint8Array(color255));

    let context = this.canvas.getContext('2d');
    let id = context.createImageData(1,1); // only do this once per page
    id.data.d = color255;
    context.putImageData( id, x, y );
};
<|endoftext|>