<commit_before>define([
	"dojo/_base/declare", "./TabViewerBase", "dojo/on", "dojo/topic",
	"dojo/dom-class", "dijit/layout/ContentPane", "dojo/dom-construct",
	"../formatter", "../TabContainer", "../GenomeOverview",
	"dojo/request", "dojo/_base/lang", "../FeatureGridContainer", "../SpecialtyGeneGridContainer",
	"../ActionBar", "../ContainerActionBar", "../PathwaysContainer", "../ProteinFamiliesContainer",
	"../DiseaseContainer", "../PublicationGridContainer", "../CircularViewerContainer",
	"../TranscriptomicsContainer", "../InteractionsContainer", "../Phylogeny", "../GenomeBrowser",
	"../SequenceGridContainer", "../../util/PathJoin"
], function(declare, TabViewerBase, on, Topic,
			domClass, ContentPane, domConstruct,
			formatter, TabContainer, GenomeOverview,
			xhr, lang, FeatureGridContainer, SpecialtyGeneGridContainer,
			ActionBar, ContainerActionBar, PathwaysContainer, ProteinFamiliesContainer,
			DiseaseContainer, PublicationGridContainer, CircularViewerContainer,
			TranscriptomicsContainer, InteractionsContainer, Phylogeny, GenomeBrowser,
			SequenceGridContainer, PathJoin){
	return declare([TabViewerBase], {
		"baseClass": "GenomeGroup",
		"disabled": false,
		"query": null,
		containerType: "genome_group",
		genome_id: "",
		apiServiceUrl: window.App.dataAPI,
		perspectiveLabel: "Genome View",
		perspectiveIconClass: "icon-perspective-Genome",

		_setGenome_idAttr: function(id){
			// console.log("_setGenome_IDAttr: ", id, this.genome_id);
			if(!id){
				return;
			}

			if(this.genome_id == id){
				// console.log("Genome ID Already Set");
				return;
			}
			var state = this.state = this.state || {};
			this.genome_id = this.state.genome_id = id;
			this.state.genome_ids = [id];

			xhr.get(PathJoin(this.apiServiceUrl, "genome", id), {
				headers: {
					accept: "application/json",
					'X-Requested-With': null,
					'Authorization': (window.App.authorizationToken || "")
				},
				handleAs: "json"
			}).then(lang.hitch(this, function(genome){
				this.set("genome", genome)
			}));

		},

		setActivePanelState: function(){
			var activeQueryState;
			// console.log("Set ActivePanelState: ", JSON.stringify(this.state,null,4));
			if(this.state.genome_id){
				activeQueryState = lang.mixin({}, this.state, {search: "eq(genome_id," + this.state.genome_id + ")"});
			}
			var active = (this.state && this.state.hashParams && this.state.hashParams.view_tab) ? this.state.hashParams.view_tab : "overview";
			var activeTab = this[active];
			// console.log("Active Panel: ", active);

			switch(active){
				case "phylogeny":
				case "overview":
					if(this.state.genome){
						activeTab.set("state", lang.mixin({}, this.state));
					}
					break;
				case "transcriptomics":
					activeTab.set("state", lang.mixin({}, this.state, {search: "eq(genome_ids," + this.genome_id + ")"}));
					break;
				default:
					if(activeQueryState){
						// console.log("Using Default ActiveQueryState: ", activeQueryState);
						activeTab.set("state", activeQueryState);
					}else{
						console.log("Missing Active Query State for: ", active)
					}
					break;
			}
		},

		buildHeaderContent: function(genome){
			var taxon_lineage_names = genome.taxon_lineage_names.slice(1);
			var taxon_lineage_ids = genome.taxon_lineage_ids.slice(1);
			var out = taxon_lineage_names.map(function(id, idx){
				return '' + id + '';
			});
			return out.join(" » ");
		},

		_setGenomeAttr: function(genome){
			var state = this.state || {};

			this.state.genome = genome;

			// this.viewHeader.set("content", this.buildHeaderContent(genome));

			this.queryNode.innerHTML = this.buildHeaderContent(genome);
			domConstruct.empty(this.totalCountNode);
			// var active = (state && state.hashParams && state.hashParams.view_tab) ? state.hashParams.view_tab : "overview";
			// var activeTab = this[active];

			// switch(active){
			// 	case "phylogeny":
			// 	case "overview":
			// 		activeTab.set("state", state);
			// 		break;
			// 	default:
			// 		break;
			// }

			this._set("genome", genome);
			this.setActivePanelState();
			this.resize();
		},

		createOverviewPanel: function(){
			return new GenomeOverview({
				title: "Genome Overview",
				style: "overflow:auto;",
				id: this.viewer.id + "_" + "overview",
				state: this.state
			});
		},

		onSetState: function(attr, oldState, state){

			var parts = state.pathname.split("/");
			this.set("genome_id", parts[parts.length - 1]);
			state.genome_id = parts[parts.length - 1];
			state.genome_ids = [state.genome_id];
			if(!state){
				return;
			}

			// console.log("Genome: ", state.genome, state.genome_id)

			if(state && state.genome_id && !state.genome){
				// console.log("No state.genome.  state.genome_id: ", state.genome_id);
				if(oldState && oldState.genome_id){
					// console.log("oldState.genome_id: ", oldState.genome_id)

					if((state.genome_id == oldState.genome_id)){
						if(oldState.genome || this.genome){
							// console.log("oldState Genome: ", oldState.genome || this.genome);
							state.genome = oldState.genome || this.genome;
						}else{
							console.log("oldState missing Genome");
						}
					}
				}
			}

			if(state.hashParams && state.hashParams.view_tab){

				if(this[state.hashParams.view_tab]){
					var vt = this[state.hashParams.view_tab];
					vt.set("visible", true);
					this.viewer.selectChild(vt);
				}else{
					// console.log("No view-tab supplied in State Object");
				}
			}

			this.setActivePanelState();

			// console.log("viewer/Genome onSetState() after set genome_id")
		},

		postCreate: function(){
			if(!this.state){
				this.state = {};
			}

			this.inherited(arguments);

			this.overview = this.createOverviewPanel();
			this.phylogeny = new Phylogeny({
				title: "Phylogeny",
				id: this.viewer.id + "_" + "phylogeny"
			});

			this.sequences = new SequenceGridContainer({
				title: "Sequences",
				id: this.viewer.id + "_" + "sequences",
				state: lang.mixin({}, this.state, {search: "?eq(genome_id," + this.genome_id + ")"})
			});

			this.features = new FeatureGridContainer({
				title: "Features",
				id: this.viewer.id + "_" + "features",
				state: lang.mixin({}, this.state, {search: "?eq(genome_id," + this.genome_id + ")"})
			});

			this.browser = new GenomeBrowser({
				title: "Browser",
				id: this.viewer.id + "_" + "browser",
				state: lang.mixin({}, this.state)
			});

			this.circular = new CircularViewerContainer({
				title: "Circular Viewer",
				id: this.viewer.id + "_" + "circular",
				state: lang.mixin({}, this.state)
			});

			this.specialtyGenes = new SpecialtyGeneGridContainer({
				title: "Specialty Genes",
				id: this.viewer.id + "_" + "specialtyGenes",
				state: lang.mixin({}, this.state, {search: "?eq(genome_id," + this.genome_id + ")"})
			});
			this.pathways = new PathwaysContainer({
				apiServer: this.apiServiceUrl,
				title: "Pathways",
				id: this.viewer.id + "_" + "pathways",
				state: this.state
			});
			this.proteinFamilies = new ProteinFamiliesContainer({
				title: "Protein Families",
				id: this.viewer.id + "_" + "proteinFamilies",
				state: this.state
			});
			this.transcriptomics = new TranscriptomicsContainer({
				title: "Transcriptomics",
				id: this.viewer.id + "_" + "transcriptomics",
				state: this.state
			});
			this.viewer.addChild(this.overview);
			this.viewer.addChild(this.phylogeny);
			this.viewer.addChild(this.browser);
			this.viewer.addChild(this.circular);
			this.viewer.addChild(this.sequences);
			this.viewer.addChild(this.features);
			this.viewer.addChild(this.specialtyGenes);
			this.viewer.addChild(this.proteinFamilies);
			this.viewer.addChild(this.pathways);
			this.viewer.addChild(this.transcriptomics);
		}
	});
});
<commit_msg>add genome name to breadcrumb.  fixes #798<commit_after>define([
	"dojo/_base/declare", "./TabViewerBase", "dojo/on", "dojo/topic",
	"dojo/dom-class", "dijit/layout/ContentPane", "dojo/dom-construct",
	"../formatter", "../TabContainer", "../GenomeOverview",
	"dojo/request", "dojo/_base/lang", "../FeatureGridContainer", "../SpecialtyGeneGridContainer",
	"../ActionBar", "../ContainerActionBar", "../PathwaysContainer", "../ProteinFamiliesContainer",
	"../DiseaseContainer", "../PublicationGridContainer", "../CircularViewerContainer",
	"../TranscriptomicsContainer", "../InteractionsContainer", "../Phylogeny", "../GenomeBrowser",
	"../SequenceGridContainer", "../../util/PathJoin"
], function(declare, TabViewerBase, on, Topic,
			domClass, ContentPane, domConstruct,
			formatter, TabContainer, GenomeOverview,
			xhr, lang, FeatureGridContainer, SpecialtyGeneGridContainer,
			ActionBar, ContainerActionBar, PathwaysContainer, ProteinFamiliesContainer,
			DiseaseContainer, PublicationGridContainer, CircularViewerContainer,
			TranscriptomicsContainer, InteractionsContainer, Phylogeny, GenomeBrowser,
			SequenceGridContainer, PathJoin){
	return declare([TabViewerBase], {
		"baseClass": "GenomeGroup",
		"disabled": false,
		"query": null,
		containerType: "genome_group",
		genome_id: "",
		apiServiceUrl: window.App.dataAPI,
		perspectiveLabel: "Genome View",
		perspectiveIconClass: "icon-perspective-Genome",

		_setGenome_idAttr: function(id){
			// console.log("_setGenome_IDAttr: ", id, this.genome_id);
			if(!id){
				return;
			}

			if(this.genome_id == id){
				// console.log("Genome ID Already Set");
				return;
			}
			var state = this.state = this.state || {};
			this.genome_id = this.state.genome_id = id;
			this.state.genome_ids = [id];

			xhr.get(PathJoin(this.apiServiceUrl, "genome", id), {
				headers: {
					accept: "application/json",
					'X-Requested-With': null,
					'Authorization': (window.App.authorizationToken || "")
				},
				handleAs: "json"
			}).then(lang.hitch(this, function(genome){
				this.set("genome", genome)
			}));

		},

		setActivePanelState: function(){
			var activeQueryState;
			// console.log("Set ActivePanelState: ", JSON.stringify(this.state,null,4));
			if(this.state.genome_id){
				activeQueryState = lang.mixin({}, this.state, {search: "eq(genome_id," + this.state.genome_id + ")"});
			}
			var active = (this.state && this.state.hashParams && this.state.hashParams.view_tab) ? this.state.hashParams.view_tab : "overview";
			var activeTab = this[active];
			// console.log("Active Panel: ", active);

			switch(active){
				case "phylogeny":
				case "overview":
					if(this.state.genome){
						activeTab.set("state", lang.mixin({}, this.state));
					}
					break;
				case "transcriptomics":
					activeTab.set("state", lang.mixin({}, this.state, {search: "eq(genome_ids," + this.genome_id + ")"}));
					break;
				default:
					if(activeQueryState){
						// console.log("Using Default ActiveQueryState: ", activeQueryState);
						activeTab.set("state", activeQueryState);
					}else{
						console.log("Missing Active Query State for: ", active)
					}
					break;
			}
		},

		buildHeaderContent: function(genome){
			var taxon_lineage_names = genome.taxon_lineage_names.slice(1);
			var taxon_lineage_ids = genome.taxon_lineage_ids.slice(1);
			var out = taxon_lineage_names.map(function(id, idx){
				return '' + id + '';
			});
			return out.join(" » ") + " » " + genome.genome_name;
		},

		_setGenomeAttr: function(genome){
			var state = this.state || {};

			this.state.genome = genome;

			// this.viewHeader.set("content", this.buildHeaderContent(genome));

			this.queryNode.innerHTML = this.buildHeaderContent(genome);
			domConstruct.empty(this.totalCountNode);
			// var active = (state && state.hashParams && state.hashParams.view_tab) ? state.hashParams.view_tab : "overview";
			// var activeTab = this[active];

			// switch(active){
			// 	case "phylogeny":
			// 	case "overview":
			// 		activeTab.set("state", state);
			// 		break;
			// 	default:
			// 		break;
			// }

			this._set("genome", genome);
			this.setActivePanelState();
			this.resize();
		},

		createOverviewPanel: function(){
			return new GenomeOverview({
				title: "Genome Overview",
				style: "overflow:auto;",
				id: this.viewer.id + "_" + "overview",
				state: this.state
			});
		},

		onSetState: function(attr, oldState, state){

			var parts = state.pathname.split("/");
			this.set("genome_id", parts[parts.length - 1]);
			state.genome_id = parts[parts.length - 1];
			state.genome_ids = [state.genome_id];
			if(!state){
				return;
			}

			// console.log("Genome: ", state.genome, state.genome_id)

			if(state && state.genome_id && !state.genome){
				// console.log("No state.genome.  state.genome_id: ", state.genome_id);
				if(oldState && oldState.genome_id){
					// console.log("oldState.genome_id: ", oldState.genome_id)

					if((state.genome_id == oldState.genome_id)){
						if(oldState.genome || this.genome){
							// console.log("oldState Genome: ", oldState.genome || this.genome);
							state.genome = oldState.genome || this.genome;
						}else{
							console.log("oldState missing Genome");
						}
					}
				}
			}

			if(state.hashParams && state.hashParams.view_tab){

				if(this[state.hashParams.view_tab]){
					var vt = this[state.hashParams.view_tab];
					vt.set("visible", true);
					this.viewer.selectChild(vt);
				}else{
					// console.log("No view-tab supplied in State Object");
				}
			}

			this.setActivePanelState();

			// console.log("viewer/Genome onSetState() after set genome_id")
		},

		postCreate: function(){
			if(!this.state){
				this.state = {};
			}

			this.inherited(arguments);

			this.overview = this.createOverviewPanel();
			this.phylogeny = new Phylogeny({
				title: "Phylogeny",
				id: this.viewer.id + "_" + "phylogeny"
			});

			this.sequences = new SequenceGridContainer({
				title: "Sequences",
				id: this.viewer.id + "_" + "sequences",
				state: lang.mixin({}, this.state, {search: "?eq(genome_id," + this.genome_id + ")"})
			});

			this.features = new FeatureGridContainer({
				title: "Features",
				id: this.viewer.id + "_" + "features",
				state: lang.mixin({}, this.state, {search: "?eq(genome_id," + this.genome_id + ")"})
			});

			this.browser = new GenomeBrowser({
				title: "Browser",
				id: this.viewer.id + "_" + "browser",
				state: lang.mixin({}, this.state)
			});

			this.circular = new CircularViewerContainer({
				title: "Circular Viewer",
				id: this.viewer.id + "_" + "circular",
				state: lang.mixin({}, this.state)
			});

			this.specialtyGenes = new SpecialtyGeneGridContainer({
				title: "Specialty Genes",
				id: this.viewer.id + "_" + "specialtyGenes",
				state: lang.mixin({}, this.state, {search: "?eq(genome_id," + this.genome_id + ")"})
			});
			this.pathways = new PathwaysContainer({
				apiServer: this.apiServiceUrl,
				title: "Pathways",
				id: this.viewer.id + "_" + "pathways",
				state: this.state
			});
			this.proteinFamilies = new ProteinFamiliesContainer({
				title: "Protein Families",
				id: this.viewer.id + "_" + "proteinFamilies",
				state: this.state
			});
			this.transcriptomics = new TranscriptomicsContainer({
				title: "Transcriptomics",
				id: this.viewer.id + "_" + "transcriptomics",
				state: this.state
			});
			this.viewer.addChild(this.overview);
			this.viewer.addChild(this.phylogeny);
			this.viewer.addChild(this.browser);
			this.viewer.addChild(this.circular);
			this.viewer.addChild(this.sequences);
			this.viewer.addChild(this.features);
			this.viewer.addChild(this.specialtyGenes);
			this.viewer.addChild(this.proteinFamilies);
			this.viewer.addChild(this.pathways);
			this.viewer.addChild(this.transcriptomics);
		}
	});
});
<|endoftext|>


