<commit_before>/**
 * Graphology Edges Iteration Specs
 * =================================
 *
 * Testing the edges iteration-related methods of the graph.
 */
import assert from 'assert';
import {
  capitalize,
  deepMerge,
  sameMembers,
  testBunches
} from '../helpers';

const METHODS = [
  'edges',
  'inEdges',
  'outEdges',
  'inboundEdges',
  'outboundEdges',
  'directedEdges',
  'undirectedEdges'
];

export default function edgesIteration(Graph, checkers) {
  const {
    invalid,
    notFound
  } = checkers;

  const graph = new Graph({multi: true});

  graph.addNodesFrom([
    'John',
    'Thomas',
    'Martha',
    'Roger',
    'Catherine',
    'Alone',
    'Forever'
  ]);

  graph.addDirectedEdgeWithKey('J->T', 'John', 'Thomas');
  graph.addDirectedEdgeWithKey('J->M', 'John', 'Martha');
  graph.addDirectedEdgeWithKey('C->J', 'Catherine', 'John');

  graph.addUndirectedEdgeWithKey('M<->R', 'Martha', 'Roger');
  graph.addUndirectedEdgeWithKey('M<->J', 'Martha', 'John');
  graph.addUndirectedEdgeWithKey('J<->R', 'John', 'Roger');
  graph.addUndirectedEdgeWithKey('T<->M', 'Thomas', 'Martha');

  const ALL_EDGES = [
    'J->T',
    'J->M',
    'C->J',
    'M<->R',
    'M<->J',
    'J<->R',
    'T<->M'
  ];

  const ALL_DIRECTED_EDGES = [
    'J->T',
    'J->M',
    'C->J'
  ];

  const ALL_UNDIRECTED_EDGES = [
    'M<->R',
    'M<->J',
    'J<->R',
    'T<->M'
  ];

  const TEST_DATA = {
    edges: {
      all: ALL_EDGES,
      node: {
        key: 'John',
        edges: [
          'C->J',
          'J->T',
          'J->M',
          'M<->J',
          'J<->R'
        ]
      },
      bunch: {
        keys: ['Martha', 'Roger'],
        edges: [
          'J->M',
          'M<->R',
          'M<->J',
          'T<->M',
          'J<->R'
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'J->M',
          'M<->J'
        ]
      }
    },
    inEdges: {
      all: ALL_DIRECTED_EDGES,
      node: {
        key: 'John',
        edges: [
          'C->J'
        ]
      },
      bunch: {
        keys: ['Thomas', 'John'],
        edges: [
          'J->T',
          'C->J'
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'J->M'
        ]
      }
    },
    outEdges: {
      all: ALL_DIRECTED_EDGES,
      node: {
        key: 'John',
        edges: [
          'J->T',
          'J->M'
        ]
      },
      bunch: {
        keys: ['John', 'Catherine'],
        edges: [
          'J->T',
          'J->M',
          'C->J'
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'J->M'
        ]
      }
    },
    inboundEdges: {
      all: ALL_EDGES,
      node: {
        key: 'John',
        edges: [
          'C->J',
          'M<->J'
        ]
      },
      bunch: {
        keys: ['Thomas', 'John'],
        edges: [
          'J->T',
          'C->J',
          'M<->J'
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'J->M',
          'M<->J'
        ]
      }
    },
    outboundEdges: {
      all: ALL_EDGES,
      node: {
        key: 'John',
        edges: [
          'J->T',
          'J->M',
          'J<->R'
        ]
      },
      bunch: {
        keys: ['John', 'Martha'],
        edges: [
          'J->T',
          'J->M',
          'J<->R',
          'M<->R',
          'M<->J'
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'J->M',
          'M<->J'
        ]
      }
    },
    directedEdges: {
      all: ALL_DIRECTED_EDGES,
      node: {
        key: 'John',
        edges: [
          'C->J',
          'J->T',
          'J->M'
        ]
      },
      bunch: {
        keys: ['John', 'Catherine'],
        edges: [
          'C->J',
          'J->T',
          'J->M'
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'J->M'
        ]
      }
    },
    undirectedEdges: {
      all: ALL_UNDIRECTED_EDGES,
      node: {
        key: 'John',
        edges: [
          'M<->J',
          'J<->R'
        ]
      },
      bunch: {
        keys: ['John', 'Martha'],
        edges: [
          'M<->R',
          'M<->J',
          'J<->R',
          'T<->M',
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'M<->J'
        ]
      }
    }
  };

  // const graphWithSelfLoops = new Graph({multi: true});
  // graphWithSelfLoops.addNodesFrom(['John', 'Tabitha', 'Marcia', 'Alone']);
  // graphWithSelfLoops.addEdgeWithKey('J->T', 'John', 'Tabitha');
  // graphWithSelfLoops.addEdgeWithKey('J1', 'John', 'John');
  // graphWithSelfLoops.addEdgeWithKey('J2', 'John', 'John');
  // graphWithSelfLoops.addEdgeWithKey('T1', 'Tabitha', 'Tabitha');
  // graphWithSelfLoops.addEdgeWithKey('M1', 'Marcia', 'Marcia');

  // const SELF_LOOPS_TEST_DATA = {
  //   selfLoops: {
  //     all: ['J1', 'J2', 'T1', 'M1'],
  //     node: {
  //       key: 'John',
  //       loops: ['J1', 'J2']
  //     },
  //     bunch: {
  //       keys: ['John', 'Tabitha'],
  //       edges: [
  //         'J1',
  //         'J2',
  //         'T1'
  //       ]
  //     }
  //   }
  // };

  function commonTests(name) {
    return {
      ['#.' + name]: {
        'it should throw if too many arguments are provided.': function() {
          assert.throws(function() {
            graph[name](1, 2, 3);
          }, invalid());
        },

        'it should throw when the node is not found.': function() {
          assert.throws(function() {
            graph[name]('Test');
          }, notFound());
        },

        'it should throw if any of the provided bunch node is not found.': function() {
          assert.throws(function() {
            graph[name](['Test']);
          }, notFound());
        },

        'it should throw if either source or target is not found.': function() {
          assert.throws(function() {
            graph[name]('Test', 'Alone');
          }, notFound());

          assert.throws(function() {
            graph[name]('Alone', 'Test');
          }, notFound());
        }
      }
    };
  }

  function specificTests(name, data) {
    const counterName = 'count' + capitalize(name);

    return {

      // Array-creators
      ['#.' + name]: {
        'it should return all the relevant edges.': function() {
          const edges = graph[name]();

          assert.deepEqual(edges, data.all);
        },

        'it should return a node\'s relevant edges.': function() {
          const edges = graph[name](data.node.key);

          assert.deepEqual(edges, data.node.edges);
          assert.deepEqual(graph[name]('Alone'), []);
        },

        'it should return a bunch of nodes\' relevant edges.': function() {
          testBunches(data.bunch.keys, bunch => {
            const edges = graph[name](bunch);

            assert(sameMembers(edges, data.bunch.edges));
            assert.deepEqual(graph[name](['Forever', 'Alone']), []);
          });
        },

        'it should return all the relevant edges between source & target.': function() {
          const edges = graph[name](data.path.source, data.path.target);

          assert(sameMembers(edges, data.path.edges));
          assert.deepEqual(graph[name]('Forever', 'Alone'), []);
        }
      },

      // Counters
      ['#.' + counterName]: {
        'it should count all the relevant edges.': function() {
          const nb = graph[counterName]();

          assert.strictEqual(nb, data.all.length);
        },

        'it should count all the relevant edges of a node.': function() {
          const nb = graph[counterName](data.node.key);

          assert.strictEqual(nb, data.node.edges.length);
          assert.deepEqual(graph[counterName]('Alone'), 0);
        },

        'it should count a bunch of nodes\' relevant edges.': function() {
          testBunches(data.bunch.keys, bunch => {

            assert.strictEqual(graph[counterName](bunch), data.bunch.edges.length);
            assert.deepEqual(graph[counterName](['Forever', 'Alone']), 0);
          });
        },

        'it should count all the relevant edges between source & target.': function() {
          const nb = graph[counterName](data.path.source, data.path.target);

          assert.strictEqual(nb, data.path.edges.length);
          assert.deepEqual(graph[counterName]('Forever', 'Alone'), 0);
        }
      }
    };
  }

  const tests = {};

  // Common tests
  METHODS.forEach(name => deepMerge(tests, commonTests(name)));
  METHODS.forEach(name => deepMerge(tests, commonTests('count' + capitalize(name))));

  // Specific tests
  for (const name in TEST_DATA)
    deepMerge(tests, specificTests(name, TEST_DATA[name]));

  return tests;
}
<commit_msg>Fix #59 - testing edge case<commit_after>/**
 * Graphology Edges Iteration Specs
 * =================================
 *
 * Testing the edges iteration-related methods of the graph.
 */
import assert from 'assert';
import {
  capitalize,
  deepMerge,
  sameMembers,
  testBunches
} from '../helpers';

const METHODS = [
  'edges',
  'inEdges',
  'outEdges',
  'inboundEdges',
  'outboundEdges',
  'directedEdges',
  'undirectedEdges'
];