<commit_before>const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');
const mock = require('mock-require');
const log = require('npmlog');
const path = require('path');

const link = require('../src/link');

log.level = 'silent';

describe('link', () => {

  beforeEach(() => {
    delete require.cache[require.resolve('../src/link')];
  });

  // that test suite has to be changed in next PR so we have
  // fixed behaviour for `getProjectConfig` that's already in master
  it('should return when run in a folder without package.json', () => {
    const spy = sinon.spy(log, 'error');

    const config = {
      getProjectConfig: () => {
        throw new Error('No package.json found');
      },
    };

    link(config);

    expect(spy.calledWith('ERRPACKAGEJSON')).to.be.true;
  });

  it('should accept a name of a dependency to link', (done) => {
    const config = {
      getProjectConfig: () => ({ assets: [] }),
      getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }),
    };

    link(config, ['react-native-gradient'], () => {
      expect(
        config.getDependencyConfig.calledWith('react-native-gradient')
      ).to.be.true;
      done();
    });
  });

  it('should read dependencies from package.json when name not provided', (done) => {
    const config = {
      getProjectConfig: () => ({ assets: [] }),
      getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }),
    };

    mock(
      path.join(process.cwd(), 'package.json'),
      {
        dependencies: {
          'react-native-test': '*',
        },
      }
    );

    link(config, [], () => {
      expect(
        config.getDependencyConfig.calledWith('react-native-test')
      ).to.be.true;
      done();
    });
  });

  it('should register native module when android/ios projects are present', () => {
    const registerNativeModule = sinon.stub();
    const config = {
      getProjectConfig: () => ({ android: {}, ios: {}, assets: [] }),
      getDependencyConfig: sinon.stub().returns({ android: {}, ios: {}, assets: [], commands: {} }),
    };

    mock(
      '../src/android/registerNativeModule.js',
      registerNativeModule
    );

    mock(
      '../src/ios/registerNativeModule.js',
      registerNativeModule
    );

    const link = require('../src/link');

    link(config, ['react-native-blur'], () => {
      expect(registerNativeModule.calledTwice).to.be.true;
    });
  });

  it('should copy assets from both project and dependencies projects', (done) => {
    const dependencyAssets = ['Fonts/Font.ttf'];
    const projectAssets = ['Fonts/FontC.ttf'];
    const copyAssets = sinon.stub();

    mock(
      '../src/ios/copyAssets.js',
      copyAssets
    );

    const config = {
      getProjectConfig: () => ({ ios: {}, assets: projectAssets }),
      getDependencyConfig: sinon.stub().returns({ assets: dependencyAssets, commands: {} }),
    };

    const link = require('../src/link');

    link(config, ['react-native-blur'], () => {
      expect(copyAssets.calledOnce).to.be.true;
      expect(copyAssets.getCall(0).args[0]).to.deep.equals(
        projectAssets.concat(dependencyAssets)
      );
      done();
    });
  });

  it('should remove duplicated assets before copying them', (done) => {
    const copyAssets = sinon.stub();
    const dependencyAssets = ['Fonts/FontB.ttf'];
    const projectAssets = ['Fonts/FontB.ttf', 'Fonts/FontA.ttf'];

    mock(
      '../src/ios/copyAssets.js',
      copyAssets
    );

    const config = {
      getProjectConfig: () => ({ ios: {}, assets: projectAssets }),
      getDependencyConfig: sinon.stub().returns({ assets: dependencyAssets, commands: {} }),
    };

    const link = require('../src/link');

    link(config, ['react-native-blur'], () => {
      expect(copyAssets.calledOnce).to.be.true;
      expect(copyAssets.getCall(0).args[0]).to.deep.equals(projectAssets);
      done();
    });
  });

  afterEach(() => {
    mock.stopAll();
  });

});
<commit_msg>Add 0.17 / 0.18 detection algorythm tests<commit_after>const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');
const mock = require('mock-require');
const log = require('npmlog');
const path = require('path');

const link = require('../src/link');

log.level = 'silent';

describe('link', () => {

  beforeEach(() => {
    delete require.cache[require.resolve('../src/link')];
  });

  // that test suite has to be changed in next PR so we have
  // fixed behaviour for `getProjectConfig` that's already in master
  it('should return when run in a folder without package.json', () => {
    const spy = sinon.spy(log, 'error');

    const config = {
      getProjectConfig: () => {
        throw new Error('No package.json found');
      },
    };

    link(config);

    expect(spy.calledWith('ERRPACKAGEJSON')).to.be.true;
  });

  it('should accept a name of a dependency to link', (done) => {
    const config = {
      getProjectConfig: () => ({ assets: [] }),
      getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }),
    };

    link(config, ['react-native-gradient'], () => {
      expect(
        config.getDependencyConfig.calledWith('react-native-gradient')
      ).to.be.true;
      done();
    });
  });

  it('should read dependencies from package.json when name not provided', (done) => {
    const config = {
      getProjectConfig: () => ({ assets: [] }),
      getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }),
    };

    mock(
      path.join(process.cwd(), 'package.json'),
      {
        dependencies: {
          'react-native-test': '*',
        },
      }
    );

    link(config, [], () => {
      expect(
        config.getDependencyConfig.calledWith('react-native-test')
      ).to.be.true;
      done();
    });
  });

  it('should register native module when android/ios projects are present', () => {
    const registerNativeModule = sinon.stub();
    const config = {
      getProjectConfig: () => ({ android: {}, ios: {}, assets: [] }),
      getDependencyConfig: sinon.stub().returns({ android: {}, ios: {}, assets: [], commands: {} }),
    };

    mock(
      '../src/android/registerNativeModule.js',
      registerNativeModule
    );

    mock(
      '../src/ios/registerNativeModule.js',
      registerNativeModule
    );

    const link = require('../src/link');

    link(config, ['react-native-blur'], () => {
      expect(registerNativeModule.calledTwice).to.be.true;
    });
  });

  it('should copy assets from both project and dependencies projects', (done) => {
    const dependencyAssets = ['Fonts/Font.ttf'];
    const projectAssets = ['Fonts/FontC.ttf'];
    const copyAssets = sinon.stub();

    mock(
      '../src/ios/copyAssets.js',
      copyAssets
    );

    const config = {
      getProjectConfig: () => ({ ios: {}, assets: projectAssets }),
      getDependencyConfig: sinon.stub().returns({ assets: dependencyAssets, commands: {} }),
    };

    const link = require('../src/link');

    link(config, ['react-native-blur'], () => {
      expect(copyAssets.calledOnce).to.be.true;
      expect(copyAssets.getCall(0).args[0]).to.deep.equals(
        projectAssets.concat(dependencyAssets)
      );
      done();
    });
  });

  it('should remove duplicated assets before copying them', (done) => {
    const copyAssets = sinon.stub();
    const dependencyAssets = ['Fonts/FontB.ttf'];
    const projectAssets = ['Fonts/FontB.ttf', 'Fonts/FontA.ttf'];

    mock(
      '../src/ios/copyAssets.js',
      copyAssets
    );

    const config = {
      getProjectConfig: () => ({ ios: {}, assets: projectAssets }),
      getDependencyConfig: sinon.stub().returns({ assets: dependencyAssets, commands: {} }),
    };

    const link = require('../src/link');

    link(config, ['react-native-blur'], () => {
      expect(copyAssets.calledOnce).to.be.true;
      expect(copyAssets.getCall(0).args[0]).to.deep.equals(projectAssets);
      done();
    });
  });

  it('require a specific patch for react-native < 0.18', () => {
    const spy = sinon.spy();
    mock('node_modules/react-native/package.json', {
      version: '0.17.0',
    });

    mock('../src/android/patches/0.17', spy);
    const registerAndroid = require('../src/android/registerNativeModule');

    registerAndroid({}, {}, { folder: './' });
    expect(spy.calledOnce).to.be.true;
  });

  it('require a specific patch for react-native > 0.18', () => {
    const spy = sinon.spy();
    mock('node_modules/react-native/package.json', {
      version: '0.18.0',
    });

    mock('../src/android/patches/0.18', spy);
    const registerAndroid = require('../src/android/registerNativeModule');

    registerAndroid({}, {}, { folder: './' });
    expect(spy.calledOnce).to.be.true;
  });

  afterEach(() => {
    mock.stopAll();
  });

});
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import { Text, View, StyleSheet, ActivityIndicator, TouchableOpacity} from 'react-native';
import TopBar from '../components/TopBar';
import ServiceList from '../components/ServiceList';
import Bluetooth from 'react-native-bluetooth';
import { getAppState, setAppState } from '../lib/GlobalState';

const DeviceDetail = React.createClass({
  propTypes: {
    navigator: PropTypes.func.isRequired,
  },

  getInitialState() {
    const { selectedDevice, isConnected } = getAppState();
    this.unsubscribe = () => {};

    return {
      device: selectedDevice,
      error: null,
      services: [],
      isConnected: isConnected || false,
      connectionInProgress: false,
    };
  },

  componentWillMount() {
  },

  componentWillUnmount() {
    this.unsubscribe();
  },

  connect() {
    if (this.state.isConnected) {
      this.setState({
        isConnected: false,
        connectionInProgress: true,
      });

      Bluetooth.disconnect(this.state.device)
      .then(() => {
        setAppState({ isConnected: false });

        this.setState({
          isConnected: false,
          connectionInProgress: false,
          services: [],
        });
      });

      return;
    }

    setAppState({ isConnected: false });
    this.setState({
      isConnected: false,
      connectionInProgress: true,
    });

    Bluetooth.connect(this.state.device)
    .then(() => {
      setAppState({ isConnected: true });

      this.setState({
        isConnected: true,
        connectionInProgress: false,
      });

      return Bluetooth.discoverServices(this.state.device, null, service => {
          this.setState({
            services: [...this.state.services, service]
          });
        });
    })
    .then(unsubscribe => this.unsubscribe = unsubscribe)
    .catch(error => {
      this.setState({
        error: error,
      });
    });
  },

  serviceSelected(service) {
    setAppState({
      selectedService: service,
    });

    this.props.navigator('ServiceDetail');
  },

  goBack() {
    Bluetooth.disconnect(this.state.device);

    setAppState({
      selectedDevice: null,
    });

    this.props.navigator('DeviceDiscovery');
  },

  renderError() {
    if (this.state.error == null) return null;

    return (<Text style={styles.errorText}>{this.state.error}</Text>);
  },

  renderStatus() {
    if (this.state.connectionInProgress) {
      return <ActivityIndicator animating={true} />;
    }

    return (
      <View style={styles.statusContainer}>
        <TouchableOpacity onPress={this.connect}>
          <Text style={styles.statusText}>{this.state.isConnected ? 'Disconnect' : 'Connect'}</Text>
        </TouchableOpacity>
      </View>
    );
  },

  render() {
    return (
      <View style={styles.container}>
        <TopBar
          headerText={"Device - " + this.state.device.name}
          backAction={ this.goBack } />
        {this.renderError()}
        {this.renderStatus()}
        <View style={styles.listContainer}>
          <ServiceList services={this.state.services} selectService={this.serviceSelected} />
        </View>
      </View>
    );
  },
});

const styles = StyleSheet.create({
  errorText: {
    fontSize: 18,
    color: 'red',
  },
  statusText: {
    fontSize: 20,
    color: '#00AFEE',
  },
  container: {
    flex: 1,
  },
  listContainer: {
    flex: 1,
  },
  statusContainer: {
    flexDirection: 'row',
    justifyContent: 'center',
    marginTop: 5,
    marginBottom: 15,
  },
});

export default DeviceDetail;
<commit_msg>Make sure global connection is set when going back from device detail screen.<commit_after>import React, { PropTypes } from 'react';
import { Text, View, StyleSheet, ActivityIndicator, TouchableOpacity} from 'react-native';
import TopBar from '../components/TopBar';
import ServiceList from '../components/ServiceList';
import Bluetooth from 'react-native-bluetooth';
import { getAppState, setAppState } from '../lib/GlobalState';

const DeviceDetail = React.createClass({
  propTypes: {
    navigator: PropTypes.func.isRequired,
  },

  getInitialState() {
    const { selectedDevice, isConnected } = getAppState();
    this.unsubscribe = () => {};

    return {
      device: selectedDevice,
      error: null,
      services: [],
      isConnected: isConnected || false,
      connectionInProgress: false,
    };
  },

  componentWillMount() {
  },

  componentWillUnmount() {
    this.unsubscribe();
  },

  connect() {
    if (this.state.isConnected) {
      this.setState({
        isConnected: false,
        connectionInProgress: true,
      });

      Bluetooth.disconnect(this.state.device)
      .then(() => {
        setAppState({ isConnected: false });

        this.setState({
          isConnected: false,
          connectionInProgress: false,
          services: [],
        });
      });

      return;
    }

    setAppState({ isConnected: false });
    this.setState({
      isConnected: false,
      connectionInProgress: true,
    });

    Bluetooth.connect(this.state.device)
    .then(() => {
      setAppState({ isConnected: true });

      this.setState({
        isConnected: true,
        connectionInProgress: false,
      });

      return Bluetooth.discoverServices(this.state.device, null, service => {
          this.setState({
            services: [...this.state.services, service]
          });
        });
    })
    .then(unsubscribe => this.unsubscribe = unsubscribe)
    .catch(error => {
      this.setState({
        error: error,
      });
    });
  },

  serviceSelected(service) {
    setAppState({
      selectedService: service,
    });

    this.props.navigator('ServiceDetail');
  },

  goBack() {
    Bluetooth.disconnect(this.state.device);

    setAppState({
      selectedDevice: null,
      isConnected: false,
    });

    this.props.navigator('DeviceDiscovery');
  },

  renderError() {
    if (this.state.error == null) return null;

    return (<Text style={styles.errorText}>{this.state.error}</Text>);
  },

  renderStatus() {
    if (this.state.connectionInProgress) {
      return <ActivityIndicator animating={true} />;
    }

    return (
      <View style={styles.statusContainer}>
        <TouchableOpacity onPress={this.connect}>
          <Text style={styles.statusText}>{this.state.isConnected ? 'Disconnect' : 'Connect'}</Text>
        </TouchableOpacity>
      </View>
    );
  },

  render() {
    return (
      <View style={styles.container}>
        <TopBar
          headerText={"Device - " + this.state.device.name}
          backAction={ this.goBack } />
        {this.renderError()}
        {this.renderStatus()}
        <View style={styles.listContainer}>
          <ServiceList services={this.state.services} selectService={this.serviceSelected} />
        </View>
      </View>
    );
  },
});

const styles = StyleSheet.create({
  errorText: {
    fontSize: 18,
    color: 'red',
  },
  statusText: {
    fontSize: 20,
    color: '#00AFEE',
  },
  container: {
    flex: 1,
  },
  listContainer: {
    flex: 1,
  },
  statusContainer: {
    flexDirection: 'row',
    justifyContent: 'center',
    marginTop: 5,
    marginBottom: 15,
  },
});

export default DeviceDetail;
<|endoftext|>


<commit_before>define([
    'jquery',
    'underscore',
    'backbone',
    'solmiBasics',
    'helpers/helpers',
    'models/playCommon',
    'models/frequenciesTuner',
    'views/forms',
    'views/additional'
], function ($, _, Backbone, sb, helpers, playCommon, frequenciesTuner, forms, viewsAdd) {

    $('#div1').after(forms.randomForm());
    $('#div1').after(viewsAdd.inputBaseToneLength());
    $('#div1').after(forms.selectFieldForRepetition());
    $('#div1').after(forms.selectFieldForInstruments());
    $('#div1').after(forms.selectFieldForCentralViewScale());

    for (var i = 1; i <= 2; i++) {
        $('.squares-3x3 .row:nth-child(' + i + ') div:first-child').css({'visibility': 'hidden'});
    }

    $('#solmi-strings form .go').click(function () {
        console.log('scalesCurrent after click go: ', sb.scalesCurrent);
//        try {
            playCommon.prepareForPlay(this);
//        } catch (e) {
//            console.error(e.message);
//            return false;
//        }
//        return false;
    });

    $('form#random .go').click(function () {
        console.log('scalesCurrent after click form#random: ', sb.scalesCurrent);
//        try {
            playCommon.prepareForPlay(this, 'random');
//        } catch (e) {
//            console.error(e.message);
//            return false;
//        }
//        return false;
    });

    $('#central-view-scale').change(function () {
        $('#all-squares .row div').css({'background-image': "none"});
        sb.centralViewScale = parseInt($(this).val());
        sb.scalesCurrent = helpers.setScalesCurrent(sb.scaleRange, sb.centralViewScale, sb.scalesCurrent);
    });

    $('#instruments').change(function () {
        sb.currentInstrument = $(this).val();
    });

    $('#repetition').change(function () {
        sb.remainingNumberOfPlaying = sb.currentNumberOfPlaying = parseInt($(this).val());
    });

    $('#base-tone-length').change(function () {
        sb.baseToneLength = parseInt($(this).val());
    });

    $('.sound-keys').change(function () {// later: remove this, action only if form is clicked
        var soundKey = $(this).val();
        sb.setSoundKeyCurrent(soundKey);
    });

    $('#frequencies-string2').after(forms.frequenciesForm);
    $('form.frequencies .start').click(function () {

        try {
            if (frequenciesTuner.tunerActive === false) {
                frequenciesTuner.tunerActive = true;
                frequenciesTuner.startTuner();
            } else {
                frequenciesTuner.tunerShowData = true;
            }
        } catch (e) {
            console.error(e.message);
            return false;
        }
        return false;
    });
    $('form.frequencies .stop').click(function () {
        try {
            frequenciesTuner.tunerShowData = false;
console.log('solmiMain .stop frequenciesTuner', frequenciesTuner);
        } catch (e) {
            console.error(e.message);
            return false;
        }
        return false;
    });
    $('form.frequencies .reset').click(function () {
        try {
            $('#frequencies-string2').empty();
        } catch (e) {
            console.error(e.message);
            return false;
        }
        return false;
    });
//    if (hasGetUserMedia()) {
//        alert('Has getUserMedia!');
//    } else {
//        alert('getUserMedia() is not supported in your browser');
//    }

    return {};
});<commit_msg>Prevent unwanted reload of page.<commit_after>define([
    'jquery',
    'underscore',
    'backbone',
    'solmiBasics',
    'helpers/helpers',
    'models/playCommon',
    'models/frequenciesTuner',
    'views/forms',
    'views/additional'
], function ($, _, Backbone, sb, helpers, playCommon, frequenciesTuner, forms, viewsAdd) {

    $('#div1').after(forms.randomForm());
    $('#div1').after(viewsAdd.inputBaseToneLength());
    $('#div1').after(forms.selectFieldForRepetition());
    $('#div1').after(forms.selectFieldForInstruments());
    $('#div1').after(forms.selectFieldForCentralViewScale());

    for (var i = 1; i <= 2; i++) {
        $('.squares-3x3 .row:nth-child(' + i + ') div:first-child').css({'visibility': 'hidden'});
    }

    $('#solmi-strings form input').keypress(function (event) {
         event.preventDefault();
    });

    $('#solmi-strings form .go').click(function () {
        console.log('scalesCurrent after click go: ', sb.scalesCurrent);
//        try {
            playCommon.prepareForPlay(this);
//        } catch (e) {
//            console.error(e.message);
//            return false;
//        }
//        return false;
    });

    $('form#random .go').click(function () {
        console.log('scalesCurrent after click form#random: ', sb.scalesCurrent);
//        try {
            playCommon.prepareForPlay(this, 'random');
//        } catch (e) {
//            console.error(e.message);
//            return false;
//        }
//        return false;
    });

    $('#central-view-scale').change(function () {
        $('#all-squares .row div').css({'background-image': "none"});
        sb.centralViewScale = parseInt($(this).val());
        sb.scalesCurrent = helpers.setScalesCurrent(sb.scaleRange, sb.centralViewScale, sb.scalesCurrent);
    });

    $('#instruments').change(function () {
        sb.currentInstrument = $(this).val();
    });

    $('#repetition').change(function () {
        sb.remainingNumberOfPlaying = sb.currentNumberOfPlaying = parseInt($(this).val());
    });

    $('#base-tone-length').change(function () {
        sb.baseToneLength = parseInt($(this).val());
    });

    $('.sound-keys').change(function () {// later: remove this, action only if form is clicked
        var soundKey = $(this).val();
        sb.setSoundKeyCurrent(soundKey);
    });

    $('#frequencies-string2').after(forms.frequenciesForm);
    $('form.frequencies .start').click(function () {

        try {
            if (frequenciesTuner.tunerActive === false) {
                frequenciesTuner.tunerActive = true;
                frequenciesTuner.startTuner();
            } else {
                frequenciesTuner.tunerShowData = true;
            }
        } catch (e) {
            console.error(e.message);
            return false;
        }
        return false;
    });
    $('form.frequencies .stop').click(function () {
        try {
            frequenciesTuner.tunerShowData = false;
console.log('solmiMain .stop frequenciesTuner', frequenciesTuner);
        } catch (e) {
            console.error(e.message);
            return false;
        }
        return false;
    });
    $('form.frequencies .reset').click(function () {
        try {
            $('#frequencies-string2').empty();
        } catch (e) {
            console.error(e.message);
            return false;
        }
        return false;
    });
//    if (hasGetUserMedia()) {
//        alert('Has getUserMedia!');
//    } else {
//        alert('getUserMedia() is not supported in your browser');
//    }

    return {};
});<|endoftext|>