export default function edgesIteration(Graph, checkers) {
  const {
    invalid,
    notFound
  } = checkers;

  const graph = new Graph({multi: true});

  graph.addNodesFrom([
    'John',
    'Thomas',
    'Martha',
    'Roger',
    'Catherine',
    'Alone',
    'Forever'
  ]);

  graph.addDirectedEdgeWithKey('J->T', 'John', 'Thomas');
  graph.addDirectedEdgeWithKey('J->M', 'John', 'Martha');
  graph.addDirectedEdgeWithKey('C->J', 'Catherine', 'John');

  graph.addUndirectedEdgeWithKey('M<->R', 'Martha', 'Roger');
  graph.addUndirectedEdgeWithKey('M<->J', 'Martha', 'John');
  graph.addUndirectedEdgeWithKey('J<->R', 'John', 'Roger');
  graph.addUndirectedEdgeWithKey('T<->M', 'Thomas', 'Martha');

  const ALL_EDGES = [
    'J->T',
    'J->M',
    'C->J',
    'M<->R',
    'M<->J',
    'J<->R',
    'T<->M'
  ];

  const ALL_DIRECTED_EDGES = [
    'J->T',
    'J->M',
    'C->J'
  ];

  const ALL_UNDIRECTED_EDGES = [
    'M<->R',
    'M<->J',
    'J<->R',
    'T<->M'
  ];

  const TEST_DATA = {
    edges: {
      all: ALL_EDGES,
      node: {
        key: 'John',
        edges: [
          'C->J',
          'J->T',
          'J->M',
          'M<->J',
          'J<->R'
        ]
      },
      bunch: {
        keys: ['Martha', 'Roger'],
        edges: [
          'J->M',
          'M<->R',
          'M<->J',
          'T<->M',
          'J<->R'
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'J->M',
          'M<->J'
        ]
      }
    },
    inEdges: {
      all: ALL_DIRECTED_EDGES,
      node: {
        key: 'John',
        edges: [
          'C->J'
        ]
      },
      bunch: {
        keys: ['Thomas', 'John'],
        edges: [
          'J->T',
          'C->J'
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'J->M'
        ]
      }
    },
    outEdges: {
      all: ALL_DIRECTED_EDGES,
      node: {
        key: 'John',
        edges: [
          'J->T',
          'J->M'
        ]
      },
      bunch: {
        keys: ['John', 'Catherine'],
        edges: [
          'J->T',
          'J->M',
          'C->J'
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'J->M'
        ]
      }
    },
    inboundEdges: {
      all: ALL_EDGES,
      node: {
        key: 'John',
        edges: [
          'C->J',
          'M<->J'
        ]
      },
      bunch: {
        keys: ['Thomas', 'John'],
        edges: [
          'J->T',
          'C->J',
          'M<->J'
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'J->M',
          'M<->J'
        ]
      }
    },
    outboundEdges: {
      all: ALL_EDGES,
      node: {
        key: 'John',
        edges: [
          'J->T',
          'J->M',
          'J<->R'
        ]
      },
      bunch: {
        keys: ['John', 'Martha'],
        edges: [
          'J->T',
          'J->M',
          'J<->R',
          'M<->R',
          'M<->J'
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'J->M',
          'M<->J'
        ]
      }
    },
    directedEdges: {
      all: ALL_DIRECTED_EDGES,
      node: {
        key: 'John',
        edges: [
          'C->J',
          'J->T',
          'J->M'
        ]
      },
      bunch: {
        keys: ['John', 'Catherine'],
        edges: [
          'C->J',
          'J->T',
          'J->M'
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'J->M'
        ]
      }
    },
    undirectedEdges: {
      all: ALL_UNDIRECTED_EDGES,
      node: {
        key: 'John',
        edges: [
          'M<->J',
          'J<->R'
        ]
      },
      bunch: {
        keys: ['John', 'Martha'],
        edges: [
          'M<->R',
          'M<->J',
          'J<->R',
          'T<->M',
        ]
      },
      path: {
        source: 'John',
        target: 'Martha',
        edges: [
          'M<->J'
        ]
      }
    }
  };

  // const graphWithSelfLoops = new Graph({multi: true});
  // graphWithSelfLoops.addNodesFrom(['John', 'Tabitha', 'Marcia', 'Alone']);
  // graphWithSelfLoops.addEdgeWithKey('J->T', 'John', 'Tabitha');
  // graphWithSelfLoops.addEdgeWithKey('J1', 'John', 'John');
  // graphWithSelfLoops.addEdgeWithKey('J2', 'John', 'John');
  // graphWithSelfLoops.addEdgeWithKey('T1', 'Tabitha', 'Tabitha');
  // graphWithSelfLoops.addEdgeWithKey('M1', 'Marcia', 'Marcia');

  // const SELF_LOOPS_TEST_DATA = {
  //   selfLoops: {
  //     all: ['J1', 'J2', 'T1', 'M1'],
  //     node: {
  //       key: 'John',
  //       loops: ['J1', 'J2']
  //     },
  //     bunch: {
  //       keys: ['John', 'Tabitha'],
  //       edges: [
  //         'J1',
  //         'J2',
  //         'T1'
  //       ]
  //     }
  //   }
  // };

  function commonTests(name) {
    return {
      ['#.' + name]: {
        'it should throw if too many arguments are provided.': function() {
          assert.throws(function() {
            graph[name](1, 2, 3);
          }, invalid());
        },

        'it should throw when the node is not found.': function() {
          assert.throws(function() {
            graph[name]('Test');
          }, notFound());
        },

        'it should throw if any of the provided bunch node is not found.': function() {
          assert.throws(function() {
            graph[name](['Test']);
          }, notFound());
        },

        'it should throw if either source or target is not found.': function() {
          assert.throws(function() {
            graph[name]('Test', 'Alone');
          }, notFound());

          assert.throws(function() {
            graph[name]('Alone', 'Test');
          }, notFound());
        }
      }
    };
  }

  function specificTests(name, data) {
    const counterName = 'count' + capitalize(name);

    return {

      // Array-creators
      ['#.' + name]: {
        'it should return all the relevant edges.': function() {
          const edges = graph[name]();

          assert.deepEqual(edges, data.all);
        },

        'it should return a node\'s relevant edges.': function() {
          const edges = graph[name](data.node.key);

          assert.deepEqual(edges, data.node.edges);
          assert.deepEqual(graph[name]('Alone'), []);
        },

        'it should return a bunch of nodes\' relevant edges.': function() {
          testBunches(data.bunch.keys, bunch => {
            const edges = graph[name](bunch);

            assert(sameMembers(edges, data.bunch.edges));
            assert.deepEqual(graph[name](['Forever', 'Alone']), []);
          });
        },

        'it should return all the relevant edges between source & target.': function() {
          const edges = graph[name](data.path.source, data.path.target);

          assert(sameMembers(edges, data.path.edges));
          assert.deepEqual(graph[name]('Forever', 'Alone'), []);
        }
      },

      // Counters
      ['#.' + counterName]: {
        'it should count all the relevant edges.': function() {
          const nb = graph[counterName]();

          assert.strictEqual(nb, data.all.length);
        },

        'it should count all the relevant edges of a node.': function() {
          const nb = graph[counterName](data.node.key);

          assert.strictEqual(nb, data.node.edges.length);
          assert.deepEqual(graph[counterName]('Alone'), 0);
        },

        'it should count a bunch of nodes\' relevant edges.': function() {
          testBunches(data.bunch.keys, bunch => {

            assert.strictEqual(graph[counterName](bunch), data.bunch.edges.length);
            assert.deepEqual(graph[counterName](['Forever', 'Alone']), 0);
          });
        },

        'it should count all the relevant edges between source & target.': function() {
          const nb = graph[counterName](data.path.source, data.path.target);

          assert.strictEqual(nb, data.path.edges.length);
          assert.deepEqual(graph[counterName]('Forever', 'Alone'), 0);
        }
      }
    };
  }

  const tests = {
    'Various cases': {
      'simple graph indices should work.': function() {
        const simpleGraph = new Graph();
        simpleGraph.addNodesFrom([1, 2, 3, 4]);
        simpleGraph.addEdgeWithKey('1->2', 1, 2);
        simpleGraph.addEdgeWithKey('1->3', 1, 3);
        simpleGraph.addEdgeWithKey('1->4', 1, 4);

        assert.deepEqual(simpleGraph.edges(1), ['1->2', '1->3', '1->4']);
      }
    }
  };

  // Common tests
  METHODS.forEach(name => deepMerge(tests, commonTests(name)));
  METHODS.forEach(name => deepMerge(tests, commonTests('count' + capitalize(name))));

  // Specific tests
  for (const name in TEST_DATA)
    deepMerge(tests, specificTests(name, TEST_DATA[name]));

  return tests;
}
<|endoftext|>


<commit_before>import angular from 'angular';

const modName = 'app.services.midi';

class Midi {
  constructor() {
    this.init();
    this.inputs = [];
  }