<commit_before>/* global app:true, io:false */

var socket;
var myname;
var block_id;
var blockData;
var ledData;
var ledTime;
var imageData;
var imageTime;
var timeline;

(function() {
  'use strict';

  //var blockdata = [];
  //var qs = new Querystring();
  //var block_id = qs.get("myBlock");

  socket = io.connect();
  socket.on('connect', function(){
  	socket.emit('/replay/#join');
  });

  socket.on('/replay/#postdata', function(data){
  	blockData = [];
	ledData = [];
	ledTime = [];
	blockData.push(data[0]); // time
	blockData.push(data[1]); // owner
	blockData.push(data[2]); // experiment id
	blockData.push(data[3]); // pattern id

	// image dir = 171.65.102.132:3001/blockid/

	for (var i=0;i<data[4].length/2;i++)
	{
	  ledData.push(data[4][i*2]);
	  ledTime.push(Math.round(parseInt(data[4][i*2+1])/100)*100);
	}

	var info_p_0 = document.createElement("p");
	var info_node_0 = document.createTextNode("Block ID: "+block_id);
	var info_node_1 = document.createTextNode("Block Start Time: "+data[0]);
	var info_node_2 = document.createTextNode("Owner : "+data[1]);
	var info_node_3 = document.createTextNode("Experiment : experiment id "+data[2]);
	var info_node_4 = document.createTextNode("Pattern : "+ (data[3] == 0 ? "Freeform Experiment" : "pattern id "+data[3]));
	info_p.appendChild(info_node_0);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_1);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_2);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_3);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_4);

	var info_box_div = document.getElementById('info_box');
	info_box_div.appendChild(info_p);

	console.log(ledData);
	console.log(ledData.length);
	console.log(ledTime);
	console.log(ledTime.length);

	socket.emit('/replay/#callimglist', { targetBlock: block_id});
  });

  socket.on('/replay/#postimglist', function(data){
	imageData = [];
	imageTime = [];
	timeline = [];
	data.forEach(function(filename){
	    var res = filename.split(".");
	    imageTime.push(Math.round(parseInt(res[0])/100)*100);
	    imageData.push(filename);
	});

	console.log(imageData);
	console.log(imageData.length);
	console.log(imageTime);
	console.log(imageTime.length);

	setupCanvas();
	getVideo(imageData[0]);
	//var path = '../../Dropbox/live-gallery/'+targetBlock;
  });

  socket.on('/replay/#newUser', function(user) {
      myname = user;
      console.log('>>> DB connected' + block_id);
  });

  socket.on('message', function (message) {
  	console.log(message);
  });

  socket.on('disconnect', function() {
	console.log('>>> DB disconnected');
  });

  app = app || {};

  app.Reset = Backbone.Model.extend({
    defaults: {
      success: false,
      errors: [],
      errfor: {},
      blockid: undefined,
      confitm: ''
    },
    url: function(){
      return '/account/replay/' + this.get('block_id') +'/';
    }
  });

  app.MovieView = Backbone.View.extend({
    el: '#playback_nav',
    template: _.template( $('#tmpl-movie_nav').html() ),
    events: {
      'click .btn-play': 'reqPlay',
      'click .btn-stop': 'reqStop',
      'click .btn-first': 'reqFirstFrame'
    },
    initialize: function() {
      //this.model = new app.Blocks();
      //this.listenTo(this.model, 'sync', this.render);
      this.render();
    },
    render: function() {
      this.$el.html(this.template);
    },
    reqPlay: function() {
      console.log("PLAY");
    },
    reqStop: function() {
      console.log("STOP");
    },
    reqFirstFrame: function() {
      console.log("1st frame");
    }
  });

  app.Router = Backbone.Router.extend({
    routes: {
      'account/replay/': 'default',
      'account/replay/load/:block/': 'load'
    },
    default: function(){
      console.log(" >>default view, nothing loaded");
    },
    load: function(block) {
      console.log(">> loading block "+block);
      block_id = block;
      socket.emit('/replay/#callblock', { targetBlock: block});
    }
  });

  $(document).ready(function() {
    app.movieView = new app.MovieView();
    app.router = new app.Router();
    Backbone.history.start({ pushState: true });
  });

/////////////////////////////
// Canvas setup functions
/////////////////////////////

var 	shape_bg,
	shape_stage,
	g_ledL,
	g_ledR,
	g_ledU,
	g_ledD,
	led_L,
	led_R,
	led_U,
	led_D,
	vid_canvas,
	svg_led,
	vid_context;

function setupCanvas() { // called in init
	vid_canvas = d3.select("#canvasArea").append("canvas")
		.attr("class", "display-canvas")
		.attr("width", vid_width+40)
		.attr("height", vid_height+40)
		.style("position", "absolute")
		.style("top", 0)
		.style("left", 0);

	svg_led = d3.select("#canvasArea").append("svg:svg")
		.attr("width", vid_width+40)
		.attr("height", vid_height+40)
		.style("position", "absolute")
		.style("top", 0)
		.style("left", 0);

	vid_context = vid_canvas.node().getContext("2d");

	led_L = svg_led.append("svg:g").
		append("svg:polygon")
		.attr("points", "26,230 66,260 26,290")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	led_R = svg_led.append("svg:g")
		.append("svg:polygon")
		.attr("points", "654,230 654,290 614,260")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	led_U = svg_led.append("svg:g")
		.append("svg:polygon")
		.attr("points", "310,26 370,26 340,66")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	led_D = svg_led.append("svg:g")
		.append("svg:polygon")
		.attr("points", "310,494 340,454 370,494")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	//window.setInterval(getVideo, 1000/10);

	/*d3.timer(function(){
		joystick_draw();
		drawObjects();
	});*/
}

function resetCanvas(e) { // on resize events
    max_val = (document.getElementById("joystickArea").offsetWidth-100)/2;
    console.log('max_val :' + max_val);

    // resize the canvas - this clears the canvas
    control_canvas.width = document.getElementById("joystickArea").offsetWidth-20;
    control_canvas.height = control_canvas.width;
    halfWidth = (control_canvas.width)/2;
    halfHeight = (control_canvas.height)/2;

    rect_joy = control_canvas.getBoundingClientRect();

    // make sure we scroll to the top left.
    window.scrollTo(0, 0);
}

var 	vid_width = 640,
	vid_height = 480,
	m = 20,
	degrees = 180 / Math.PI;

function drawObjects(){
	led_U.style("opacity",arrow.int1);
	led_L.style("opacity",arrow.int2);
	led_D.style("opacity",arrow.int3);
	led_R.style("opacity",arrow.int4);
}

function drawLEDArrow(arrow){

}

function getVideo(frame_img_name){
	getVidFrame("http://171.65.102.132:3001/" + block_id +"/"+ frame_img_name, function(image) {
		vid_context.clearRect(20, 20, vid_width+20, vid_height+20);
		vid_context.drawImage(image, 20, 20, vid_width, vid_height);
	});
}

function getVidFrame(path, callback) {
	var image = new Image;
	image.src = path;
	image.onload = function() {
		callback(image);
	    //console.log(new Date().getTime());
	};
}

}());
<commit_msg>info box<commit_after>/* global app:true, io:false */

var socket;
var myname;
var block_id;
var blockData;
var ledData;
var ledTime;
var imageData;
var imageTime;
var timeline;

(function() {
  'use strict';

  //var blockdata = [];
  //var qs = new Querystring();
  //var block_id = qs.get("myBlock");

  socket = io.connect();
  socket.on('connect', function(){
  	socket.emit('/replay/#join');
  });

  socket.on('/replay/#postdata', function(data){
  	blockData = [];
	ledData = [];
	ledTime = [];
	blockData.push(data[0]); // time
	blockData.push(data[1]); // owner
	blockData.push(data[2]); // experiment id
	blockData.push(data[3]); // pattern id

	// image dir = 171.65.102.132:3001/blockid/

	for (var i=0;i<data[4].length/2;i++)
	{
	  ledData.push(data[4][i*2]);
	  ledTime.push(Math.round(parseInt(data[4][i*2+1])/100)*100);
	}

	var info_p = document.createElement("p");
	var info_node_0 = document.createTextNode("Block ID: "+block_id);
	var info_node_1 = document.createTextNode("Block Start Time: "+data[0]);
	var info_node_2 = document.createTextNode("Owner : "+data[1]);
	var info_node_3 = document.createTextNode("Experiment : experiment id "+data[2]);
	var info_node_4 = document.createTextNode("Pattern : "+ (data[3] == 0 ? "Freeform Experiment" : "pattern id "+data[3]));
	info_p.appendChild(info_node_0);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_1);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_2);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_3);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_4);

	var info_box_div = document.getElementById('info_box');
	info_box_div.appendChild(info_p);

	console.log(ledData);
	console.log(ledData.length);
	console.log(ledTime);
	console.log(ledTime.length);

	socket.emit('/replay/#callimglist', { targetBlock: block_id});
  });

  socket.on('/replay/#postimglist', function(data){
	imageData = [];
	imageTime = [];
	timeline = [];
	data.forEach(function(filename){
	    var res = filename.split(".");
	    imageTime.push(Math.round(parseInt(res[0])/100)*100);
	    imageData.push(filename);
	});

	console.log(imageData);
	console.log(imageData.length);
	console.log(imageTime);
	console.log(imageTime.length);

	setupCanvas();
	getVideo(imageData[0]);
	//var path = '../../Dropbox/live-gallery/'+targetBlock;
  });

  socket.on('/replay/#newUser', function(user) {
      myname = user;
      console.log('>>> DB connected' + block_id);
  });

  socket.on('message', function (message) {
  	console.log(message);
  });

  socket.on('disconnect', function() {
	console.log('>>> DB disconnected');
  });

  app = app || {};

  app.Reset = Backbone.Model.extend({
    defaults: {
      success: false,
      errors: [],
      errfor: {},
      blockid: undefined,
      confitm: ''
    },
    url: function(){
      return '/account/replay/' + this.get('block_id') +'/';
    }
  });

  app.MovieView = Backbone.View.extend({
    el: '#playback_nav',
    template: _.template( $('#tmpl-movie_nav').html() ),
    events: {
      'click .btn-play': 'reqPlay',
      'click .btn-stop': 'reqStop',
      'click .btn-first': 'reqFirstFrame'
    },
    initialize: function() {
      //this.model = new app.Blocks();
      //this.listenTo(this.model, 'sync', this.render);
      this.render();
    },
    render: function() {
      this.$el.html(this.template);
    },
    reqPlay: function() {
      console.log("PLAY");
    },
    reqStop: function() {
      console.log("STOP");
    },
    reqFirstFrame: function() {
      console.log("1st frame");
    }
  });

  app.Router = Backbone.Router.extend({
    routes: {
      'account/replay/': 'default',
      'account/replay/load/:block/': 'load'
    },
    default: function(){
      console.log(" >>default view, nothing loaded");
    },
    load: function(block) {
      console.log(">> loading block "+block);
      block_id = block;
      socket.emit('/replay/#callblock', { targetBlock: block});
    }
  });

  $(document).ready(function() {
    app.movieView = new app.MovieView();
    app.router = new app.Router();
    Backbone.history.start({ pushState: true });
  });

/////////////////////////////
// Canvas setup functions
/////////////////////////////

var 	shape_bg,
	shape_stage,
	g_ledL,
	g_ledR,
	g_ledU,
	g_ledD,
	led_L,
	led_R,
	led_U,
	led_D,
	vid_canvas,
	svg_led,
	vid_context;

function setupCanvas() { // called in init
	vid_canvas = d3.select("#canvasArea").append("canvas")
		.attr("class", "display-canvas")
		.attr("width", vid_width+40)
		.attr("height", vid_height+40)
		.style("position", "absolute")
		.style("top", 0)
		.style("left", 0);

	svg_led = d3.select("#canvasArea").append("svg:svg")
		.attr("width", vid_width+40)
		.attr("height", vid_height+40)
		.style("position", "absolute")
		.style("top", 0)
		.style("left", 0);

	vid_context = vid_canvas.node().getContext("2d");

	led_L = svg_led.append("svg:g").
		append("svg:polygon")
		.attr("points", "26,230 66,260 26,290")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	led_R = svg_led.append("svg:g")
		.append("svg:polygon")
		.attr("points", "654,230 654,290 614,260")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	led_U = svg_led.append("svg:g")
		.append("svg:polygon")
		.attr("points", "310,26 370,26 340,66")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	led_D = svg_led.append("svg:g")
		.append("svg:polygon")
		.attr("points", "310,494 340,454 370,494")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	//window.setInterval(getVideo, 1000/10);

	/*d3.timer(function(){
		joystick_draw();
		drawObjects();
	});*/
}

function resetCanvas(e) { // on resize events
    max_val = (document.getElementById("joystickArea").offsetWidth-100)/2;
    console.log('max_val :' + max_val);

    // resize the canvas - this clears the canvas
    control_canvas.width = document.getElementById("joystickArea").offsetWidth-20;
    control_canvas.height = control_canvas.width;
    halfWidth = (control_canvas.width)/2;
    halfHeight = (control_canvas.height)/2;

    rect_joy = control_canvas.getBoundingClientRect();

    // make sure we scroll to the top left.
    window.scrollTo(0, 0);
}

var 	vid_width = 640,
	vid_height = 480,
	m = 20,
	degrees = 180 / Math.PI;

function drawObjects(){
	led_U.style("opacity",arrow.int1);
	led_L.style("opacity",arrow.int2);
	led_D.style("opacity",arrow.int3);
	led_R.style("opacity",arrow.int4);
}

function drawLEDArrow(arrow){

}

function getVideo(frame_img_name){
	getVidFrame("http://171.65.102.132:3001/" + block_id +"/"+ frame_img_name, function(image) {
		vid_context.clearRect(20, 20, vid_width+20, vid_height+20);
		vid_context.drawImage(image, 20, 20, vid_width, vid_height);
	});
}

function getVidFrame(path, callback) {
	var image = new Image;
	image.src = path;
	image.onload = function() {
		callback(image);
	    //console.log(new Date().getTime());
	};
}

}());
<|endoftext|>


<commit_before>var p2 = require('p2');
var _ = require('lodash');
var config = require('./config');

function Sim(gameState) {

    this.gameState = gameState;

    this.scoreHandler = _.noop;
    this.destroyBlockHandler = _.noop;

    // var self = this;
    // setInterval( function() { self.scoreHandler(); }, 500 );

    this.reset();

    this.initBlocks(gameState.grid);

    // simulation

    this.fixedTimeStep = 1/60;
    this.maxSubSteps = 10;
    this.lastTime = process.hrtime();

}

Sim.prototype.initBlocks = function SimInitBlocks(grid) {
    var y = grid.length;
    while (y--) {
        var row = grid[y];
        var x = row.length;
        while (x--) {
            if (row[x] > 0) {
                this.addBlock(x, y);
            }
        }
    }
};

Sim.prototype.update = function SimUpdate() {

    var deltaTime = process.hrtime(this.lastTime);

    this.lastTime = process.hrtime();

    this.world.step( this.fixedTimeStep, deltaTime[1]/1e9, this.maxSubSteps );

    this.pendingRemoval.forEach(this.world.removeBody.bind(this.world));
    this.pendingRemoval = [];

    this.gameState.disc.pos.x = this.discBody.interpolatedPosition[0];
    this.gameState.disc.pos.y = this.discBody.interpolatedPosition[1];
    this.gameState.disc.angle = this.discBody.interpolatedAngle;;
    this.gameState.disc.vel.x = this.discBody.velocity[0];
    this.gameState.disc.vel.y = this.discBody.velocity[1];
};

Sim.prototype.reset = function SimReset() {
    // set up p2-based physics simulation

    this.pendingRemoval = [];

    this.world = new p2.World({
        islandSplit: false,
    });
    this.world.applyGravity = false;
    this.world.applySpringForces = false;
    this.world.applyDamping = false;
    this.world.on('beginContact', this.handleCollision.bind(this));

    // create materials with no friction and perfect bounce

    this.discMaterial = new p2.Material();
    this.bounceMaterial = new p2.Material();

    var bounceContactMaterial = new p2.ContactMaterial(this.bounceMaterial, this.discMaterial, {
        friction : 0.0,
        restitution: 1.0 + config.DISC.BOUNCE_SPEEDUP,
    });
    bounceContactMaterial.stiffness = 1e12;
    // bounceContactMaterial.relaxation = 1;
    this.world.addContactMaterial(bounceContactMaterial);

    // Add a disc

    var discShape = new p2.Circle({ radius: config.DISC.DIAMETER/2 });
    discShape.material = this.discMaterial;
    this.discBody = new p2.Body({
        mass:1,
        position:[0, 0],
        angularVelocity: 0,
        angularDamping: 0,
        damping: 0,
        fixedRotation: true,
        velocity: [Math.random()*20 - 10, Math.random()*20 - 10],
        allowSleep: false,
    });
    this.discBody.customType = 'disc';
    this.discBody.addShape(discShape);
    this.world.addBody(this.discBody);

    // add some walls

    var topWall    = this.makeWall( [0, config.GRID.HEIGHT/2 + config.GRID.WALL_DEPTH/2],  [config.GRID.WIDTH*2, config.GRID.WALL_DEPTH] );
    var bottomWall = this.makeWall( [0, -config.GRID.HEIGHT/2 - config.GRID.WALL_DEPTH/2], [config.GRID.WIDTH*2, config.GRID.WALL_DEPTH] );
    var rightWall  = this.makeWall( [config.GRID.WIDTH/2 + config.GRID.WALL_DEPTH/2, 0],   [config.GRID.WALL_DEPTH, config.GRID.HEIGHT*2] );
    var leftWall   = this.makeWall( [-config.GRID.WIDTH/2 - config.GRID.WALL_DEPTH/2, 0],  [config.GRID.WALL_DEPTH, config.GRID.HEIGHT*2] );

    this.world.addBody( topWall    );
    this.world.addBody( bottomWall );
    this.world.addBody( rightWall  );
    this.world.addBody( leftWall   );

    // add goals

    var topGoalShape = new p2.Line({ length: config.GOAL.WIDTH });
    var bottomGoalShape = new p2.Line({ length: config.GOAL.WIDTH });
    topGoalShape.sensor = true;
    bottomGoalShape.sensor = true;
    topGoalBody = new p2.Body({ position: [0, -config.GRID.HEIGHT/2] });
    botGoalBody = new p2.Body({ position: [0, config.GRID.HEIGHT/2] });
    topGoalBody.addShape(topGoalShape);
    botGoalBody.addShape(bottomGoalShape);
    topGoalBody.damping = 0;
    botGoalBody.damping = 0;
    topGoalBody.customGoal = 'a';
    botGoalBody.customGoal = 'b';

    this.world.addBody(topGoalBody);
    this.world.addBody(botGoalBody);

    this.world.enableBodyCollision();
};

Sim.prototype.addBlock = function SimAddBlock(x, y, player) {

    // add block to sim

    if (this.gameState.grid[y][x] > 0) {
        return false;
    }

    var blockShape = new p2.Box({ width: 1.00, height: 1.00 });
    blockShape.material = this.bounceMaterial;

    var px = -1 * config.GRID.WIDTH/2 + 1/2 + x;
    var py = -1 * config.GRID.HEIGHT/2 + 1/2 + y;

    var blockBody = new p2.Body({ position: [px, py] });
    blockBody.addShape(blockShape);
    blockBody.customPlayer = player;
    blockBody.customType = 'block';
    blockBody.customGridPosition = [x,y];

    this.world.addBody(blockBody);

    // update gameState grid as well

    this.gameState.grid[y][x] += 1;

    return blockBody;
};

Sim.prototype.removeBlock = function SimRemoveBlock(x, y) {

    // find the block at x,y
    var block = _.find(this.world.bodies, { customGridPosition: [x,y] });
    var xy;

    // remove it from the world if it exists
    if (block) {
        this.pendingRemoval.push(block);
        xy = block.customGridPosition;

        // report back the destruction
        this.destroyBlockHandler({ x: xy[0], y: xy[1] }, block.customPlayer);
    }

    // update gameState grid as well
    this.gameState.grid[y][x] = 0;

    return block;
};

Sim.prototype.onScore = function SimOnScore(callback) {
    this.scoreHandler = callback;
};

Sim.prototype.onDestroyBlock = function simOnDestroyBlock(callback) {
    this.destroyBlockHandler = callback;
};