  init() {
    navigator.requestMIDIAccess()
      .then(this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this));
  }

  onMIDISuccess(access) {
    const input = access.inputs.values();

    for (var o = input.next(); !o.done; o = input.next()) {
      this.inputs.push(o.value);
    }
    for (var cnt = 0; cnt < this.inputs.length; cnt++) {
      this.inputs[cnt].onmidimessage = this.onMIDIEvent;
    }
  }

  onMIDIFailure(msg) {
    console.log("oh... midi failed");
    console.log(msg);
  }

  onMIDIEvent(e) {
    const statusByte = e.data[0].toString(16).substring(0, 1);
    if (statusByte === "8") {
      console.log("note off");
    }
    if (statusByte === "9") {
      console.log("note on");
    }
  }
}

angular.module(modName, [])
.factory('midi', () => {
  return new Midi();
});

export default modName;
<commit_msg>(feat): add handler<commit_after>import angular from 'angular';
import Converter from '../converter';

const modName = 'app.services.midi';

class Midi {
  constructor() {
    this.init();
    this.inputs = [];
    this.handler = null;
  }

  init() {
    navigator.requestMIDIAccess()
      .then(this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this));
  }

  onMIDISuccess(access) {
    const input = access.inputs.values();

    for (var o = input.next(); !o.done; o = input.next()) {
      this.inputs.push(o.value);
    }
    for (var cnt = 0; cnt < this.inputs.length; cnt++) {
      this.inputs[cnt].onmidimessage = this.onMIDIEvent;
    }
  }

  onMIDIFailure(msg) {
    console.log("oh... midi failed");
    console.log(msg);
  }

  onMIDIEvent(e) {
    const statusByte = e.data[0].toString(16).substring(0, 1);
    const converter = new Converter('major');
    if (statusByte === "8") {
      // interval = e.timeStamp - this.noteOnStamp;
      // this.noteOnStamp = e.timeStamp;
    }
    if (statusByte === "9") {
      converter.setNoteNumber(e.data[1]);
      console.log(converter.toRowIndex());
    }
  }

  addHandler(callback) {
    this.handler = callback;
  }
}

angular.module(modName, [])
.factory('midi', () => {
  return new Midi();
});

export default modName;
<|endoftext|>