Sim.prototype.handleCollision = function SimHandleCollision(evt) {
    var obj1 = evt.bodyA;
    var obj2 = evt.bodyB;
    if (obj1.customType === 'disc' && obj2.customType === 'block') {
        console.log('Removing block ' + obj2.customGridPosition);
        this.pendingRemoval.push(obj2);
        this.destroyBlockHandler({ x: obj2.customGridPosition[0], y: obj2.customGridPosition[1] });
        this.gameState.grid[obj2.customGridPosition[1]][obj2.customGridPosition[0]] -= 1;
    }
    else if (obj2.customType === 'disc' && obj1.customType === 'block') {
        console.log('Removing block ' + obj1.customGridPosition);
        this.pendingRemoval.push(obj1);
        this.destroyBlockHandler({ x: obj1.customGridPosition[0], y: obj1.customGridPosition[1] });
        this.gameState.grid[obj1.customGridPosition[1]][obj1.customGridPosition[0]] -= 1;
    }
    else if ([obj1.customGoal, obj2.customGoal].indexOf('a') >= 0) {
        this.scoreHandler('a');
    }
    else if ([obj1.customGoal, obj2.customGoal].indexOf('b') >= 0) {
        this.scoreHandler('b');
    }
};

Sim.prototype.makeWall = function SimMakeWall(position, size) {

    var wallShape = new p2.Box({ width: size[0], height: size[1] });
    wallShape.material = this.bounceMaterial;

    var wallBody = new p2.Body({ position: position });
    wallBody.addShape(wallShape);

    return wallBody;
};

module.exports = Sim;
<commit_msg>add player letter to all destroyBlockHandlers<commit_after>var p2 = require('p2');
var _ = require('lodash');
var config = require('./config');

function Sim(gameState) {

    this.gameState = gameState;

    this.scoreHandler = _.noop;
    this.destroyBlockHandler = _.noop;

    // var self = this;
    // setInterval( function() { self.scoreHandler(); }, 500 );

    this.reset();

    this.initBlocks(gameState.grid);

    // simulation

    this.fixedTimeStep = 1/60;
    this.maxSubSteps = 10;
    this.lastTime = process.hrtime();

}

Sim.prototype.initBlocks = function SimInitBlocks(grid) {
    var y = grid.length;
    while (y--) {
        var row = grid[y];
        var x = row.length;
        while (x--) {
            if (row[x] > 0) {
                this.addBlock(x, y);
            }
        }
    }
};

Sim.prototype.update = function SimUpdate() {

    var deltaTime = process.hrtime(this.lastTime);

    this.lastTime = process.hrtime();

    this.world.step( this.fixedTimeStep, deltaTime[1]/1e9, this.maxSubSteps );

    this.pendingRemoval.forEach(this.world.removeBody.bind(this.world));
    this.pendingRemoval = [];

    this.gameState.disc.pos.x = this.discBody.interpolatedPosition[0];
    this.gameState.disc.pos.y = this.discBody.interpolatedPosition[1];
    this.gameState.disc.angle = this.discBody.interpolatedAngle;;
    this.gameState.disc.vel.x = this.discBody.velocity[0];
    this.gameState.disc.vel.y = this.discBody.velocity[1];
};

Sim.prototype.reset = function SimReset() {
    // set up p2-based physics simulation

    this.pendingRemoval = [];

    this.world = new p2.World({
        islandSplit: false,
    });
    this.world.applyGravity = false;
    this.world.applySpringForces = false;
    this.world.applyDamping = false;
    this.world.on('beginContact', this.handleCollision.bind(this));

    // create materials with no friction and perfect bounce

    this.discMaterial = new p2.Material();
    this.bounceMaterial = new p2.Material();

    var bounceContactMaterial = new p2.ContactMaterial(this.bounceMaterial, this.discMaterial, {
        friction : 0.0,
        restitution: 1.0 + config.DISC.BOUNCE_SPEEDUP,
    });
    bounceContactMaterial.stiffness = 1e12;
    // bounceContactMaterial.relaxation = 1;
    this.world.addContactMaterial(bounceContactMaterial);

    // Add a disc

    var discShape = new p2.Circle({ radius: config.DISC.DIAMETER/2 });
    discShape.material = this.discMaterial;
    this.discBody = new p2.Body({
        mass:1,
        position:[0, 0],
        angularVelocity: 0,
        angularDamping: 0,
        damping: 0,
        fixedRotation: true,
        velocity: [Math.random()*20 - 10, Math.random()*20 - 10],
        allowSleep: false,
    });
    this.discBody.customType = 'disc';
    this.discBody.addShape(discShape);
    this.world.addBody(this.discBody);

    // add some walls

    var topWall    = this.makeWall( [0, config.GRID.HEIGHT/2 + config.GRID.WALL_DEPTH/2],  [config.GRID.WIDTH*2, config.GRID.WALL_DEPTH] );
    var bottomWall = this.makeWall( [0, -config.GRID.HEIGHT/2 - config.GRID.WALL_DEPTH/2], [config.GRID.WIDTH*2, config.GRID.WALL_DEPTH] );
    var rightWall  = this.makeWall( [config.GRID.WIDTH/2 + config.GRID.WALL_DEPTH/2, 0],   [config.GRID.WALL_DEPTH, config.GRID.HEIGHT*2] );
    var leftWall   = this.makeWall( [-config.GRID.WIDTH/2 - config.GRID.WALL_DEPTH/2, 0],  [config.GRID.WALL_DEPTH, config.GRID.HEIGHT*2] );

    this.world.addBody( topWall    );
    this.world.addBody( bottomWall );
    this.world.addBody( rightWall  );
    this.world.addBody( leftWall   );

    // add goals

    var topGoalShape = new p2.Line({ length: config.GOAL.WIDTH });
    var bottomGoalShape = new p2.Line({ length: config.GOAL.WIDTH });
    topGoalShape.sensor = true;
    bottomGoalShape.sensor = true;
    topGoalBody = new p2.Body({ position: [0, -config.GRID.HEIGHT/2] });
    botGoalBody = new p2.Body({ position: [0, config.GRID.HEIGHT/2] });
    topGoalBody.addShape(topGoalShape);
    botGoalBody.addShape(bottomGoalShape);
    topGoalBody.damping = 0;
    botGoalBody.damping = 0;
    topGoalBody.customGoal = 'a';
    botGoalBody.customGoal = 'b';

    this.world.addBody(topGoalBody);
    this.world.addBody(botGoalBody);

    this.world.enableBodyCollision();
};

Sim.prototype.addBlock = function SimAddBlock(x, y, player) {

    // add block to sim

    if (this.gameState.grid[y][x] > 0) {
        return false;
    }

    var blockShape = new p2.Box({ width: 1.00, height: 1.00 });
    blockShape.material = this.bounceMaterial;

    var px = -1 * config.GRID.WIDTH/2 + 1/2 + x;
    var py = -1 * config.GRID.HEIGHT/2 + 1/2 + y;

    var blockBody = new p2.Body({ position: [px, py] });
    blockBody.addShape(blockShape);
    blockBody.customPlayer = player;
    blockBody.customType = 'block';
    blockBody.customGridPosition = [x,y];

    this.world.addBody(blockBody);

    // update gameState grid as well

    this.gameState.grid[y][x] += 1;

    return blockBody;
};

Sim.prototype.removeBlock = function SimRemoveBlock(x, y) {

    // find the block at x,y
    var block = _.find(this.world.bodies, { customGridPosition: [x,y] });
    var xy;

    // remove it from the world if it exists
    if (block) {
        this.pendingRemoval.push(block);
        xy = block.customGridPosition;

        // report back the destruction
        this.destroyBlockHandler({ x: xy[0], y: xy[1] }, block.customPlayer);
    }

    // update gameState grid as well
    this.gameState.grid[y][x] = 0;

    return block;
};

Sim.prototype.onScore = function SimOnScore(callback) {
    this.scoreHandler = callback;
};

Sim.prototype.onDestroyBlock = function simOnDestroyBlock(callback) {
    this.destroyBlockHandler = callback;
};

Sim.prototype.handleCollision = function SimHandleCollision(evt) {
    var obj1 = evt.bodyA;
    var obj2 = evt.bodyB;
    if (obj1.customType === 'disc' && obj2.customType === 'block') {
        console.log('Removing block ' + obj2.customGridPosition);
        this.destroyBlockHandler({ x: obj2.customGridPosition[0], y: obj2.customGridPosition[1] }, obj2.customPlayer);
        this.pendingRemoval.push(obj2);
        this.gameState.grid[obj2.customGridPosition[1]][obj2.customGridPosition[0]] -= 1;
    }
    else if (obj2.customType === 'disc' && obj1.customType === 'block') {
        console.log('Removing block ' + obj1.customGridPosition);
        this.destroyBlockHandler({ x: obj1.customGridPosition[0], y: obj1.customGridPosition[1] }, obj1.customPlayer);
        this.pendingRemoval.push(obj1);
        this.gameState.grid[obj1.customGridPosition[1]][obj1.customGridPosition[0]] -= 1;
    }
    else if ([obj1.customGoal, obj2.customGoal].indexOf('a') >= 0) {
        this.scoreHandler('a');
    }
    else if ([obj1.customGoal, obj2.customGoal].indexOf('b') >= 0) {
        this.scoreHandler('b');
    }
};

Sim.prototype.makeWall = function SimMakeWall(position, size) {

    var wallShape = new p2.Box({ width: size[0], height: size[1] });
    wallShape.material = this.bounceMaterial;

    var wallBody = new p2.Body({ position: position });
    wallBody.addShape(wallShape);

    return wallBody;
};

module.exports = Sim;
<|endoftext|>


<commit_before>// @flow
const azure = require('azure-storage');
const config = require('config');
const fs = require('fs-extra');
const gm = require('gm');
const mkdirp = require('mkdirp');
const parse = require('co-busboy');
const path = require('path');
const Promise = require('bluebird');
const request = require('request');
const logger = require('./logger');
const util = require('./util');
const LabelModel = require('./label');
const send = require('koa-send');
const _ = require('koa-route');

const blobService = azure.createBlobService(
  config.storage.STORAGE_ACCOUNT,
  config.storage.STORAGE_ACCESS_KEY,
);

async function getFilePath(param) {
  const containers = await getContainersAsync();
  logger.info(`param: ${param}`);

  const result = /(\d+)(.*)/.exec(param);
  logger.info(result);
  if (!result) {
    return null;
  }

  const containerIndex = parseInt(result[1], 10);
  const container = containers[containerIndex];
  if (!container) {
    return null;
  }

  let subdir = result[2];
  if (subdir.length > 0) {
    if (subdir[0] !== '/') {
      return null;
    }

    subdir = subdir.substr(1);
  }
  const filepath = subdir ? `${container.name}/${subdir}` : container.name;
  return filepath;
}

/**
 * @param {string} storageAccount
 * @param {string} containerName
 * @param {string} filename
 * @return {string}
 */
function generateAzureBlobURL(
  storageAccount /* :string */,
  containerName /* :string */,
  filename /* :string */,
) {
  const url = `https://${storageAccount}.blob.core.windows.net/${containerName}/${filename}`;
  return url;
}

/**
 * @param {string} storageAccount
 * @param {string} containerName
 * @param {string} filename
 */
async function getLabels(
  storageAccount /* :string */,
  containerName /* :string */,
  filename /* :string */,
) {
  const docUrl = generateAzureBlobURL(storageAccount, containerName, filename);
  const labels = await LabelModel.find({ docUrl }).exec();
  const modeledLabels = labels.map(label => ({
    start: label.begin,
    end: label.end,
    label: label.label || '',
  }));

  return modeledLabels;
}

/**
 * @param {string} filepath
 * @return {Promise<array>}
 */
function deleteLabels(
  storageAccount /* :string */,
  containerName /* :string */,
  filename /* :string */,
) {
  return new Promise((resolve) => {
    const docUrl = generateAzureBlobURL(storageAccount, containerName, filename);
    const labels = LabelModel.deleteMany(
      { docUrl },
      err => (err ? console.error(err) : resolve(labels)),
    );
  });
}

/**
 * @param {string} storageAccount
 * @param {string} containerName
 * @param {string} filename
 * @param {string} data
 */
function addLabels(
  storageAccount /* :string */,
  containerName /* :string */,
  filename /* :string */,
  data /* :any */,
) {
  const docUrl = generateAzureBlobURL(storageAccount, containerName, filename);

  const newData = data.map(label => ({
    docUrl,
    begin: label.start,
    end: label.end,
    label: label.label,
  }));

  let newLabels = [];
  return new Promise((resolve) => {
    LabelModel.insertMany(newData, (err, result) => {
      newLabels = result;
      resolve(newLabels);
    });
  });
}

function getImageInfo(filepath) {
  const fileparts = [];
  fileparts[0] = filepath.substring(0, filepath.indexOf('/'));
  fileparts[1] = filepath.substring(filepath.indexOf('/') + 1);
  const info = {};

  return new Promise((resolve, reject) => {
    blobService.getBlobToStream(
      fileparts[0],
      fileparts[1],
      fs.createWriteStream('output.jpeg'),
      (error) => {
        if (!error) {
          const img = gm('output.jpeg');
          img.size((err, value) => {
            if (err) {
              reject(err);
              return;
            }

            info.size = value;
            img.orientation((err2, value2) => {
              if (err2) {
                reject(err2);
                return;
              }

              info.orientation = value2;
              resolve(info);
            });
          });
        }
      },
    );
  });
}

/**
 * Returns a promise resolving to an array of pkgcloud contaienrs
 * @return {Promise<array>}
 */
async function getContainersAsync() {
  let containers = [];

  const aggregateContainers = (err, result, cb) => {
    if (err) {
      cb(err);
    } else {
      containers = containers.concat(result.entries);
      if (result.continuationToken !== null) {
        blobService.listContainersSegmented(result.continuationToken, aggregateContainers);
      } else {
        cb(null, containers);
      }
    }
  };

  return new Promise((resolve, reject) => {
    blobService.listContainersSegmented(null, (err, result) => {
      aggregateContainers(err, result, (err, containers) => {
        if (err) {
          logger.warn(err);
          reject(err);
        } else {
          // Add azure storage account to containers
          const decaratedContainers = Array.isArray(containers)
            ? containers.map(container =>
              Object.assign(container, { storageAccount: config.storage.STORAGE_ACCOUNT }),
            )
            : containers;
          resolve(decaratedContainers);
        }
      });
    });
  });
}

/**
 * Returns a promise resolving to an array of pkgcloud files
 * @param {string} container target container name
 * @return {Promise<array>}
 */
async function getBlobsAsync(container /* :string */) {
  let blobs = [];
  const aggregateBlobs = (containerName, err, result, cb) => {
    if (err) {
      cb(err);
    } else {
      blobs = blobs.concat(result.entries);
      if (result.continuationToken !== null) {
        blobService.listBlobsSegmented(containerName, result.continuationToken, aggregateBlobs);
      } else {
        cb(null, blobs);
      }
    }
  };

  return new Promise((resolve, reject) => {
    blobService.listBlobsSegmented(container, null, (err, result) => {
      aggregateBlobs(container, err, result, (err, blobs) => {
        if (err) {
          logger.warn(err);
          reject(err);
        } else {
          resolve(blobs);
        }
      });
    });
  });
}

const funcs = {
  async downloadfile(ctx, param) {
    const requiredParams = ['storageAccount', 'containerName', 'filename'];
    const isValidGet = () =>
      Object.keys(ctx.query).every(getParam => requiredParams.includes(getParam));

    if (isValidGet()) {
      const storageAccount = ctx.query.storageAccount;
      const containerName = ctx.query.containerName;
      const filename = ctx.query.filename;

      // Concat to local file dir
      const filepath = `files/${storageAccount}/${containerName}/${filename}`;
      const dirname = path.dirname(filepath);

      if (!fs.existsSync(dirname)) {
        mkdirp.sync(dirname);
      }

      // download file locally; then send it
      try {
        await new Promise((resolve, reject) => {
          // Prep write stream
          const writeStream = fs.createWriteStream(filepath);
          writeStream
            .on('finish', () => {
              resolve(filepath);
            })
            .on('error', (err /* : Error */) => {
              reject(err);
            });

          // download
          const urlSafeAccount = encodeURIComponent(storageAccount);
          const urlSafeContainer = encodeURIComponent(containerName);
          const urlSafeFilename = encodeURIComponent(filename);
          const downloadUrl = `https://${urlSafeAccount}.blob.core.windows.net/${urlSafeContainer}/${urlSafeFilename}`;
          request(downloadUrl).pipe(writeStream);
        });
        await send(ctx, `./${filepath}`);
      } catch (err) {
        logger.error(err);
      }
    } else {
      ctx.body = { error: 'Invalid GET request' };
    }
  },

  /**
   * @param {string} param container-index/filename
   */
  async labels(ctx) {
    // retrieve container/filename information with getFilePath
    const requiredParams = ['storageAccount', 'containerName', 'filename'];
    const getParams = ctx.query;
    const isValidGet = () => Object.keys(getParams).every(param => requiredParams.includes(param));

    if (isValidGet()) {
      const storageAccount = getParams.storageAccount;
      const containerName = getParams.containerName;
      const filename = getParams.filename;
      ctx.body = await getLabels(storageAccount, containerName, filename);
    } else {
      ctx.body = { message: 'Invalid GET request' };
    }
  },

  async saveLabels(ctx) {
    // White list valid POST params
    const isValidPost = () =>
      Object.keys(ctx.request.body).every(param =>
        ['storageAccount', 'labels', 'containerName', 'filename'].includes(param),
      );

    if (isValidPost()) {
      const storageAccount = ctx.request.body.storageAccount;
      const containerName = ctx.request.body.containerName;
      const filename = ctx.request.body.filename;
      const data = ctx.request.body.labels;
      ctx.body = await deleteLabels(storageAccount, containerName, filename);
      ctx.body = await addLabels(storageAccount, containerName, filename, data);
    } else {
      ctx.body = { message: 'Invalid POST request' };
    }
  },

  async bookmarks(ctx) {
    const bookmarks = config.get('bookmarks');
    ctx.body = JSON.stringify(bookmarks.map(b => b.name));
  },

  async containers(ctx) {
    const containers = await getContainersAsync();
    ctx.body = containers;
  },

  async dir(ctx, param) {
    const dir = await getFilePath(param);
    if (!dir) {
      ctx.body = 'invalid directory';
      ctx.status = 404;
      return;
    }
    let files = [];
    logger.info(`dir: ${dir}`);
    const containerFiles = await getBlobsAsync(dir);
    files = files.concat(containerFiles);

    const data = files.reduce((result, file) => {
      // const stats = fs.lstatSync(path.resolve(dir, file));
      if (file.name.includes('.flac') || file.name.includes('.mp3')) {
        result.push({
          name: file.name,
          isDirectory: false, // file.name.indexOf('/') > -1,
          size: file.contentLength,
          mtime: file.lastModified,
        });
      }
      return result;
    }, []);
    ctx.body = data;
  },

  async imageInfo(ctx, param) {
    const filepath = getFilePath(param);
    if (!filepath) {
      ctx.body = 'invalid location';
      return;
    }

    ctx.body = await getImageInfo(filepath);
  },

  async download(ctx, param) {
    const filepath = getFilePath(param);
    if (!filepath) {
      ctx.body = { success: false, message: 'invalid location' };
    }
  },

  async image(ctx, param) {
    const filepath = getFilePath(param);
    if (!filepath) {
      ctx.body = 'invalid location';
      return;
    }

    if (!config.cacheDir) {
      await send(ctx, filepath);
      return;
    }

    const type = ctx.query.type;
    if (type !== 'sq100' && type !== 'max800') {
      ctx.body = 'invalid type';
      return;
    }

    const cacheFilepath = util.getImageCacheFilepath(filepath, type);
    await util.createImageCache(type, filepath, cacheFilepath);

    await send(ctx, cacheFilepath);
    if (!ctx.status) {
      ctx.throw(404);
    }
  },

  * upload(ctx, param) {
    const dir = getFilePath(param);
    if (!dir) {
      this.body = 'invalid location';
      return;
    }

    const parts = parse(this);
    let part = yield parts;
    while (part) {
      const filename = path.resolve(dir, part.filename);
      const readStream = part;
      const writeStream = fs.createWriteStream(filename);
      yield new Promise((resolve, reject) => {
        readStream.on('error', reject);
        writeStream.on('error', reject);
        writeStream.on('finish', resolve);
        readStream.pipe(writeStream);
      });

      part = yield parts;
    }

    ctx.status = 200;
  },

  async createFolder(ctx, param) {
    const dir = getFilePath(param);
    if (!dir) {
      ctx.body = 'invalid location';
      return;
    }

    const folderName = ctx.request.body.name;

    const folderFilepath = path.resolve(dir, folderName);
    await fs.mkdirAsync(folderFilepath);

    ctx.body = {};
  },

  async delete(ctx, param) {
    const dir = getFilePath(param);
    if (!dir) {
      ctx.body = 'invalid location';
      return;
    }

    const files = ctx.request.body;

    if (!config.trashDir) {
      await Promise.all(files.map(file => fs.unlink(path.resolve(dir, file)))).catch(err =>
        console.error(err),
      );
      ctx.body = {};
      return;
    }

    // await Promise.all() for parallel deletion
    await Promise.all(
      files.map((file) => {
        const sourceFilepath = path.resolve(dir, file);
        const trashFilename = `${Date.now()}_${file}`;
        const trashFilepath = path.resolve(config.trashDir, trashFilename);
        return fs.move(sourceFilepath, trashFilepath);
      }),
    ).catch((err) => {
      console.error(err);
    });

    ctx.body = {};
  },
};

function init(app) {
  app.use(async (ctx, next) => {
    const reqPath = decodeURIComponent(ctx.path);
    const result = /^\/api\/(\w+)\/?(.*)/.exec(reqPath);

    if (result) {
      const apiName = result[1];
      const param = result[2];
      if (!funcs[apiName]) {
        logger.warn(`Invalid api: ${apiName}`);
        ctx.throw(403);
      }

      try {
        await funcs[apiName](ctx, param);
      } catch (err) {
        logger.warn(`Failed to handle api: ${apiName}`, err, err.stack);
        ctx.throw(500);
      }
    }

    await next();
  });
}

module.exports = init;
<commit_msg>Added file cache on server<commit_after>// @flow
const azure = require('azure-storage');
const config = require('config');
const fs = require('fs-extra');
const gm = require('gm');
const mkdirp = require('mkdirp');
const parse = require('co-busboy');
const path = require('path');
const Promise = require('bluebird');
const request = require('request');
const logger = require('./logger');
const util = require('./util');
const LabelModel = require('./label');
const send = require('koa-send');
const _ = require('koa-route');

const blobService = azure.createBlobService(
  config.storage.STORAGE_ACCOUNT,
  config.storage.STORAGE_ACCESS_KEY,
);

async function getFilePath(param) {
  const containers = await getContainersAsync();
  logger.info(`param: ${param}`);

  const result = /(\d+)(.*)/.exec(param);
  logger.info(result);
  if (!result) {
    return null;
  }

  const containerIndex = parseInt(result[1], 10);
  const container = containers[containerIndex];
  if (!container) {
    return null;
  }

  let subdir = result[2];
  if (subdir.length > 0) {
    if (subdir[0] !== '/') {
      return null;
    }

    subdir = subdir.substr(1);
  }
  const filepath = subdir ? `${container.name}/${subdir}` : container.name;
  return filepath;
}

/**
 * @param {string} storageAccount
 * @param {string} containerName
 * @param {string} filename
 * @return {string}
 */
function generateAzureBlobURL(
  storageAccount /* :string */,
  containerName /* :string */,
  filename /* :string */,
) {
  const url = `https://${storageAccount}.blob.core.windows.net/${containerName}/${filename}`;
  return url;
}