<commit_before>/**
 * Copyright 2015 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

var should = require("should");
var csvNode = require("../../../../nodes/core/parsers/70-CSV.js");
var helper = require("../../helper.js");

describe('CSV node', function() {

    before(function(done) {
        helper.startServer(done);
    });

    afterEach(function() {
        helper.unload();
    });

    it('should be loaded with defaults', function(done) {
        var flow = [{id:"csvNode1", type:"csv", name: "csvNode" }];
        helper.load(csvNode, flow, function() {
            var n1 = helper.getNode("csvNode1");
            n1.should.have.property('name', 'csvNode');
            n1.should.have.property('template', [ '' ]);
            n1.should.have.property('sep', ',');
            n1.should.have.property('quo', '"');
            n1.should.have.property('ret', '\n');
            n1.should.have.property('winflag', false);
            n1.should.have.property('lineend', '\n');
            n1.should.have.property('multi', 'one');
            n1.should.have.property('hdrin', false);
            done();
        });
    });

    describe('csv to json', function() {

        it('should convert a simple csv string to a javascript object', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 });
                    done();
                });
                var testString = "1,2,3,4"+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
            });
        });

        it('should remove quotes and whitespace from template', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:'"a",  "b" , " c "," d  " ', wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 });
                    done();
                });
                var testString = "1,2,3,4"+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
            });
        });

        it('should create column names if no template provided', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:'', wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    msg.should.have.property('payload', { col1: 1, col2: 2, col3: 3, col4: 4 });
                    done();
                });
                var testString = "1,2,3,4"+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
            });
        });

        it('should allow dropping of fields from the template', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,,,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    msg.should.have.property('payload', { a: 1, d: 4 });
                    done();
                });
                var testString = "1,2,3,4"+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
            });
        });

        it('should allow quotes in the input', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    //console.log(msg);
                    msg.should.have.property('payload', { a: 1, b: -2, c: '+3', d: 4, e: -5, f: 'ab"cd', g: 'with,a,comma' });
                    done();
                });
                var testString = '"1","-2","+3","04","-05",ab""cd,"with,a,comma"'+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
            });
        });

        it('should recover from an odd number of quotes in the input', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    //console.log(msg);
                    msg.should.have.property('payload', { a: 1, b: -2, c: '+3', d: 4, e: -5, f: 'ab"cd', g: 'with,a,comma' });
                    done();
                });
                var testString = '"with,a"n,odd",num"ber,of"quotes'+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
                testString = '"1","-2","+3","04","-05",ab""cd,"with,a,comma"'+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
            });
        });

        it('should be able to use the first line as a template', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrin:true, wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                var c = 0;
                n2.on("input", function(msg) {
                    //console.log(msg);
                    if (c === 0) {
                        msg.should.have.property('payload', { w: 1, x: 2, y: 3, z: 4 });
                        c += 1;
                    }
                    else {
                        msg.should.have.property('payload', { w: 5, x: 6, y: 7, z: 8 });
                        done();
                    }
                });
                var testString = "w,x,y,z\n1,2,3,4\n\n5,6,7,8";
                n1.emit("input", {payload:testString});
            });
        });

        it('should be able to output multiple lines as one array', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", multi:"yes", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    msg.should.have.property('payload', [ { a: 1, b: 2, c: 3, d: 4 },{ a: 5, b: -6, c: 7, d: '+8' },{ a: 9, b: 0, c: 'a', d: 'b' },{ a: 'c', b: 'd', c: 'e', d: 'f' } ]);
                    done();
                });
                var testString = "1,2,3,4\n5,-6,07,+8\n9,0,a,b\nc,d,e,f";
                n1.emit("input", {payload:testString});
            });
        });
    });

    describe('json object to csv', function() {

        it('should convert a simple object back to a csv', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    try {
                        msg.should.have.property('payload', '4,3,2,1\n');
                        done();
                    }
                    catch(e) { done(e); }
                });
                var testJson = { d: 1, b: 3, c: 2, a: 4 };
                n1.emit("input", {payload:testJson});
            });
        });

        it('should convert an array of objects to a multi-line csv', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    try {
                        msg.should.have.property('payload', '4,3,2,1\n1,2,3,4\n');
                        done();
                    }
                    catch(e) { done(e); }
                });
                var testJson = [{ d: 1, b: 3, c: 2, a: 4 },{d:4,a:1,c:3,b:2}];
                n1.emit("input", {payload:testJson});
            });
        });

        it('should convert a simple array back to a csv', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    try {
                        msg.should.have.property('payload', '1,2,3,4\n');
                        done();
                    }
                    catch(e) { done(e); }
                });
                var testJson = [1,2,3,4];
                n1.emit("input", {payload:testJson});
            });
        });

        it('should convert an array of arrays back to a multi-line csv', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    try {
                        msg.should.have.property('payload', '1,2,3,4\n4,3,2,1\n');
                        done();
                    }
                    catch(e) { done(e); }
                });
                var testJson = [[1,2,3,4],[4,3,2,1]];
                n1.emit("input", {payload:testJson});
            });
        });

        it('should be able to include column names as first row', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrout:true, ret:"\r\n", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    try {
                        msg.should.have.property('payload', 'a,b,c,d\r\n4,3,2,1\r\n');
                        done();
                    }
                    catch(e) { done(e); }
                });
                var testJson = [{ d: 1, b: 3, c: 2, a: 4 }];
                n1.emit("input", {payload:testJson});
            });
        });

        it('should handle quotes and sub-properties', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    try {
                        msg.should.have.property('payload', '{},"text,with,commas","This ""is"" a banana","{""sub"":""object""}"\n');
                        done();
                    }
                    catch(e) { done(e); }
                });
                var testJson = { d: {sub:"object"}, b: "text,with,commas", c: 'This "is" a banana', a: {sub2:undefined} };
                n1.emit("input", {payload:testJson});
            });
        });

    });

    it('should just pass through if no payload provided', function(done) {
        var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                {id:"n2", type:"helper"} ];
        helper.load(csvNode, flow, function() {
            var n1 = helper.getNode("n1");
            var n2 = helper.getNode("n2");
            n2.on("input", function(msg) {
                try {
                    msg.should.have.property('topic', { a: 4, b: 3, c: 2, d: 1 });
                    msg.should.not.have.property('payload');

                    done();
                }
                catch(e) { done(e); }
            });
            var testJson = { d: 1, b: 3, c: 2, a: 4 };
            n1.emit("input", {topic:testJson});
        });
    });

    it('should warn if provided a number or boolean', function(done) {
        var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                {id:"n2", type:"helper"} ];
        helper.load(csvNode, flow, function() {
            var n1 = helper.getNode("n1");
            var n2 = helper.getNode("n2");
            setTimeout(function() {
                try {
                    var logEvents = helper.log().args.filter(function(evt) {
                        return evt[0].type == "csv";
                    });
                    logEvents.should.have.length(2);
                    logEvents[0][0].should.have.a.property('msg');
                    logEvents[0][0].msg.toString().should.startWith('csv.errors.csv_js');
                    logEvents[1][0].should.have.a.property('msg');
                    logEvents[1][0].msg.toString().should.startWith('csv.errors.csv_js');
                    done();
                } catch(err) {
                    done(err);
                }
            },150);
            n1.emit("input", {payload:1});
            n1.emit("input", {payload:true});
        });
    });

});
<commit_msg>fix test for CSV array input<commit_after>/**
 * Copyright 2015 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

var should = require("should");
var csvNode = require("../../../../nodes/core/parsers/70-CSV.js");
var helper = require("../../helper.js");

describe('CSV node', function() {

    before(function(done) {
        helper.startServer(done);
    });

    afterEach(function() {
        helper.unload();
    });

    it('should be loaded with defaults', function(done) {
        var flow = [{id:"csvNode1", type:"csv", name: "csvNode" }];
        helper.load(csvNode, flow, function() {
            var n1 = helper.getNode("csvNode1");
            n1.should.have.property('name', 'csvNode');
            n1.should.have.property('template', [ '' ]);
            n1.should.have.property('sep', ',');
            n1.should.have.property('quo', '"');
            n1.should.have.property('ret', '\n');
            n1.should.have.property('winflag', false);
            n1.should.have.property('lineend', '\n');
            n1.should.have.property('multi', 'one');
            n1.should.have.property('hdrin', false);
            done();
        });
    });

    describe('csv to json', function() {

        it('should convert a simple csv string to a javascript object', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 });
                    done();
                });
                var testString = "1,2,3,4"+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
            });
        });

        it('should remove quotes and whitespace from template', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:'"a",  "b" , " c "," d  " ', wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 });
                    done();
                });
                var testString = "1,2,3,4"+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
            });
        });

        it('should create column names if no template provided', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:'', wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    msg.should.have.property('payload', { col1: 1, col2: 2, col3: 3, col4: 4 });
                    done();
                });
                var testString = "1,2,3,4"+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
            });
        });

        it('should allow dropping of fields from the template', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,,,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    msg.should.have.property('payload', { a: 1, d: 4 });
                    done();
                });
                var testString = "1,2,3,4"+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
            });
        });

        it('should allow quotes in the input', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    //console.log(msg);
                    msg.should.have.property('payload', { a: 1, b: -2, c: '+3', d: 4, e: -5, f: 'ab"cd', g: 'with,a,comma' });
                    done();
                });
                var testString = '"1","-2","+3","04","-05",ab""cd,"with,a,comma"'+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
            });
        });

        it('should recover from an odd number of quotes in the input', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    //console.log(msg);
                    msg.should.have.property('payload', { a: 1, b: -2, c: '+3', d: 4, e: -5, f: 'ab"cd', g: 'with,a,comma' });
                    done();
                });
                var testString = '"with,a"n,odd",num"ber,of"quotes'+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
                testString = '"1","-2","+3","04","-05",ab""cd,"with,a,comma"'+String.fromCharCode(10);
                n1.emit("input", {payload:testString});
            });
        });

        it('should be able to use the first line as a template', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrin:true, wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                var c = 0;
                n2.on("input", function(msg) {
                    //console.log(msg);
                    if (c === 0) {
                        msg.should.have.property('payload', { w: 1, x: 2, y: 3, z: 4 });
                        c += 1;
                    }
                    else {
                        msg.should.have.property('payload', { w: 5, x: 6, y: 7, z: 8 });
                        done();
                    }
                });
                var testString = "w,x,y,z\n1,2,3,4\n\n5,6,7,8";
                n1.emit("input", {payload:testString});
            });
        });

        it('should be able to output multiple lines as one array', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", multi:"yes", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    msg.should.have.property('payload', [ { a: 1, b: 2, c: 3, d: 4 },{ a: 5, b: -6, c: 7, d: '+8' },{ a: 9, b: 0, c: 'a', d: 'b' },{ a: 'c', b: 'd', c: 'e', d: 'f' } ]);
                    done();
                });
                var testString = "1,2,3,4\n5,-6,07,+8\n9,0,a,b\nc,d,e,f";
                n1.emit("input", {payload:testString});
            });
        });
    });

    describe('json object to csv', function() {

        it('should convert a simple object back to a csv', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    try {
                        msg.should.have.property('payload', '4,3,2,1\n');
                        done();
                    }
                    catch(e) { done(e); }
                });
                var testJson = { d: 1, b: 3, c: 2, a: 4 };
                n1.emit("input", {payload:testJson});
            });
        });

        it('should convert an array of objects to a multi-line csv', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    try {
                        msg.should.have.property('payload', '4,3,2,1\n1,2,3,4\n');
                        done();
                    }
                    catch(e) { done(e); }
                });
                var testJson = [{ d: 1, b: 3, c: 2, a: 4 },{d:4,a:1,c:3,b:2}];
                n1.emit("input", {payload:testJson});
            });
        });

        it('should convert a simple array back to a csv', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    try {
                        msg.should.have.property('payload', '0,1,2,3,4\n');
                        done();
                    }
                    catch(e) { done(e); }
                });
                var testJson = [0,1,2,3,4];
                n1.emit("input", {payload:testJson});
            });
        });

        it('should convert an array of arrays back to a multi-line csv', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    try {
                        msg.should.have.property('payload', '0,1,2,3,4\n4,3,2,1,0\n');
                        done();
                    }
                    catch(e) { done(e); }
                });
                var testJson = [[0,1,2,3,4],[4,3,2,1,0]];
                n1.emit("input", {payload:testJson});
            });
        });

        it('should be able to include column names as first row', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrout:true, ret:"\r\n", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    try {
                        msg.should.have.property('payload', 'a,b,c,d\r\n4,3,2,1\r\n');
                        done();
                    }
                    catch(e) { done(e); }
                });
                var testJson = [{ d: 1, b: 3, c: 2, a: 4 }];
                n1.emit("input", {payload:testJson});
            });
        });

        it('should handle quotes and sub-properties', function(done) {
            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                    {id:"n2", type:"helper"} ];
            helper.load(csvNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                n2.on("input", function(msg) {
                    try {
                        msg.should.have.property('payload', '{},"text,with,commas","This ""is"" a banana","{""sub"":""object""}"\n');
                        done();
                    }
                    catch(e) { done(e); }
                });
                var testJson = { d: {sub:"object"}, b: "text,with,commas", c: 'This "is" a banana', a: {sub2:undefined} };
                n1.emit("input", {payload:testJson});
            });
        });

    });

    it('should just pass through if no payload provided', function(done) {
        var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                {id:"n2", type:"helper"} ];
        helper.load(csvNode, flow, function() {
            var n1 = helper.getNode("n1");
            var n2 = helper.getNode("n2");
            n2.on("input", function(msg) {
                try {
                    msg.should.have.property('topic', { a: 4, b: 3, c: 2, d: 1 });
                    msg.should.not.have.property('payload');

                    done();
                }
                catch(e) { done(e); }
            });
            var testJson = { d: 1, b: 3, c: 2, a: 4 };
            n1.emit("input", {topic:testJson});
        });
    });

    it('should warn if provided a number or boolean', function(done) {
        var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
                {id:"n2", type:"helper"} ];
        helper.load(csvNode, flow, function() {
            var n1 = helper.getNode("n1");
            var n2 = helper.getNode("n2");
            setTimeout(function() {
                try {
                    var logEvents = helper.log().args.filter(function(evt) {
                        return evt[0].type == "csv";
                    });
                    logEvents.should.have.length(2);
                    logEvents[0][0].should.have.a.property('msg');
                    logEvents[0][0].msg.toString().should.startWith('csv.errors.csv_js');
                    logEvents[1][0].should.have.a.property('msg');
                    logEvents[1][0].msg.toString().should.startWith('csv.errors.csv_js');
                    done();
                } catch(err) {
                    done(err);
                }
            },150);
            n1.emit("input", {payload:1});
            n1.emit("input", {payload:true});
        });
    });

});
<|endoftext|>


<commit_before>/* global requestAnimationFrame */

(function() {

  function Ticker (fn) {

    var me = this;

    var lastTime = 0;
    var pausedFor = 0;

    function tick (t) {
      var dt = t - lastTime;
      if (me.paused) {
        pausedFor += dt;
      } else {
        fn(dt, t - pausedFor);
      }
      lastTime = t;
      requestAnimationFrame(tick);
    }

    tick(0);

  }

  if (typeof module !== "undefined") {
    module.exports = Ticker;
  } else {
    this.Ticker = Ticker;
  }

})();
<commit_msg>remove some curlies to ensure future bugginess<commit_after>/* global requestAnimationFrame */

(function() {

  function Ticker (fn) {

    var me = this;

    var lastTime = 0;
    var pausedFor = 0;

    function tick (t) {
      var dt = t - lastTime;
      if (me.paused)
        pausedFor += dt;
      else
        fn(dt, t - pausedFor);
      lastTime = t;
      requestAnimationFrame(tick);
    }

    tick(0);

  }

  if (typeof module !== "undefined")
    module.exports = Ticker;
  else
    this.Ticker = Ticker;

})();
<|endoftext|>


<commit_before>const {shell, clipboard, remote} = require('electron')
const {app} = remote || require('electron')