/**
 * @param {string} storageAccount
 * @param {string} containerName
 * @param {string} filename
 */
async function getLabels(
  storageAccount /* :string */,
  containerName /* :string */,
  filename /* :string */,
) {
  const docUrl = generateAzureBlobURL(storageAccount, containerName, filename);
  const labels = await LabelModel.find({ docUrl }).exec();
  const modeledLabels = labels.map(label => ({
    start: label.begin,
    end: label.end,
    label: label.label || '',
  }));

  return modeledLabels;
}

/**
 * @param {string} filepath
 * @return {Promise<array>}
 */
function deleteLabels(
  storageAccount /* :string */,
  containerName /* :string */,
  filename /* :string */,
) {
  return new Promise((resolve) => {
    const docUrl = generateAzureBlobURL(storageAccount, containerName, filename);
    const labels = LabelModel.deleteMany(
      { docUrl },
      err => (err ? console.error(err) : resolve(labels)),
    );
  });
}

/**
 * @param {string} storageAccount
 * @param {string} containerName
 * @param {string} filename
 * @param {string} data
 */
function addLabels(
  storageAccount /* :string */,
  containerName /* :string */,
  filename /* :string */,
  data /* :any */,
) {
  const docUrl = generateAzureBlobURL(storageAccount, containerName, filename);

  const newData = data.map(label => ({
    docUrl,
    begin: label.start,
    end: label.end,
    label: label.label,
  }));

  let newLabels = [];
  return new Promise((resolve) => {
    LabelModel.insertMany(newData, (err, result) => {
      newLabels = result;
      resolve(newLabels);
    });
  });
}

function getImageInfo(filepath) {
  const fileparts = [];
  fileparts[0] = filepath.substring(0, filepath.indexOf('/'));
  fileparts[1] = filepath.substring(filepath.indexOf('/') + 1);
  const info = {};

  return new Promise((resolve, reject) => {
    blobService.getBlobToStream(
      fileparts[0],
      fileparts[1],
      fs.createWriteStream('output.jpeg'),
      (error) => {
        if (!error) {
          const img = gm('output.jpeg');
          img.size((err, value) => {
            if (err) {
              reject(err);
              return;
            }

            info.size = value;
            img.orientation((err2, value2) => {
              if (err2) {
                reject(err2);
                return;
              }

              info.orientation = value2;
              resolve(info);
            });
          });
        }
      },
    );
  });
}

/**
 * Returns a promise resolving to an array of pkgcloud contaienrs
 * @return {Promise<array>}
 */
async function getContainersAsync() {
  let containers = [];

  const aggregateContainers = (err, result, cb) => {
    if (err) {
      cb(err);
    } else {
      containers = containers.concat(result.entries);
      if (result.continuationToken !== null) {
        blobService.listContainersSegmented(result.continuationToken, aggregateContainers);
      } else {
        cb(null, containers);
      }
    }
  };

  return new Promise((resolve, reject) => {
    blobService.listContainersSegmented(null, (err, result) => {
      aggregateContainers(err, result, (err, containers) => {
        if (err) {
          logger.warn(err);
          reject(err);
        } else {
          // Add azure storage account to containers
          const decaratedContainers = Array.isArray(containers)
            ? containers.map(container =>
              Object.assign(container, { storageAccount: config.storage.STORAGE_ACCOUNT }),
            )
            : containers;
          resolve(decaratedContainers);
        }
      });
    });
  });
}

/**
 * Returns a promise resolving to an array of pkgcloud files
 * @param {string} container target container name
 * @return {Promise<array>}
 */
async function getBlobsAsync(container /* :string */) {
  let blobs = [];
  const aggregateBlobs = (containerName, err, result, cb) => {
    if (err) {
      cb(err);
    } else {
      blobs = blobs.concat(result.entries);
      if (result.continuationToken !== null) {
        blobService.listBlobsSegmented(containerName, result.continuationToken, aggregateBlobs);
      } else {
        cb(null, blobs);
      }
    }
  };

  return new Promise((resolve, reject) => {
    blobService.listBlobsSegmented(container, null, (err, result) => {
      aggregateBlobs(container, err, result, (err, blobs) => {
        if (err) {
          logger.warn(err);
          reject(err);
        } else {
          resolve(blobs);
        }
      });
    });
  });
}

const funcs = {
  async downloadfile(ctx, param) {
    const requiredParams = ['storageAccount', 'containerName', 'filename'];
    const isValidGet = () =>
      Object.keys(ctx.query).every(getParam => requiredParams.includes(getParam));

    if (isValidGet()) {
      const storageAccount = ctx.query.storageAccount;
      const containerName = ctx.query.containerName;
      const filename = ctx.query.filename;

      // Concat to local file dir
      const filepath = `files/${storageAccount}/${containerName}/${filename}`;
      const dirname = path.dirname(filepath);

      if (!fs.existsSync(dirname)) {
        mkdirp.sync(dirname);
      }

      // download file locally; then send it
      try {
        // if file exists check age
        const shouldDownload = (maxage = 3600000) => {
          try {
            const stats = fs.statSync(filepath);
            const timeCreated = stats.birthtime.getTime();
            const now = new Date().getTime();
            const age = new Date(now - timeCreated);
            return age.getTime() > maxage; // older than an hour
          } catch (err) {
            return true;
          }
        };

        // download if file doesn't exist or is too old
        if (shouldDownload()) {
          await new Promise((resolve, reject) => {
            // Prep write stream
            const writeStream = fs.createWriteStream(filepath);
            writeStream
              .on('finish', () => {
                resolve(filepath);
              })
              .on('error', (err /* : Error */) => {
                reject(err);
              });

            // download
            const urlSafeAccount = encodeURIComponent(storageAccount);
            const urlSafeContainer = encodeURIComponent(containerName);
            const urlSafeFilename = encodeURIComponent(filename);
            const downloadUrl = `https://${urlSafeAccount}.blob.core.windows.net/${urlSafeContainer}/${urlSafeFilename}`;
            request(downloadUrl).pipe(writeStream);
          });
        }
        await send(ctx, `./${filepath}`);
      } catch (err) {
        logger.error(err);
      }
    } else {
      ctx.body = { error: 'Invalid GET request' };
    }
  },

  /**
   * @param {string} param container-index/filename
   */
  async labels(ctx) {
    // retrieve container/filename information with getFilePath
    const requiredParams = ['storageAccount', 'containerName', 'filename'];
    const getParams = ctx.query;
    const isValidGet = () => Object.keys(getParams).every(param => requiredParams.includes(param));

    if (isValidGet()) {
      const storageAccount = getParams.storageAccount;
      const containerName = getParams.containerName;
      const filename = getParams.filename;
      ctx.body = await getLabels(storageAccount, containerName, filename);
    } else {
      ctx.body = { message: 'Invalid GET request' };
    }
  },

  async saveLabels(ctx) {
    // White list valid POST params
    const isValidPost = () =>
      Object.keys(ctx.request.body).every(param =>
        ['storageAccount', 'labels', 'containerName', 'filename'].includes(param),
      );

    if (isValidPost()) {
      const storageAccount = ctx.request.body.storageAccount;
      const containerName = ctx.request.body.containerName;
      const filename = ctx.request.body.filename;
      const data = ctx.request.body.labels;
      ctx.body = await deleteLabels(storageAccount, containerName, filename);
      ctx.body = await addLabels(storageAccount, containerName, filename, data);
    } else {
      ctx.body = { message: 'Invalid POST request' };
    }
  },

  async bookmarks(ctx) {
    const bookmarks = config.get('bookmarks');
    ctx.body = JSON.stringify(bookmarks.map(b => b.name));
  },

  async containers(ctx) {
    const containers = await getContainersAsync();
    ctx.body = containers;
  },

  async dir(ctx, param) {
    const dir = await getFilePath(param);
    if (!dir) {
      ctx.body = 'invalid directory';
      ctx.status = 404;
      return;
    }
    let files = [];
    logger.info(`dir: ${dir}`);
    const containerFiles = await getBlobsAsync(dir);
    files = files.concat(containerFiles);

    const data = files.reduce((result, file) => {
      // const stats = fs.lstatSync(path.resolve(dir, file));
      if (file.name.includes('.flac') || file.name.includes('.mp3')) {
        result.push({
          name: file.name,
          isDirectory: false, // file.name.indexOf('/') > -1,
          size: file.contentLength,
          mtime: file.lastModified,
        });
      }
      return result;
    }, []);
    ctx.body = data;
  },

  async imageInfo(ctx, param) {
    const filepath = getFilePath(param);
    if (!filepath) {
      ctx.body = 'invalid location';
      return;
    }

    ctx.body = await getImageInfo(filepath);
  },

  async download(ctx, param) {
    const filepath = getFilePath(param);
    if (!filepath) {
      ctx.body = { success: false, message: 'invalid location' };
    }
  },

  async image(ctx, param) {
    const filepath = getFilePath(param);
    if (!filepath) {
      ctx.body = 'invalid location';
      return;
    }

    if (!config.cacheDir) {
      await send(ctx, filepath);
      return;
    }

    const type = ctx.query.type;
    if (type !== 'sq100' && type !== 'max800') {
      ctx.body = 'invalid type';
      return;
    }

    const cacheFilepath = util.getImageCacheFilepath(filepath, type);
    await util.createImageCache(type, filepath, cacheFilepath);

    await send(ctx, cacheFilepath);
    if (!ctx.status) {
      ctx.throw(404);
    }
  },

  * upload(ctx, param) {
    const dir = getFilePath(param);
    if (!dir) {
      this.body = 'invalid location';
      return;
    }

    const parts = parse(this);
    let part = yield parts;
    while (part) {
      const filename = path.resolve(dir, part.filename);
      const readStream = part;
      const writeStream = fs.createWriteStream(filename);
      yield new Promise((resolve, reject) => {
        readStream.on('error', reject);
        writeStream.on('error', reject);
        writeStream.on('finish', resolve);
        readStream.pipe(writeStream);
      });

      part = yield parts;
    }

    ctx.status = 200;
  },

  async createFolder(ctx, param) {
    const dir = getFilePath(param);
    if (!dir) {
      ctx.body = 'invalid location';
      return;
    }

    const folderName = ctx.request.body.name;

    const folderFilepath = path.resolve(dir, folderName);
    await fs.mkdirAsync(folderFilepath);

    ctx.body = {};
  },

  async delete(ctx, param) {
    const dir = getFilePath(param);
    if (!dir) {
      ctx.body = 'invalid location';
      return;
    }

    const files = ctx.request.body;

    if (!config.trashDir) {
      await Promise.all(files.map(file => fs.unlink(path.resolve(dir, file)))).catch(err =>
        console.error(err),
      );
      ctx.body = {};
      return;
    }

    // await Promise.all() for parallel deletion
    await Promise.all(
      files.map((file) => {
        const sourceFilepath = path.resolve(dir, file);
        const trashFilename = `${Date.now()}_${file}`;
        const trashFilepath = path.resolve(config.trashDir, trashFilename);
        return fs.move(sourceFilepath, trashFilepath);
      }),
    ).catch((err) => {
      console.error(err);
    });

    ctx.body = {};
  },
};

function init(app) {
  app.use(async (ctx, next) => {
    const reqPath = decodeURIComponent(ctx.path);
    const result = /^\/api\/(\w+)\/?(.*)/.exec(reqPath);

    if (result) {
      const apiName = result[1];
      const param = result[2];
      if (!funcs[apiName]) {
        logger.warn(`Invalid api: ${apiName}`);
        ctx.throw(403);
      }

      try {
        await funcs[apiName](ctx, param);
      } catch (err) {
        logger.warn(`Failed to handle api: ${apiName}`, err, err.stack);
        ctx.throw(500);
      }
    }

    await next();
  });
}

module.exports = init;
<|endoftext|>