const helper = require('../modules/helper')
const setting = remote && remote.require('./modules/setting')

const sabaki = typeof window !== 'undefined' && window.sabaki
const dialog = sabaki && require('../modules/dialog')
const gametree = sabaki && require('../modules/gametree')

let toggleSetting = key => setting.set(key, !setting.get(key))
let selectTool = tool => (sabaki.setMode('edit'), sabaki.setState({selectedTool: tool}))
let treePosition = () => sabaki.state.treePosition

let data = [
    {
        label: '&File',
        submenu: [
            {
                label: '&New',
                accelerator: 'CmdOrCtrl+N',
                click: () => sabaki.newFile({playSound: true, showInfo: true})
            },
            {
                label: 'New &Window',
                accelerator: 'CmdOrCtrl+Shift+N',
                clickMain: 'newWindow',
                enabled: true
            },
            {type: 'separator'},
            {
                label: '&Open…',
                accelerator: 'CmdOrCtrl+O',
                click: () => sabaki.loadFile()
            },
            {
                label: '&Save',
                accelerator: 'CmdOrCtrl+S',
                click: () => sabaki.saveFile(sabaki.state.representedFilename)
            },
            {
                label: 'Sa&ve As…',
                accelerator: 'CmdOrCtrl+Shift+S',
                click: () => sabaki.saveFile()
            },
            {type: 'separator'},
            {
                label: '&Clipboard',
                submenu: [
                    {
                        label: '&Load SGF',
                        click: () => sabaki.loadContent(clipboard.readText(), 'sgf', {ignoreEncoding: true})
                    },
                    {
                        label: '&Copy SGF',
                        click: () => clipboard.writeText(sabaki.getSGF())
                    },
                    {
                        label: 'Copy &ASCII Diagram',
                        click: () => clipboard.writeText(gametree.getBoard(...treePosition()).generateAscii())
                    }
                ]
            },
            {type: 'separator'},
            {
                label: 'Game &Info',
                accelerator: 'CmdOrCtrl+I',
                click: () => sabaki.openDrawer('info')
            },
            {
                label: '&Manage Games…',
                accelerator: 'CmdOrCtrl+Shift+M',
                click: () => sabaki.openDrawer('gamechooser')
            },
            {type: 'separator'},
            {
                label: '&Preferences…',
                accelerator: 'CmdOrCtrl+,',
                click: () => sabaki.openDrawer('preferences')
            }
        ]
    },
    {
        label: '&Play',
        submenu: [
            {
                label: '&Toggle Player',
                click: () => sabaki.setPlayer(...treePosition(), -sabaki.getPlayer(...treePosition()))
            },
            {type: 'separator'},
            {
                label: '&Select Point',
                accelerator: 'CmdOrCtrl+L',
                click: () => dialog.showInputBox('Enter a coordinate to select a point', ({value}) => {
                    sabaki.clickVertex(value)
                })
            },
            {
                label: '&Pass',
                accelerator: 'CmdOrCtrl+P',
                click: () => sabaki.makeMove([-1, -1])
            },
            {
                label: '&Resign',
                click: () => sabaki.makeResign()
            },
            {type: 'separator'},
            {
                label: '&Estimate',
                click: () => sabaki.setMode('estimator')
            },
            {
                label: 'Sc&ore',
                click: () => sabaki.setMode('scoring')
            }
        ]
    },
    {
        label: '&Edit',
        submenu: [
            {
                label: 'Toggle &Edit Mode',
                accelerator: 'CmdOrCtrl+E',
                click: () => sabaki.setMode(sabaki.state.mode === 'edit' ? 'play' : 'edit')
            },
            {
                label: 'Clean &Markup…',
                click: () => sabaki.openDrawer('cleanmarkup')
            },
            {
                label: '&Select Tool',
                submenu: [
                    {
                        label: '&Stone Tool',
                        accelerator: 'CmdOrCtrl+1',
                        click: () => selectTool('stone_1')
                    },
                    {
                        label: '&Cross Tool',
                        accelerator: 'CmdOrCtrl+2',
                        click: () => selectTool('cross')
                    },
                    {
                        label: '&Triangle Tool',
                        accelerator: 'CmdOrCtrl+3',
                        click: () => selectTool('triangle')
                    },
                    {
                        label: 'S&quare Tool',
                        accelerator: 'CmdOrCtrl+4',
                        click: () => selectTool('square')
                    },
                    {
                        label: 'C&ircle Tool',
                        accelerator: 'CmdOrCtrl+5',
                        click: () => selectTool('circle')
                    },
                    {
                        label: '&Line Tool',
                        accelerator: 'CmdOrCtrl+6',
                        click: () => selectTool('line')
                    },
                    {
                        label: '&Arrow Tool',
                        accelerator: 'CmdOrCtrl+7',
                        click: () => selectTool('arrow')
                    },
                    {
                        label: 'La&bel Tool',
                        accelerator: 'CmdOrCtrl+8',
                        click: () => selectTool('label')
                    },
                    {
                        label: '&Number Tool',
                        accelerator: 'CmdOrCtrl+9',
                        click: () => selectTool('number')
                    }
                ]
            },
            {type: 'separator'},
            {
                label: '&Copy Variation',
                click: () => sabaki.copyVariation(...treePosition())
            },
            {
                label: 'Cu&t Variation',
                click: () => sabaki.cutVariation(...treePosition())
            },
            {
                label: '&Paste Variation',
                click: () => sabaki.pasteVariation(...treePosition())
            },
            {type: 'separator'},
            {
                label: 'Make Main &Variation',
                click: () => sabaki.makeMainVariation(...treePosition())
            },
            {
                label: 'Shift &Left',
                click: () => sabaki.shiftVariation(...treePosition(), -1)
            },
            {
                label: 'Shift Ri&ght',
                click: () => sabaki.shiftVariation(...treePosition(), 1)
            },
            {type: 'separator'},
            {
                label: '&Flatten',
                click: () => sabaki.flattenVariation(...treePosition())
            },
            {
                label: '&Remove Node',
                accelerator: 'CmdOrCtrl+Delete',
                click: () => sabaki.removeNode(...treePosition())
            },
            {
                label: 'Remove &Other Variations',
                click: () => sabaki.removeOtherVariations(...treePosition())
            }
        ]
    },
    {
        label: 'Fin&d',
        submenu: [
            {
                label: 'Toggle &Find Mode',
                accelerator: 'CmdOrCtrl+F',
                click: () => sabaki.setMode(sabaki.state.mode === 'find' ? 'play' : 'find'),
            },
            {
                label: 'Find &Next',
                accelerator: 'F3',
                click: () => {
                    sabaki.setMode('find')
                    sabaki.findMove(1, {
                        vertex: sabaki.state.findVertex,
                        text: sabaki.state.findText
                    })
                }
            },
            {
                label: 'Find &Previous',
                accelerator: 'Shift+F3',
                click: () => {
                    sabaki.setMode('find')
                    sabaki.findMove(-1, {
                        vertex: sabaki.state.findVertex,
                        text: sabaki.state.findText
                    })
                }
            },
            {type: 'separator'},
            {
                label: 'Toggle &Hotspot',
                accelerator: 'CmdOrCtrl+B',
                click: () => sabaki.setComment(...treePosition(), {
                    hotspot: !('HO' in treePosition()[0].nodes[treePosition()[1]])
                })
            },
            {
                label: 'Jump to Ne&xt Hotspot',
                accelerator: 'F2',
                click: () => sabaki.findHotspot(1),
            },
            {
                label: 'Jump to Pre&vious Hotspot',
                accelerator: 'Shift+F2',
                click: () => sabaki.findHotspot(-1),
            }
        ]
    },
    {
        label: '&Navigation',
        submenu: [
            {
                label: '&Back',
                accelerator: 'Up',
                click: () => sabaki.goStep(-1)
            },
            {
                label: '&Forward',
                accelerator: 'Down',
                click: () => sabaki.goStep(1)
            },
            {type: 'separator'},
            {
                label: 'Go to &Previous Fork',
                accelerator: 'CmdOrCtrl+Up',
                click: () => sabaki.goToPreviousFork()
            },
            {
                label: 'Go to &Next Fork',
                accelerator: 'CmdOrCtrl+Down',
                click: () => sabaki.goToNextFork()
            },
            {type: 'separator'},
            {
                label: 'Go to Previous Commen&t',
                accelerator: 'CmdOrCtrl+Shift+Up',
                click: () => sabaki.goToComment(-1)
            },
            {
                label: 'Go to Next &Comment',
                accelerator: 'CmdOrCtrl+Shift+Down',
                click: () => sabaki.goToComment(1)
            },
            {type: 'separator'},
            {
                label: 'Go to Be&ginning',
                accelerator: 'Home',
                click: () => sabaki.goToBeginning()
            },
            {
                label: 'Go to &End',
                accelerator: 'End',
                click: () => sabaki.goToEnd()
            },
            {type: 'separator'},
            {
                label: 'Go to &Main Variation',
                accelerator: 'CmdOrCtrl+Left',
                click: () => sabaki.goToMainVariation()
            },
            {
                label: 'Go to Previous &Variation',
                accelerator: 'Left',
                click: () => sabaki.goToSiblingVariation(-1)
            },
            {
                label: 'Go to Next Va&riation',
                accelerator: 'Right',
                click: () => sabaki.goToSiblingVariation(1)
            },
            {type: 'separator'},
            {
                label: 'Go to Move N&umber',
                accelerator: 'CmdOrCtrl+G',
                click: () => dialog.showInputBox('Enter a move number to go to', ({value}) => {
                    sabaki.closeDrawer()
                    sabaki.goToMoveNumber(value)
                })
            }
        ]
    },
    {
        label: 'Eng&ines',
        submenu: [
            {
                label: '&Attach…',
                click: () => sabaki.openDrawer('info')
            },
            {
                label: '&Detach',
                click: () => sabaki.detachEngines()
            },
            {
                label: '&Suspend',
                click: () => sabaki.suspendEngines()
            },
            {type: 'separator'},
            {
                label: 'Manage &Engines…',
                click: () => (sabaki.setState({preferencesTab: 'engines'}), sabaki.openDrawer('preferences'))
            },
            {
                label: 'Generate &Move',
                accelerator: 'F5',
                click: () => sabaki.startGeneratingMoves()
            },
            {type: 'separator'},
            {
                label: 'Toggle &GTP Console',
                click: () => toggleSetting('view.show_leftsidebar')
            },
            {
                label: '&Clear Console',
                click: () => sabaki.setState({consoleLog: []})
            }
        ]
    },
    {
        label: '&View',
        submenu: [
            {
                label: 'Toggle Menu &Bar',
                click: () => toggleSetting('view.show_menubar')
            },
            {
                label: 'Toggle &Full Screen',
                accelerator: 'CmdOrCtrl+Shift+F',
                click: () => sabaki.setState(({fullScreen}) => ({fullScreen: !fullScreen}))
            },
            {type: 'separator'},
            {
                label: 'Toggle Auto&play Mode',
                click: () => sabaki.setMode(sabaki.state.mode === 'autoplay' ? 'play' : 'autoplay')
            },
            {
                label: 'Toggle G&uess Mode',
                click: () => sabaki.setMode(sabaki.state.mode === 'guess' ? 'play' : 'guess')
            },
            {type: 'separator'},
            {
                label: 'Show &Coordinates',
                accelerator: 'CmdOrCtrl+Shift+C',
                checked: 'view.show_coordinates',
                click: () => toggleSetting('view.show_coordinates')
            },
            {
                label: 'Show Move Colori&zation',
                checked: 'view.show_move_colorization',
                click: () => toggleSetting('view.show_move_colorization')
            },
            {
                label: 'Show &Next Moves',
                checked: 'view.show_next_moves',
                click: () => toggleSetting('view.show_next_moves')
            },
            {
                label: 'Show &Sibling Variations',
                checked: 'view.show_siblings',
                click: () => toggleSetting('view.show_siblings')
            },
            {type: 'separator'},
            {
                label: 'Show Game &Tree',
                checked: 'view.show_graph',
                accelerator: 'CmdOrCtrl+T',
                click: () => toggleSetting('view.show_graph')
            },
            {
                label: 'Show Co&mments',
                checked: 'view.show_comments',
                accelerator: 'CmdOrCtrl+Shift+T',
                click: () => toggleSetting('view.show_comments')
            },
            {type: 'separator'},
            {
                label: 'Z&oom Factor',
                submenu: [
                    {
                        label: 'Increase',
                        accelerator: 'CmdOrCtrl+Plus',
                        click: () => setting.set('app.zoom_factor',
                            setting.get('app.zoom_factor') + .1
                        )
                    },
                    {
                        label: 'Decrease',
                        accelerator: 'CmdOrCtrl+-',
                        click: () => setting.set('app.zoom_factor',
                            Math.max(0, setting.get('app.zoom_factor') - .1)
                        )
                    },
                    {
                        label: 'Reset',
                        accelerator: 'CmdOrCtrl+0',
                        click: () => setting.set('app.zoom_factor', 1)
                    }
                ]
            }
        ]
    },
    {
        label: '&Help',
        submenu: [
            {
                label: `${app.getName()} v${app.getVersion()}`,
                enabled: false
            },
            {
                label: 'Check for &Updates',
                clickMain: 'checkForUpdates',
                enabled: true
            },
            {type: 'separator'},
            {
                label: 'GitHub &Respository',
                click: () => shell.openExternal(`https://github.com/yishn/${sabaki.appName}`)
            },
            {
                label: 'Report &Issue',
                click: () => shell.openExternal(`https://github.com/yishn/${sabaki.appName}/issues`)
            }
        ]
    }
]

let findMenuItem = str => data.find(item => item.label.replace('&', '') === str)

// Modify menu for macOS

if (process.platform === 'darwin') {
    // Add 'App' menu

    let appMenu = [{role: 'about'}]
    let helpMenu = findMenuItem('Help')
    let items = helpMenu.submenu.splice(0, 3)

    appMenu.push(...items.slice(0, 2))

    // Remove original 'Preferences' menu item

    let fileMenu = findMenuItem('File')
    let preferenceItem = fileMenu.submenu.splice(fileMenu.submenu.length - 2, 2)[1]

    appMenu.push(
        {type: 'separator'},
        preferenceItem,
        {type: 'separator'},
        {submenu: [], role: 'services'},
        {
            label: 'Text',
            submenu: [
                {role: 'undo'},
                {role: 'redo'},
                {type: 'separator'},
                {role: 'cut'},
                {role: 'copy'},
                {role: 'paste'},
                {role: 'selectall'}
            ]
        },
        {type: 'separator'},
        {role: 'hide'},
        {role: 'hideothers'},
        {type: 'separator'},
        {role: 'quit'}
    )

    data.unshift({
        label: app.getName(),
        submenu: appMenu
    })

    // Add 'Window' menu

    data.splice(data.length - 1, 0, {
        submenu: [
            {
                label: 'New Window',
                clickMain: 'newWindow',
                enabled: true
            },
            {role: 'minimize'},
            {type: 'separator'},
            {role: 'front'}
        ],
        role: 'window'
    })

    // Remove 'Toggle Menu Bar' menu item

    let viewMenu = findMenuItem('View')
    viewMenu.submenu.splice(0, 1)
}

// Generate ids for all menu items

let generateIds = (menu, idPrefix = '') => {
    menu.forEach((item, i) => {
        item.id = idPrefix + i

        if ('submenu' in item) {
            generateIds(item.submenu, `${item.id}-`)
        }
    })
}

generateIds(data)

module.exports = exports = data

exports.clone = function() {
    return helper.clone(data)
}
<commit_msg>Rename 'Zoom Factor' to 'Zoom'<commit_after>const {shell, clipboard, remote} = require('electron')
const {app} = remote || require('electron')

const helper = require('../modules/helper')
const setting = remote && remote.require('./modules/setting')

const sabaki = typeof window !== 'undefined' && window.sabaki
const dialog = sabaki && require('../modules/dialog')
const gametree = sabaki && require('../modules/gametree')

let toggleSetting = key => setting.set(key, !setting.get(key))
let selectTool = tool => (sabaki.setMode('edit'), sabaki.setState({selectedTool: tool}))
let treePosition = () => sabaki.state.treePosition

let data = [
    {
        label: '&File',
        submenu: [
            {
                label: '&New',
                accelerator: 'CmdOrCtrl+N',
                click: () => sabaki.newFile({playSound: true, showInfo: true})
            },
            {
                label: 'New &Window',
                accelerator: 'CmdOrCtrl+Shift+N',
                clickMain: 'newWindow',
                enabled: true
            },
            {type: 'separator'},
            {
                label: '&Open…',
                accelerator: 'CmdOrCtrl+O',
                click: () => sabaki.loadFile()
            },
            {
                label: '&Save',
                accelerator: 'CmdOrCtrl+S',
                click: () => sabaki.saveFile(sabaki.state.representedFilename)
            },
            {
                label: 'Sa&ve As…',
                accelerator: 'CmdOrCtrl+Shift+S',
                click: () => sabaki.saveFile()
            },
            {type: 'separator'},
            {
                label: '&Clipboard',
                submenu: [
                    {
                        label: '&Load SGF',
                        click: () => sabaki.loadContent(clipboard.readText(), 'sgf', {ignoreEncoding: true})
                    },
                    {
                        label: '&Copy SGF',
                        click: () => clipboard.writeText(sabaki.getSGF())
                    },
                    {
                        label: 'Copy &ASCII Diagram',
                        click: () => clipboard.writeText(gametree.getBoard(...treePosition()).generateAscii())
                    }
                ]
            },
            {type: 'separator'},
            {
                label: 'Game &Info',
                accelerator: 'CmdOrCtrl+I',
                click: () => sabaki.openDrawer('info')
            },
            {
                label: '&Manage Games…',
                accelerator: 'CmdOrCtrl+Shift+M',
                click: () => sabaki.openDrawer('gamechooser')
            },
            {type: 'separator'},
            {
                label: '&Preferences…',
                accelerator: 'CmdOrCtrl+,',
                click: () => sabaki.openDrawer('preferences')
            }
        ]
    },
    {
        label: '&Play',
        submenu: [
            {
                label: '&Toggle Player',
                click: () => sabaki.setPlayer(...treePosition(), -sabaki.getPlayer(...treePosition()))
            },
            {type: 'separator'},
            {
                label: '&Select Point',
                accelerator: 'CmdOrCtrl+L',
                click: () => dialog.showInputBox('Enter a coordinate to select a point', ({value}) => {
                    sabaki.clickVertex(value)
                })
            },
            {
                label: '&Pass',
                accelerator: 'CmdOrCtrl+P',
                click: () => sabaki.makeMove([-1, -1])
            },
            {
                label: '&Resign',
                click: () => sabaki.makeResign()
            },
            {type: 'separator'},
            {
                label: '&Estimate',
                click: () => sabaki.setMode('estimator')
            },
            {
                label: 'Sc&ore',
                click: () => sabaki.setMode('scoring')
            }
        ]
    },
    {
        label: '&Edit',
        submenu: [
            {
                label: 'Toggle &Edit Mode',
                accelerator: 'CmdOrCtrl+E',
                click: () => sabaki.setMode(sabaki.state.mode === 'edit' ? 'play' : 'edit')
            },
            {
                label: 'Clean &Markup…',
                click: () => sabaki.openDrawer('cleanmarkup')
            },
            {
                label: '&Select Tool',
                submenu: [
                    {
                        label: '&Stone Tool',
                        accelerator: 'CmdOrCtrl+1',
                        click: () => selectTool('stone_1')
                    },
                    {
                        label: '&Cross Tool',
                        accelerator: 'CmdOrCtrl+2',
                        click: () => selectTool('cross')
                    },
                    {
                        label: '&Triangle Tool',
                        accelerator: 'CmdOrCtrl+3',
                        click: () => selectTool('triangle')
                    },
                    {
                        label: 'S&quare Tool',
                        accelerator: 'CmdOrCtrl+4',
                        click: () => selectTool('square')
                    },
                    {
                        label: 'C&ircle Tool',
                        accelerator: 'CmdOrCtrl+5',
                        click: () => selectTool('circle')
                    },
                    {
                        label: '&Line Tool',
                        accelerator: 'CmdOrCtrl+6',
                        click: () => selectTool('line')
                    },
                    {
                        label: '&Arrow Tool',
                        accelerator: 'CmdOrCtrl+7',
                        click: () => selectTool('arrow')
                    },
                    {
                        label: 'La&bel Tool',
                        accelerator: 'CmdOrCtrl+8',
                        click: () => selectTool('label')
                    },
                    {
                        label: '&Number Tool',
                        accelerator: 'CmdOrCtrl+9',
                        click: () => selectTool('number')
                    }
                ]
            },
            {type: 'separator'},
            {
                label: '&Copy Variation',
                click: () => sabaki.copyVariation(...treePosition())
            },
            {
                label: 'Cu&t Variation',
                click: () => sabaki.cutVariation(...treePosition())
            },
            {
                label: '&Paste Variation',
                click: () => sabaki.pasteVariation(...treePosition())
            },
            {type: 'separator'},
            {
                label: 'Make Main &Variation',
                click: () => sabaki.makeMainVariation(...treePosition())
            },
            {
                label: 'Shift &Left',
                click: () => sabaki.shiftVariation(...treePosition(), -1)
            },
            {
                label: 'Shift Ri&ght',
                click: () => sabaki.shiftVariation(...treePosition(), 1)
            },
            {type: 'separator'},
            {
                label: '&Flatten',
                click: () => sabaki.flattenVariation(...treePosition())
            },
            {
                label: '&Remove Node',
                accelerator: 'CmdOrCtrl+Delete',
                click: () => sabaki.removeNode(...treePosition())
            },
            {
                label: 'Remove &Other Variations',
                click: () => sabaki.removeOtherVariations(...treePosition())
            }
        ]
    },
    {
        label: 'Fin&d',
        submenu: [
            {
                label: 'Toggle &Find Mode',
                accelerator: 'CmdOrCtrl+F',
                click: () => sabaki.setMode(sabaki.state.mode === 'find' ? 'play' : 'find'),
            },
            {
                label: 'Find &Next',
                accelerator: 'F3',
                click: () => {
                    sabaki.setMode('find')
                    sabaki.findMove(1, {
                        vertex: sabaki.state.findVertex,
                        text: sabaki.state.findText
                    })
                }
            },
            {
                label: 'Find &Previous',
                accelerator: 'Shift+F3',
                click: () => {
                    sabaki.setMode('find')
                    sabaki.findMove(-1, {
                        vertex: sabaki.state.findVertex,
                        text: sabaki.state.findText
                    })
                }
            },
            {type: 'separator'},
            {
                label: 'Toggle &Hotspot',
                accelerator: 'CmdOrCtrl+B',
                click: () => sabaki.setComment(...treePosition(), {
                    hotspot: !('HO' in treePosition()[0].nodes[treePosition()[1]])
                })
            },
            {
                label: 'Jump to Ne&xt Hotspot',
                accelerator: 'F2',
                click: () => sabaki.findHotspot(1),
            },
            {
                label: 'Jump to Pre&vious Hotspot',
                accelerator: 'Shift+F2',
                click: () => sabaki.findHotspot(-1),
            }
        ]
    },
    {
        label: '&Navigation',
        submenu: [
            {
                label: '&Back',
                accelerator: 'Up',
                click: () => sabaki.goStep(-1)
            },
            {
                label: '&Forward',
                accelerator: 'Down',
                click: () => sabaki.goStep(1)
            },
            {type: 'separator'},
            {
                label: 'Go to &Previous Fork',
                accelerator: 'CmdOrCtrl+Up',
                click: () => sabaki.goToPreviousFork()
            },
            {
                label: 'Go to &Next Fork',
                accelerator: 'CmdOrCtrl+Down',
                click: () => sabaki.goToNextFork()
            },
            {type: 'separator'},
            {
                label: 'Go to Previous Commen&t',
                accelerator: 'CmdOrCtrl+Shift+Up',
                click: () => sabaki.goToComment(-1)
            },
            {
                label: 'Go to Next &Comment',
                accelerator: 'CmdOrCtrl+Shift+Down',
                click: () => sabaki.goToComment(1)
            },
            {type: 'separator'},
            {
                label: 'Go to Be&ginning',
                accelerator: 'Home',
                click: () => sabaki.goToBeginning()
            },
            {
                label: 'Go to &End',
                accelerator: 'End',
                click: () => sabaki.goToEnd()
            },
            {type: 'separator'},
            {
                label: 'Go to &Main Variation',
                accelerator: 'CmdOrCtrl+Left',
                click: () => sabaki.goToMainVariation()
            },
            {
                label: 'Go to Previous &Variation',
                accelerator: 'Left',
                click: () => sabaki.goToSiblingVariation(-1)
            },
            {
                label: 'Go to Next Va&riation',
                accelerator: 'Right',
                click: () => sabaki.goToSiblingVariation(1)
            },
            {type: 'separator'},
            {
                label: 'Go to Move N&umber',
                accelerator: 'CmdOrCtrl+G',
                click: () => dialog.showInputBox('Enter a move number to go to', ({value}) => {
                    sabaki.closeDrawer()
                    sabaki.goToMoveNumber(value)
                })
            }
        ]
    },
    {
        label: 'Eng&ines',
        submenu: [
            {
                label: '&Attach…',
                click: () => sabaki.openDrawer('info')
            },
            {
                label: '&Detach',
                click: () => sabaki.detachEngines()
            },
            {
                label: '&Suspend',
                click: () => sabaki.suspendEngines()
            },
            {type: 'separator'},
            {
                label: 'Manage &Engines…',
                click: () => (sabaki.setState({preferencesTab: 'engines'}), sabaki.openDrawer('preferences'))
            },
            {
                label: 'Generate &Move',
                accelerator: 'F5',
                click: () => sabaki.startGeneratingMoves()
            },
            {type: 'separator'},
            {
                label: 'Toggle &GTP Console',
                click: () => toggleSetting('view.show_leftsidebar')
            },
            {
                label: '&Clear Console',
                click: () => sabaki.setState({consoleLog: []})
            }
        ]
    },
    {
        label: '&View',
        submenu: [
            {
                label: 'Toggle Menu &Bar',
                click: () => toggleSetting('view.show_menubar')
            },
            {
                label: 'Toggle &Full Screen',
                accelerator: 'CmdOrCtrl+Shift+F',
                click: () => sabaki.setState(({fullScreen}) => ({fullScreen: !fullScreen}))
            },
            {type: 'separator'},
            {
                label: 'Toggle Auto&play Mode',
                click: () => sabaki.setMode(sabaki.state.mode === 'autoplay' ? 'play' : 'autoplay')
            },
            {
                label: 'Toggle G&uess Mode',
                click: () => sabaki.setMode(sabaki.state.mode === 'guess' ? 'play' : 'guess')
            },
            {type: 'separator'},
            {
                label: 'Show &Coordinates',
                accelerator: 'CmdOrCtrl+Shift+C',
                checked: 'view.show_coordinates',
                click: () => toggleSetting('view.show_coordinates')
            },
            {
                label: 'Show Move Colori&zation',
                checked: 'view.show_move_colorization',
                click: () => toggleSetting('view.show_move_colorization')
            },
            {
                label: 'Show &Next Moves',
                checked: 'view.show_next_moves',
                click: () => toggleSetting('view.show_next_moves')
            },
            {
                label: 'Show &Sibling Variations',
                checked: 'view.show_siblings',
                click: () => toggleSetting('view.show_siblings')
            },
            {type: 'separator'},
            {
                label: 'Show Game &Tree',
                checked: 'view.show_graph',
                accelerator: 'CmdOrCtrl+T',
                click: () => toggleSetting('view.show_graph')
            },
            {
                label: 'Show Co&mments',
                checked: 'view.show_comments',
                accelerator: 'CmdOrCtrl+Shift+T',
                click: () => toggleSetting('view.show_comments')
            },
            {type: 'separator'},
            {
                label: 'Z&oom',
                submenu: [
                    {
                        label: 'Increase',
                        accelerator: 'CmdOrCtrl+Plus',
                        click: () => setting.set('app.zoom_factor',
                            setting.get('app.zoom_factor') + .1
                        )
                    },
                    {
                        label: 'Decrease',
                        accelerator: 'CmdOrCtrl+-',
                        click: () => setting.set('app.zoom_factor',
                            Math.max(0, setting.get('app.zoom_factor') - .1)
                        )
                    },
                    {
                        label: 'Reset',
                        accelerator: 'CmdOrCtrl+0',
                        click: () => setting.set('app.zoom_factor', 1)
                    }
                ]
            }
        ]
    },
    {
        label: '&Help',
        submenu: [
            {
                label: `${app.getName()} v${app.getVersion()}`,
                enabled: false
            },
            {
                label: 'Check for &Updates',
                clickMain: 'checkForUpdates',
                enabled: true
            },
            {type: 'separator'},
            {
                label: 'GitHub &Respository',
                click: () => shell.openExternal(`https://github.com/yishn/${sabaki.appName}`)
            },
            {
                label: 'Report &Issue',
                click: () => shell.openExternal(`https://github.com/yishn/${sabaki.appName}/issues`)
            }
        ]
    }
]

let findMenuItem = str => data.find(item => item.label.replace('&', '') === str)

// Modify menu for macOS

if (process.platform === 'darwin') {
    // Add 'App' menu

    let appMenu = [{role: 'about'}]
    let helpMenu = findMenuItem('Help')
    let items = helpMenu.submenu.splice(0, 3)

    appMenu.push(...items.slice(0, 2))

    // Remove original 'Preferences' menu item

    let fileMenu = findMenuItem('File')
    let preferenceItem = fileMenu.submenu.splice(fileMenu.submenu.length - 2, 2)[1]

    appMenu.push(
        {type: 'separator'},
        preferenceItem,
        {type: 'separator'},
        {submenu: [], role: 'services'},
        {
            label: 'Text',
            submenu: [
                {role: 'undo'},
                {role: 'redo'},
                {type: 'separator'},
                {role: 'cut'},
                {role: 'copy'},
                {role: 'paste'},
                {role: 'selectall'}
            ]
        },
        {type: 'separator'},
        {role: 'hide'},
        {role: 'hideothers'},
        {type: 'separator'},
        {role: 'quit'}
    )

    data.unshift({
        label: app.getName(),
        submenu: appMenu
    })

    // Add 'Window' menu

    data.splice(data.length - 1, 0, {
        submenu: [
            {
                label: 'New Window',
                clickMain: 'newWindow',
                enabled: true
            },
            {role: 'minimize'},
            {type: 'separator'},
            {role: 'front'}
        ],
        role: 'window'
    })

    // Remove 'Toggle Menu Bar' menu item

    let viewMenu = findMenuItem('View')
    viewMenu.submenu.splice(0, 1)
}

// Generate ids for all menu items

let generateIds = (menu, idPrefix = '') => {
    menu.forEach((item, i) => {
        item.id = idPrefix + i

        if ('submenu' in item) {
            generateIds(item.submenu, `${item.id}-`)
        }
    })
}

generateIds(data)

module.exports = exports = data

exports.clone = function() {
    return helper.clone(data)
}
<|endoftext|>


<commit_before>import * as defaultConfig from '../config.json'
import * as _ from 'lodash'

export function getFileName (path) {
  return path.replace(/^.*[\\\/]/, '')
}

export function getFilePath (path) {
  return path.match(/^.*[\\\/]/)[0]
}

export function bytesToSize (bytes, keepDigits) {
  bytes = Number(bytes)
  var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
  if (bytes === 0) return '0 KB'
  var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)), 10)
  if (i === 0) return bytes + ' ' + sizes[i]
  var k = [1, 1, 10, 100, 100][i]
  if (Number.isInteger(keepDigits)) k = Math.pow(10, keepDigits)
  return `${Math.floor(bytes / Math.pow(1024, i) * k) / k} ${sizes[i]}`
}

export function addZeros (num, length, zero = '0') {
  var str = String(num)
  return new Array(length - str.length + 1).join(zero) + str
}

export function findDiff (names) {
  let name = names[0]
  let common = ''
  for (var i = 0; i < name.length; i++) {
    let test = `${common}${name[i]}`
    let success = true
    for (let t = 0; t < names.length; t++) {
      if (names[t].indexOf(test) !== 0) {
        success = false
        break
      }
    }
    if (!success) break
    common = test
  }
  return {
    common,
    diff: names.map(n => n.substr(common.length))
  }
}

export function generateGid (i = 0) {
  return addZeros(Date.now().toString(16), 14, 'f') + addZeros(i.toString(16), 2)
}

export function getQueryParam (param) {
  param = param.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]')
  var regex = new RegExp('[\\?&]' + param + '=([^&#]*)')
  var results = regex.exec(window.location.href)
  if (!results) return null
  else return window.decodeURIComponent(results[1])
}

export function parseServerString (s) {
  /* eslint-disable no-unused-vars */
  var [address, secret] = s.split('||')
  var [url, ssl, host, port, extension] = address.match(/(\w+):\/\/([^\:|\/]+)(\:\d*)?(.*)/i)
  ssl = ssl === 'https'
  port = port ? port.replace(':', '') : null
  extension = extension.replace('/', '')
  var server = { ssl, host, port, extension, secret }
  server = _.omitBy(server, val => !val && val !== false)
  return Object.assign({}, defaultConfig.defaultServer, server)
}
<commit_msg>replace getFileName with getEntryFileName<commit_after>import * as defaultConfig from '../config.json'
import * as _ from 'lodash'

export function getEntryFileName (listEntry) {
  let name = ''
  if (listEntry.bittorrent) name = listEntry.bittorrent.info.name
  else name = listEntry.files[0].path.replace(/^.*[\\\/]/, '')
  if (!name) name = listEntry.files[0].uris[0].uri
  return name || '...'
}

export function getFilePath (path) {
  return path.match(/^.*[\\\/]/)[0]
}

export function bytesToSize (bytes, keepDigits) {
  bytes = Number(bytes)
  var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
  if (bytes === 0) return '0 KB'
  var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)), 10)
  if (i === 0) return bytes + ' ' + sizes[i]
  var k = [1, 1, 10, 100, 100][i]
  if (Number.isInteger(keepDigits)) k = Math.pow(10, keepDigits)
  return `${Math.floor(bytes / Math.pow(1024, i) * k) / k} ${sizes[i]}`
}

export function addZeros (num, length, zero = '0') {
  var str = String(num)
  return new Array(length - str.length + 1).join(zero) + str
}

export function findDiff (names) {
  let name = names[0]
  let common = ''
  for (var i = 0; i < name.length; i++) {
    let test = `${common}${name[i]}`
    let success = true
    for (let t = 0; t < names.length; t++) {
      if (names[t].indexOf(test) !== 0) {
        success = false
        break
      }
    }
    if (!success) break
    common = test
  }
  return {
    common,
    diff: names.map(n => n.substr(common.length))
  }
}

export function generateGid (i = 0) {
  return addZeros(Date.now().toString(16), 14, 'f') + addZeros(i.toString(16), 2)
}

export function getQueryParam (param) {
  param = param.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]')
  var regex = new RegExp('[\\?&]' + param + '=([^&#]*)')
  var results = regex.exec(window.location.href)
  if (!results) return null
  else return window.decodeURIComponent(results[1])
}

export function parseServerString (s) {
  /* eslint-disable no-unused-vars */
  var [address, secret] = s.split('||')
  var [url, ssl, host, port, extension] = address.match(/(\w+):\/\/([^\:|\/]+)(\:\d*)?(.*)/i)
  ssl = ssl === 'https'
  port = port ? port.replace(':', '') : null
  extension = extension.replace('/', '')
  var server = { ssl, host, port, extension, secret }
  server = _.omitBy(server, val => !val && val !== false)
  return Object.assign({}, defaultConfig.defaultServer, server)
}
<|endoftext|>