Initial commit

Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com>
Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com>
Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com>
Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com>
Co-Authored-By: Johan  <4934465+joolsus@users.noreply.github.com>
Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com>
Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
Michael Cartner
2024-01-26 11:52:55 +01:00
commit b9c60b07dc
2789 changed files with 868795 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Noodl Jasmine Spec Runner</title>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-3.7.1/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="lib/jasmine-3.7.1/jasmine.css">
<!-- libs needed for some of the views -->
<script type="text/javascript" src="../src/assets/lib/jquery-min.js"></script>
<script type="text/javascript" src="../src/assets/lib/jquery.autosize.min.js"></script>
<script type="text/javascript">
var fs = require('fs');
['.html', '.css'].forEach(ext => {
require.extensions[ext] = function (module, filename) {
module.exports = fs.readFileSync(filename, 'utf8');
};
});
require.extensions['.json'] = function (module, filename) {
module.exports = JSON.parse(fs.readFileSync(filename, 'utf8'));
};
</script>
<script type="text/javascript" src="lib/jasmine-3.7.1/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-3.7.1/jasmine-html.js"></script>
<script type="text/javascript" src="lib/jasmine-3.7.1/boot.js"></script>
<script type="text/javascript">
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 1000;
</script>
<script defer type="text/javascript" src="http://localhost:8081/index.bundle.js" onload="document.querySelector('#loader').remove()"></script>
</head>
<body>
<div id="loader">Waiting for webpack...</div>
</body>
</html>

View File

@@ -0,0 +1,390 @@
const CloudFormation = require('@noodl-utils/cloudformation').default;
const SchemaModel = require('@noodl-models/schemamodel');
const _ = require('lodash');
const { _listenersEnabled } = require('../../src/shared/model');
// Noodl unit tests cloud services
const cs = {
endpoint: 'https://cs2.noodlapp.com/noodl-dev/w6lw4bfbzp',
appId: 'noodl-dev-w6lw4bfbzp',
masterKey: 'yl3QiFnutSUjTR4D'
};
function _makeRequest(path, options) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
var json;
try {
json = JSON.parse(xhr.response);
} catch (e) {}
if (xhr.status === 200 || xhr.status === 201) {
options.success(json);
} else {
options.error(json);
}
}
};
xhr.open(options.method || 'GET', cs.endpoint + path, true);
xhr.setRequestHeader('X-Parse-Application-Id', cs.appId);
xhr.setRequestHeader('X-Parse-MASTER-Key', cs.masterKey);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(options.content));
}
function deleteCollection(options) {
// First delete all objects
_makeRequest('/classes/' + options.collection, {
method: 'GET',
success: (r) => {
const url = new URL(cs.endpoint);
const _batchDelete = {
requests: r.results.map((o) => ({
method: 'DELETE',
path: url.pathname + '/classes/' + options.collection + '/' + o.objectId
}))
};
_makeRequest('/batch', {
method: 'POST',
content: _batchDelete,
success: function (response) {
// Collection empty, delete schema
_makeRequest('/schemas/' + options.collection, {
method: 'DELETE',
success: function (response) {
options.success();
},
error: function (res) {
console.log(res.error);
options.error();
}
});
},
error: (res) => {
console.log(res.error);
options.error();
}
});
},
error: (err) => {
console.log(err);
options.error();
}
});
}
function _pluck(o, fields) {
var _o = {};
fields.forEach((f) => {
_o[f] = o[f];
});
return _o;
}
function verifyObjects(options) {
_makeRequest('/classes/' + options.collection, {
method: 'GET',
success: (r) => {
if (options.data.length !== r.results.length) options.error();
else {
const a = {},
b = {};
options.data.forEach((o) => {
a[o.ref] = _pluck(o, options.fields);
});
r.results.forEach((o) => {
b[o.ref] = _pluck(o, options.fields);
});
if (_.isEqual(a, b)) options.success();
else options.error();
}
},
error: (err) => {
console.log(res.error);
options.error();
}
});
}
function verifySchema(options) {
var sm = new SchemaModel(cs);
sm.getSchema({
collection: options.name,
success: function (schema) {
for (var key in options.fields) {
if (schema.fields[key] === undefined || schema.fields[key].type !== options.fields[key].type) {
return options.result(false);
}
}
options.result(true);
},
error: function () {
options.result(false);
}
});
}
describe('Cloud formation', function () {
beforeEach(function (done) {
deleteCollection({
collection: 'form_test',
success: function () {
done();
},
error: function () {
done();
}
});
});
xit('can handle create collection', function (done) {
const cf = new CloudFormation({
cs: cs
});
const formationJson = {
formation: [
{
type: 'collection',
name: 'form_test',
properties: {
a: {
type: 'Number'
},
b: {
type: 'String'
}
}
}
]
};
cf._form({
template: formationJson,
success: function () {
verifySchema({
name: 'form_test',
fields: {
a: {
type: 'Number'
},
b: {
type: 'String'
}
},
result: function (r) {
expect(r).toBe(true);
done();
}
});
},
error: function () {}
});
});
xit('will add property to existing collection', function (done) {
const cf = new CloudFormation({
cs: cs
});
const formationJson1 = {
formation: [
{
type: 'collection',
name: 'form_test',
properties: {
a: {
type: 'Number'
},
b: {
type: 'String'
}
}
}
]
};
const formationJson2 = {
formation: [
{
type: 'collection',
name: 'form_test',
properties: {
c: {
type: 'Date'
}
}
}
]
};
cf._form({
template: formationJson1,
success: function () {
cf._form({
template: formationJson2,
success: function () {
verifySchema({
name: 'form_test',
fields: {
a: {
type: 'Number'
},
b: {
type: 'String'
},
c: {
type: 'Date'
}
},
result: function (r) {
expect(r).toBe(true);
done();
}
});
},
error: function () {
expect(true).toBe(false); // Fail
done();
}
});
},
error: function () {
expect(true).toBe(false); // Fail
done();
}
});
});
xit('will fail with property clash', function (done) {
const cf = new CloudFormation({
cs: cs
});
const formationJson1 = {
formation: [
{
type: 'collection',
name: 'form_test',
properties: {
a: {
type: 'Number'
},
b: {
type: 'String'
}
}
}
]
};
const formationJson2 = {
formation: [
{
type: 'collection',
name: 'form_test',
properties: {
a: {
type: 'String'
}
}
}
]
};
cf._form({
template: formationJson1,
success: function () {
cf._form({
template: formationJson2,
success: function () {
expect(true).toBe(false); // Fail
done();
},
error: function (err) {
expect(err).toBe('Property already exists with different type a for collection form_test');
done();
}
});
},
error: function () {
expect(true).toBe(false); // Fail
done();
}
});
});
xit('can create sample data', function (done) {
const cf = new CloudFormation({
cs: cs
});
const data = [
{
b: 20,
a: '1',
ref: 'A'
},
{
b: 22,
a: '2',
ref: 'B'
},
{
b: 25,
a: '3',
ref: 'C'
}
];
const formationJson = {
formation: [
{
type: 'collection',
name: 'form_test',
properties: {
a: {
type: 'String'
},
b: {
type: 'Number'
}
},
data: data
}
]
};
cf._form({
template: formationJson,
success: function () {
verifyObjects({
collection: 'form_test',
data: data,
fields: ['a', 'b'],
success: () => {
expect(true).toBe(true);
done();
},
error: () => {
expect(true).toBe(false); // Fail
done();
}
});
},
error: function (err) {
expect(true).toBe(false); // Fail
done();
}
});
});
});

View File

@@ -0,0 +1 @@
export * from './cloudformation';

View File

@@ -0,0 +1,372 @@
const NodeGraphModel = require('@noodl-models/nodegraphmodel').NodeGraphModel;
const NodeGraphNode = require('@noodl-models/nodegraphmodel').NodeGraphNode;
const { ComponentModel } = require('@noodl-models/componentmodel');
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
const { ProjectModel } = require('@noodl-models/projectmodel');
describe('Connecting component inputs and outputs', function () {
var g1, c1;
var ci, co, n1, n2, co2;
var p;
var con1, con2;
beforeEach(() => {
window.NodeLibraryData = require('../nodegraph/nodelibrary');
NodeLibrary.instance.loadLibrary();
g1 = new NodeGraphModel();
c1 = new ComponentModel({
name: 'c1',
graph: g1
});
ci = NodeGraphNode.fromJSON({
type: 'Component Inputs',
id: 'A'
});
g1.addRoot(ci);
co = NodeGraphNode.fromJSON({
type: 'Component Outputs',
id: 'B'
});
g1.addRoot(co);
co2 = NodeGraphNode.fromJSON({
type: 'Component Outputs',
id: 'B2'
});
g1.addRoot(co2);
n1 = NodeGraphNode.fromJSON({
type: 'group',
id: 'C'
});
g1.addRoot(n1);
n2 = NodeGraphNode.fromJSON({
type: 'group',
id: 'D'
});
g1.addRoot(n2);
p = new ProjectModel({
name: 'test'
});
p.addComponent(c1);
NodeLibrary.instance.registerModule(p);
});
afterEach(() => {
NodeLibrary.instance.unregisterModule(p);
});
function addPorts() {
c1.graph.findNodeWithId('B').addPort({
name: 'p1',
plug: 'input',
type: '*',
index: 0
});
c1.graph.findNodeWithId('B').addPort({
name: 'p2',
plug: 'input',
type: '*',
index: 1
});
c1.graph.findNodeWithId('A').addPort({
name: 'p3',
plug: 'output',
type: '*',
index: 2
});
c1.graph.findNodeWithId('B2').addPort({
name: 'p1',
plug: 'input',
type: '*',
index: 0
});
}
function addConnections() {
con1 = {
fromId: n1.id,
fromProperty: 'width',
toId: co.id,
toProperty: 'p1'
};
g1.addConnection(con1);
con2 = {
fromId: ci.id,
fromProperty: 'p3',
toId: n1.id,
toProperty: 'x'
};
g1.addConnection(con2);
}
// ------------------------------------------------------------------------------------------------------
it('can add input ports to the component', function () {
addPorts();
var ports = c1.getPorts();
expect(ports.length).toBe(3);
expect(ports[0].name).toBe('p1');
expect(ports[0].plug).toBe('output');
expect(ports[0].type).toBe('*');
expect(ports[1].name).toBe('p2');
expect(ports[1].plug).toBe('output');
expect(ports[1].type).toBe('*');
expect(ports[2].name).toBe('p3');
expect(ports[2].plug).toBe('input');
expect(ports[2].type).toBe('*');
});
// ------------------------------------------------------------------------------------------------------
it('can list ports correctly for component input/outputs', function () {
addPorts();
var ports = c1.graph.findNodeWithId('B').getPorts('input');
expect(ports[0].name).toBe('p1');
expect(ports[1].name).toBe('p2');
expect(ports[0].plug).toBe('input');
expect(ports[1].plug).toBe('input');
expect(ports[0].type).toBe('*');
expect(ports[1].type).toBe('*');
expect(ports.length).toBe(2);
var ports = c1.graph.findNodeWithId('A').getPorts('output');
expect(ports[0].name).toBe('p3');
expect(ports[0].plug).toBe('output');
expect(ports[0].type).toBe('*');
expect(ports.length).toBe(1);
});
// ------------------------------------------------------------------------------------------------------
it('reports connection status properly', function () {
addPorts();
// component input
var status = g1.getConnectionStatus({
sourceNode: ci,
sourcePort: 'p1',
targetNode: n1,
targetPort: 'width'
});
expect(status.connectable).toBe(true);
// number to number
var status = g1.getConnectionStatus({
sourceNode: n1,
sourcePort: 'screenX',
targetNode: n2,
targetPort: 'width'
});
expect(status.connectable).toBe(true);
// component outputs
var status = g1.getConnectionStatus({
sourceNode: n1,
sourcePort: 'width',
targetNode: co,
targetPort: 'p1'
});
expect(status.connectable).toBe(true);
});
// ------------------------------------------------------------------------------------------------------
it('can rename ports', function () {
addPorts();
addConnections();
expect(c1.graph.findNodeWithId('B').renamePortWithName('p1', 'p2')).toBe(false);
expect(c1.graph.findNodeWithId('B').renamePortWithName('p1', 'p1b')).toBe(true);
expect(c1.graph.findNodeWithId('A').renamePortWithName('p3', 'p3b')).toBe(true);
expect(con2.fromProperty).toBe('p3b');
expect(con1.toProperty).toBe('p1b');
expect(c1.graph.findNodeWithId('B').renamePortWithName('p1b', 'p1')).toBe(true);
expect(c1.graph.findNodeWithId('A').renamePortWithName('p3b', 'p3')).toBe(true);
});
// ------------------------------------------------------------------------------------------------------
it('cannot remove ports with connections', function () {
addPorts();
addConnections();
expect(c1.graph.findNodeWithId('B').removePortWithName('p1')).toBe(false);
expect(c1.graph.findNodeWithId('B').removePortWithName('p2')).toBe(true);
expect(c1.graph.findNodeWithId('A').removePortWithName('p3')).toBe(false);
expect(c1.findPortWithName('p2')).toBe(undefined);
expect(c1.findPortWithName('p1').name).toBe('p1');
});
// ------------------------------------------------------------------------------------------------------
it('can add more connections are report type correctly', function () {
addPorts();
addConnections();
g1.removeConnection(con1);
g1.removeConnection(con2);
// Single type, should just be number
g1.addConnection({
fromId: n1.id,
fromProperty: 'screenX',
toId: co2.id,
toProperty: 'p1'
});
expect(NodeLibrary.nameForPortType(c1.findPortWithName('p1').type)).toBe('number');
// Connect a number and boolean to the components outputs, but on different
// nodes
g1.addConnection({
fromId: n1.id,
fromProperty: 'clipOut',
toId: co.id,
toProperty: 'p1'
});
// Resulting type should be boolean (the only type that can be converted to from both boolean and number)
expect(NodeLibrary.nameForPortType(c1.findPortWithName('p1').type)).toBe('boolean');
// Adding a connection to a star type should not affect the resulting type
g1.addConnection({
fromId: ci.id,
fromProperty: 'p3',
toId: co2.id,
toProperty: 'p1'
});
expect(NodeLibrary.nameForPortType(c1.findPortWithName('p1').type)).toBe('boolean');
// Adding a reference type connection will cause it to break down, no type can be found
g1.addConnection({
fromId: n1.id,
fromProperty: 'this',
toId: co2.id,
toProperty: 'p1'
});
expect(c1.findPortWithName('p1').type).toBe('*');
});
it('can report plug correctly', function () {
addPorts();
expect(c1.findPortWithName('p1').plug).toBe('output');
expect(c1.findPortWithName('p3').plug).toBe('input');
// Adding an extra port of mixed type for p1 should change the plug
c1.graph.findNodeWithId('A').addPort({
name: 'p1',
plug: 'output',
type: '*'
});
expect(c1.getPorts().length).toBe(4);
expect(c1.getPorts()).toContain({
name: 'p1',
type: '*',
default: undefined,
group: undefined,
plug: 'output',
index: 0
});
expect(c1.getPorts()).toContain({
name: 'p1',
type: '*',
default: undefined,
group: undefined,
plug: 'input',
index: 1
});
});
it('can ignore * types', function () {
addPorts();
addConnections();
// Remove all connections
while (g1.connections.length > 0) {
g1.removeConnection(g1.connections[0]);
}
// Number type
g1.addConnection({
fromId: n1.id,
fromProperty: 'screenX',
toId: co.id,
toProperty: 'p1'
});
expect(c1.findPortWithName('p1').type).toBe('number');
// Add a connection to a * type
g1.addConnection({
fromId: ci.id,
fromProperty: 'p3',
toId: co.id,
toProperty: 'p1'
});
// Should just ignore * type and still be number
expect(c1.findPortWithName('p1').type).toBe('number');
});
it('can support units with default in types', function () {
addPorts();
addConnections();
// Remove all connections
while (g1.connections.length > 0) {
g1.removeConnection(g1.connections[0]);
}
// Add a connection to a type with units
g1.addConnection({
fromId: ci.id,
fromProperty: 'p3',
toId: n1.id,
toProperty: 'x'
});
// Should just ignore * type and still be number
expect(c1.findPortWithName('p3').type.name).toBe('number');
expect(c1.findPortWithName('p3').type.units).toEqual(['px', '%']);
expect(c1.findPortWithName('p3').default).toEqual({
value: 10,
unit: '%'
});
expect(c1.findPortWithName('p3').group).toBe('test'); // Should inherit from connected port
//Change parameter value should propagate to default
n1.setParameter('x', {
value: 50,
unit: 'px'
});
expect(c1.findPortWithName('p3').default).toEqual({
value: 50,
unit: 'px'
});
});
it('can rearrange ports', function () {
addPorts();
addConnections();
ci.arrangePort('p3', undefined, 'G1');
expect(c1.findPortWithName('p3').group).toBe('G1');
});
});

View File

@@ -0,0 +1,322 @@
const { ProjectModel } = require('@noodl-models/projectmodel');
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
const NodeGraphModel = require('@noodl-models/nodegraphmodel').NodeGraphModel;
const NodeGraphNode = require('@noodl-models/nodegraphmodel').NodeGraphNode;
describe('Component instances', function () {
var c1, c2;
var p, p2;
var w;
beforeEach(() => {
window.NodeLibraryData = require('../nodegraph/nodelibrary');
NodeLibrary.instance.loadLibrary();
p = ProjectModel.fromJSON(project);
NodeLibrary.instance.registerModule(p);
c1 = p.getComponentWithName('test');
c2 = p.getComponentWithName('Root');
});
afterEach(() => {
NodeLibrary.instance.unregisterModule(p);
});
it('can load project', function () {
expect(p).not.toBe(undefined);
});
it('can rename component inputs and outputs', function () {
expect(c1.graph.findNodeWithId('46a72429-263c-2ad1-3ada-ce8a98b27308').renamePortWithName('p1', 'p1b')).toBe(true);
expect(c1.graph.findNodeWithId('c01d18bc-b8cd-e792-6fd8-89694ef8da48').renamePortWithName('p3', 'p3b')).toBe(true);
expect(c2.graph.connections[0].toProperty).toBe('p1b');
expect(c2.graph.connections[1].fromProperty).toBe('p3b');
expect(c2.graph.findNodeWithId('56b67ac1-224e-c3c8-b058-6fee9c590bee').parameters['p1b']).toBe(50);
expect(c2.graph.findNodeWithId('56b67ac1-224e-c3c8-b058-6fee9c590bee').parameters['p1']).toBe(undefined);
});
xit('can detect unhealthy connections', function () {
c2.graph.evaluateHealth();
expect(
c2.graph.getConnectionHealth({
sourceId: '56b67ac1-224e-c3c8-b058-6fee9c590bee',
sourcePort: 'p3',
targetId: 'c0210ab9-94ab-c4c8-313b-3b394d5361f6',
targetPort: 'opacity'
}).healthy
).toBe(true);
// Remove p3 connection
c1.graph.removeConnection(c1.graph.connections[2]);
c2.graph.evaluateHealth();
expect(
c2.graph.getConnectionHealth({
sourceId: '56b67ac1-224e-c3c8-b058-6fee9c590bee',
sourcePort: 'p3b',
targetId: 'c0210ab9-94ab-c4c8-313b-3b394d5361f6',
targetPort: 'opacity'
}).healthy
).toBe(false);
// Connect p3 to a port of different type
c1.graph.addConnection({
fromId: 'f89b4fcd-5cfe-c47e-7fab-03948aad878c',
fromProperty: 'this',
toId: 'c01d18bc-b8cd-e792-6fd8-89694ef8da48',
toProperty: 'p3b'
});
// Health should still be bad (wrong type)
expect(
c2.graph.getConnectionHealth({
sourceId: '56b67ac1-224e-c3c8-b058-6fee9c590bee',
sourcePort: 'p3b',
targetId: 'c0210ab9-94ab-c4c8-313b-3b394d5361f6',
targetPort: 'opacity'
}).healthy
).toBe(false);
// Remove and restore
c1.graph.removeConnection(c1.graph.connections[2]);
c1.graph.addConnection({
fromId: 'f89b4fcd-5cfe-c47e-7fab-03948aad878c',
fromProperty: 'screenY',
toId: 'c01d18bc-b8cd-e792-6fd8-89694ef8da48',
toProperty: 'p3b'
});
c2.graph.evaluateHealth();
// Health should be back up
expect(
c2.graph.getConnectionHealth({
sourceId: '56b67ac1-224e-c3c8-b058-6fee9c590bee',
sourcePort: 'p3b',
targetId: 'c0210ab9-94ab-c4c8-313b-3b394d5361f6',
targetPort: 'opacity'
}).healthy
).toBe(true);
});
it('can detect unhealthy nodes', function () {
c2.graph.evaluateHealth();
// Multiple roots with same category is OK
var n = c2.graph.findNodeWithId('56b67ac1-224e-c3c8-b058-6fee9c590bee');
expect(n.getHealth().healthy).toBe(true);
c2.graph.removeNode(c2.graph.findNodeWithId('97720c17-1ff1-f4b0-8f97-8d70cf795348'));
c2.graph.removeNode(c2.graph.findNodeWithId('c0210ab9-94ab-c4c8-313b-3b394d5361f6'));
c2.graph.evaluateHealth();
expect(n.getHealth().healthy).toBe(true);
// The component cannot have visual children
var nn = NodeGraphNode.fromJSON({
type: 'group',
id: 'A'
});
n.addChild(nn);
c2.graph.evaluateHealth();
expect(n.getHealth().healthy).toBe(false);
// Adding a component children node will do it
c1.graph.findNodeWithId('f89b4fcd-5cfe-c47e-7fab-03948aad878c').addChild(
NodeGraphNode.fromJSON({
type: 'Component Children',
id: 'CC'
})
);
c2.graph.evaluateHealth();
expect(n.getHealth().healthy).toBe(true);
// Remove the visual root of the test component, health will become false again
c1.graph.removeNode(c1.graph.findNodeWithId('f89b4fcd-5cfe-c47e-7fab-03948aad878c'));
c2.graph.evaluateHealth();
expect(n.getHealth().healthy).toBe(false);
});
xit('component renamed are propageted to component references', function () {
var c3 = p.getComponentWithName('/has_comp_ref');
var c4 = p.getComponentWithName('/to_be_renamed');
var n = c3.graph.findNodeWithId('has_comp_ref_group');
expect(n.parameters['compref']).toBe('/to_be_renamed');
c4.rename('/is_renamed');
expect(n.parameters['compref']).toBe('/is_renamed');
});
var project = {
name: 'proj1',
components: [
{
name: 'outside',
graph: {
roots: [
{
id: 'AABB',
type: 'Component Inputs',
parameters: {},
children: [],
ports: [
{
name: 'p1',
plug: 'output',
type: '='
}
]
}
]
}
},
{
name: 'Root',
ports: [],
graph: {
connections: [
{
fromId: '97720c17-1ff1-f4b0-8f97-8d70cf795348',
fromProperty: 'screenX',
toId: '56b67ac1-224e-c3c8-b058-6fee9c590bee',
toProperty: 'p1'
},
{
fromId: '56b67ac1-224e-c3c8-b058-6fee9c590bee',
fromProperty: 'p3',
toId: 'c0210ab9-94ab-c4c8-313b-3b394d5361f6',
toProperty: 'opacity'
}
],
roots: [
{
id: '97720c17-1ff1-f4b0-8f97-8d70cf795348',
type: 'group',
x: 359,
y: 74,
parameters: {},
children: []
},
{
id: 'c0210ab9-94ab-c4c8-313b-3b394d5361f6',
type: 'group',
x: 457,
y: 276,
parameters: {},
children: []
},
{
id: '56b67ac1-224e-c3c8-b058-6fee9c590bee',
type: 'test',
x: 430,
y: 181,
parameters: {
p1: 50
},
children: []
}
]
}
},
{
name: 'test',
graph: {
connections: [
{
fromId: '46a72429-263c-2ad1-3ada-ce8a98b27308',
fromProperty: 'p2',
toId: 'f89b4fcd-5cfe-c47e-7fab-03948aad878c',
toProperty: 'clip'
},
{
fromId: '46a72429-263c-2ad1-3ada-ce8a98b27308',
fromProperty: 'p1',
toId: 'f89b4fcd-5cfe-c47e-7fab-03948aad878c',
toProperty: 'scaleX'
},
{
fromId: 'f89b4fcd-5cfe-c47e-7fab-03948aad878c',
fromProperty: 'screenX',
toId: 'c01d18bc-b8cd-e792-6fd8-89694ef8da48',
toProperty: 'p3'
}
],
roots: [
{
id: '46a72429-263c-2ad1-3ada-ce8a98b27308',
type: 'Component Inputs',
x: 336,
y: 115,
parameters: {},
children: [],
ports: [
{
name: 'p1',
plug: 'output',
type: '='
},
{
name: 'p2',
plug: 'output',
type: '='
}
]
},
{
id: 'f89b4fcd-5cfe-c47e-7fab-03948aad878c',
type: 'group',
x: 497,
y: 211,
parameters: {},
children: []
},
{
id: 'c01d18bc-b8cd-e792-6fd8-89694ef8da48',
type: 'Component Outputs',
x: 646,
y: 283,
parameters: {},
children: [],
ports: [
{
name: 'p3',
plug: 'input',
type: '='
}
]
}
]
}
},
// Test component references
{
name: '/has_comp_ref',
ports: [],
graph: {
roots: [
{
id: 'has_comp_ref_group',
type: 'group',
parameters: {
compref: '/to_be_renamed'
},
children: []
}
]
}
},
{
name: '/to_be_renamed',
graph: {}
}
]
};
});

View File

@@ -0,0 +1,147 @@
const { ComponentPorts } = require('@noodl-views/panels/componentports');
const { ProjectModel } = require('@noodl-models/projectmodel');
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
const { UndoQueue } = require('@noodl-models/undo-queue-model');
describe('Components ports panel unit tests', function () {
var cp, c;
beforeEach(() => {
var project = {
components: [
{
name: 'Root',
graph: {
roots: [
{
type: 'Component Inputs',
id: 'A'
}
]
}
}
]
};
window.NodeLibraryData = require('../nodegraph/nodelibrary');
NodeLibrary.instance.loadLibrary();
ProjectModel.instance = ProjectModel.fromJSON(project);
// NodeLibrary.instance.registerModule(project);
c = ProjectModel.instance.getComponentWithName('Root');
cp = new ComponentPorts({
model: c.graph.findNodeWithId('A'),
plug: 'input'
});
cp.render();
cp.canArrangeInGroups = true; // Enable arrange in groups
});
afterEach(() => {
ProjectModel.instance = undefined;
});
it('can add ports', function () {
expect(cp.performAdd('test').success).toBe(true);
expect(c.findPortWithName('test')).not.toBe(undefined);
// Cannot create with same name
expect(cp.performAdd('test').success).toBe(false);
// Can add with different name
expect(cp.performAdd('test2').success).toBe(true);
expect(c.findPortWithName('test2')).not.toBe(undefined);
});
it('can undo add ports', function () {
expect(cp.performAdd('undome').success).toBe(true);
expect(c.findPortWithName('undome')).not.toBe(undefined);
UndoQueue.instance.undo();
expect(c.findPortWithName('undome')).toBe(undefined);
});
it('can rename+undo ports', function () {
cp.performAdd('test');
cp.performAdd('test2');
// cannot rename to existing name
expect(
cp.performRename({
oldName: 'test',
newName: 'test2'
}).success
).toBe(false);
expect(
cp.performRename({
oldName: 'test',
newName: 'test3'
}).success
).toBe(true);
expect(c.findPortWithName('test3')).not.toBe(undefined);
expect(c.findPortWithName('test')).toBe(undefined);
// Undo rename
UndoQueue.instance.undo();
expect(c.findPortWithName('test3')).toBe(undefined);
expect(c.findPortWithName('test')).not.toBe(undefined);
});
it('can delete+undo ports', function () {
cp.performAdd('test');
cp.renderPorts(true);
expect(cp.performDelete('test').success).toBe(true);
expect(c.findPortWithName('test')).toBe(undefined);
UndoQueue.instance.undo();
expect(c.findPortWithName('test')).not.toBe(undefined);
});
it('can add group and port', function () {
expect(cp.performAddGroup('G1').success).toBe(true);
expect(cp.performAdd('P1').success).toBe(true);
cp.renderPorts(true);
expect(c.findPortWithName('P1').group).toBe('G1');
});
it('can rename group', function () {
cp.performAddGroup('G1');
cp.performAdd('P1');
cp.renderPorts(true);
expect(
cp.performRenameGroup({
newName: 'G2',
item: cp.findGroupWithName('G1')
}).success
).toBe(true);
expect(c.findPortWithName('P1').group).toBe('G2');
});
it('cannot add group with unqualified name', function () {
cp.performAddGroup('G2');
cp.performAdd('P1');
cp.renderPorts(true);
expect(cp.performAddGroup('G2').success).toBe(false); // Existing group
expect(cp.performAddGroup('').success).toBe(false); // Empty name
});
it('can delete group', function () {
cp.performAddGroup('G2');
cp.performAdd('P1');
cp.renderPorts(true);
expect(cp.performDeleteGroup(cp.findGroupWithName('G2')).success).toBe(true);
expect(c.findPortWithName('P1').group).toBe(undefined);
});
});

View File

@@ -0,0 +1,536 @@
const { ComponentsPanelView } = require('@noodl-views/panels/componentspanel/ComponentsPanel');
const { ProjectModel } = require('@noodl-models/projectmodel');
const { UndoQueue } = require('@noodl-models/undo-queue-model');
const NodeGraphEditor = require('@noodl-views/nodegrapheditor').NodeGraphEditor;
const ViewerConnection = require('../../src/editor/src/ViewerConnection');
describe('Components panel unit tests', function () {
var cp;
var p1;
var project = {
components: [
{
name: 'Root',
graph: {}
},
{
name: '/test/f1/a',
graph: {}
},
{
name: '/test/f2/a',
graph: {}
},
{
name: '/b',
graph: {}
},
{
name: '/test/ff/a',
graph: {}
},
{
name: '/q',
graph: {}
},
{
name: '/a',
graph: {}
},
{
name: '/dup/f1/a',
graph: {}
},
// Undo tests
{
name: '/delete_folder/delete_comp',
graph: {}
},
{
name: '/rename_folder/rename_comp',
graph: {}
},
{
name: '/drop/a',
graph: {}
},
{
name: '/drop2/a',
graph: {}
},
{
name: '/dropundo',
graph: {}
},
{
name: '/nested-target/a',
graph: {}
},
{
name: '/nested-dropme/test/b',
graph: {}
},
{
name: '/delete-me/with-content/a',
graph: {}
},
{
name: '/delete-me/b',
graph: {}
}
]
};
beforeAll(() => {
// Mock node graph editor
NodeGraphEditor.instance = {
getActiveComponent() {
return p1.getComponentWithName('Root');
},
on() {},
off() {},
switchToComponent() {}
};
// Viewerconnection mock
ViewerConnection.instance = {
on() {},
off() {}
};
});
afterAll(() => {
NodeGraphEditor.instance = undefined;
ViewerConnection.instance = undefined;
});
beforeEach(() => {
p1 = ProjectModel.instance = ProjectModel.fromJSON(project);
cp = new ComponentsPanelView({});
cp.setNodeGraphEditor(NodeGraphEditor.instance);
cp.render();
});
afterEach(() => {
cp.dispose();
ProjectModel.instance = undefined;
});
it('can setup view', function () {
expect(cp).not.toBe(undefined);
});
it('can add new folders', function () {
// Existing folder
expect(
cp.performAdd({
type: 'folder',
name: 'test'
}).success
).toBe(false);
// Empty name
expect(
cp.performAdd({
type: 'folder',
name: ''
}).success
).toBe(false);
// Add
expect(
cp.performAdd({
type: 'folder',
name: 'f3'
}).success
).toBe(true);
expect(cp.getFolderWithPath('/f3/')).not.toBe(undefined);
});
it('can add components', function () {
// Existing name
expect(
cp.performAdd({
type: 'component',
name: 'b',
parentPath: '/'
}).success
).toBe(false);
// Empty name
expect(
cp.performAdd({
type: 'component',
name: ''
}).success
).toBe(false);
// Add
expect(
cp.performAdd({
type: 'component',
name: 'c',
parentPath: '/'
}).success
).toBe(true);
expect(p1.getComponentWithName('/c')).not.toBe(undefined);
expect(cp.getFolderWithPath('/').hasComponentWithName('c')).toBe(true);
// Add to sub directory
expect(
cp.performAdd({
type: 'component',
name: 'subsub',
parentPath: '/test/ff/'
}).success
).toBe(true);
expect(p1.getComponentWithName('/test/ff/subsub')).not.toBe(undefined);
expect(cp.getFolderWithPath('/test/ff/').hasComponentWithName('subsub')).toBe(true);
});
it('can rename folders', function () {
// Existing name
expect(
cp.performRename({
type: 'folder',
name: 'f2',
folder: cp.getFolderWithPath('/test/ff/')
}).success
).toBe(false);
// Empty name
expect(
cp.performRename({
type: 'folder',
name: '',
folder: cp.getFolderWithPath('/test/ff/')
}).success
).toBe(false);
// Empty name
expect(
cp.performRename({
type: 'folder',
name: 'f4',
folder: cp.getFolderWithPath('/test/ff/')
}).success
).toBe(true);
expect(p1.getComponentWithName('/test/ff/a')).toBe(undefined);
expect(p1.getComponentWithName('/test/f4/a')).not.toBe(undefined);
expect(cp.getFolderWithPath('/test/ff/')).toBe(undefined);
expect(cp.getFolderWithPath('/test/f4/')).not.toBe(undefined);
});
it('can rename components', function () {
// Existing name
expect(
cp.performRename({
type: 'component',
name: 'b',
folder: cp.getFolderWithPath('/'),
component: p1.getComponentWithName('/q')
}).success
).toBe(false);
// Empty name
expect(
cp.performRename({
type: 'component',
name: '',
folder: cp.getFolderWithPath('/'),
component: p1.getComponentWithName('/q')
}).success
).toBe(false);
// Empty name
expect(
cp.performRename({
type: 'component',
name: 'q2',
folder: cp.getFolderWithPath('/'),
component: p1.getComponentWithName('/q')
}).success
).toBe(true);
expect(p1.getComponentWithName('/q')).toBe(undefined);
expect(p1.getComponentWithName('/q2')).not.toBe(undefined);
});
it('can detect duplicates', function () {
// Cannot move to folder containing a comp with same name
expect(
cp.getAcceptableDropType({
type: 'component',
component: p1.getComponentWithName('/a'),
targetFolder: cp.getFolderWithPath('/test/f1/')
})
).toBe(false);
// Cannot move folder to folder containing a folder with same name
expect(
cp.getAcceptableDropType({
type: 'folder',
folder: cp.getFolderWithPath('/dup/f1/'),
targetFolder: cp.getFolderWithPath('/test/')
})
).toBe(false);
});
it('can make correct drops of folders', function () {
// Can move a folder into a folder
expect(
cp.getAcceptableDropType({
type: 'folder',
folder: cp.getFolderWithPath('/test/f1/'),
targetFolder: cp.getFolderWithPath('/test/f2/')
})
).toBe('folder');
// Make the move
cp.dropOn({
type: 'folder',
folder: cp.getFolderWithPath('/test/f1/'),
targetFolder: cp.getFolderWithPath('/test/f2/')
});
expect(p1.getComponentWithName('/test/f2/f1/a')).not.toBe(undefined);
expect(cp.getFolderWithPath('/test/f2/f1/').name).toBe('f1');
// expect(cp.getFolderWithPath('/test/f1/')).toBe(undefined);
// Moving to an ancestor or same folder should not be acceptable
expect(
cp.getAcceptableDropType({
type: 'folder',
folder: cp.getFolderWithPath('/test/f2/'),
targetFolder: cp.getFolderWithPath('/test/f2/f1/')
})
).toBe(false);
expect(
cp.getAcceptableDropType({
type: 'folder',
folder: cp.getFolderWithPath('/test/f2/'),
targetFolder: cp.getFolderWithPath('/test/f2/')
})
).toBe(false);
});
it('can make correct drops of components', function () {
// Can move into a new folder
expect(
cp.getAcceptableDropType({
type: 'component',
folder: cp.getFolderWithPath('/'),
component: p1.getComponentWithName('/b'),
targetFolder: cp.getFolderWithPath('/test/f2/')
})
).toBe('component');
// Cannot drop to same folder
expect(
cp.getAcceptableDropType({
type: 'component',
folder: cp.getFolderWithPath('/'),
component: p1.getComponentWithName('/b'),
targetFolder: cp.getFolderWithPath('/')
})
).toBe(false);
// Make the drop
cp.dropOn({
type: 'component',
folder: cp.getFolderWithPath('/'),
component: p1.getComponentWithName('/b'),
targetFolder: cp.getFolderWithPath('/test/f2/')
});
expect(p1.getComponentWithName('/test/f2/b')).not.toBe(undefined);
expect(cp.getFolderWithPath('/').hasComponentWithName('b')).toBe(false);
expect(cp.getFolderWithPath('/test/f2/').hasComponentWithName('b')).toBe(true);
expect(p1.getComponentWithName('/b')).toBe(undefined);
});
//TODO: empty folders are removed when moved, but the undo function does not restore them. This is a bug.
xit('can drop empty folders', function () {
cp.performAdd({
type: 'folder',
name: 'empty_folder',
parentFolder: cp.getFolderWithPath('/')
});
expect(cp.getFolderWithPath('/empty_folder/')).not.toBe(undefined);
// Drop empty folder
cp.dropOn({
type: 'folder',
folder: cp.getFolderWithPath('/empty_folder/'),
targetFolder: cp.getFolderWithPath('/test/')
});
expect(cp.getFolderWithPath('/empty_folder/')).toBe(undefined);
//empty folders are removed when moved
expect(cp.getFolderWithPath('/test/empty_folder/')).toBe(undefined);
UndoQueue.instance.undo();
expect(cp.getFolderWithPath('/empty_folder/')).not.toBe(undefined);
// expect(cp.getFolderWithPath('/test/empty_folder/')).toBe(undefined);
});
it('can undo add/delete/rename component and folder', function () {
// Add component
expect(
cp.performAdd({
type: 'component',
name: 'undome',
parentPath: '/'
}).success
).toBe(true);
expect(p1.getComponentWithName('/undome')).not.toBe(undefined);
expect(UndoQueue.instance.undo().label).toBe('add component');
expect(p1.getComponentWithName('/undome')).toBe(undefined);
// Add folder
expect(
cp.performAdd({
type: 'folder',
name: 'undome',
parentPath: '/'
}).success
).toBe(true);
expect(cp.getFolderWithPath('/undome/')).not.toBe(undefined);
expect(UndoQueue.instance.undo().label).toBe('add folder');
expect(cp.getFolderWithPath('/undome/')).toBe(undefined);
// Delete component
expect(
cp.performDelete({
type: 'component',
folder: cp.getFolderWithPath('/delete_folder/'),
component: p1.getComponentWithName('/delete_folder/delete_comp')
}).success
).toBe(true);
expect(p1.getComponentWithName('/delete_folder/delete_comp')).toBe(undefined);
expect(UndoQueue.instance.undo().label).toBe('delete component');
expect(p1.getComponentWithName('/delete_folder/delete_comp')).not.toBe(undefined);
expect(UndoQueue.instance.redo().label).toBe('delete component'); // Folder must be empty for next test to run
// Delete folder
expect(
cp.performDelete({
type: 'folder',
folder: cp.getFolderWithPath('/delete_folder/')
}).success
).toBe(true);
expect(cp.getFolderWithPath('/delete_folder/')).toBe(undefined);
expect(UndoQueue.instance.undo().label).toBe('delete folder');
expect(cp.getFolderWithPath('/delete_folder/')).not.toBe(undefined);
// Rename component
expect(
cp.performRename({
type: 'component',
name: 'newname',
folder: cp.getFolderWithPath('/rename_folder/'),
component: p1.getComponentWithName('/rename_folder/rename_comp')
}).success
).toBe(true);
expect(p1.getComponentWithName('/rename_folder/newname')).not.toBe(undefined);
expect(p1.getComponentWithName('/rename_folder/rename_comp')).toBe(undefined);
expect(UndoQueue.instance.undo().label).toBe('rename component');
expect(p1.getComponentWithName('/rename_folder/newname')).toBe(undefined);
expect(p1.getComponentWithName('/rename_folder/rename_comp')).not.toBe(undefined);
// Rename folder
expect(
cp.performRename({
type: 'folder',
name: 'newname',
folder: cp.getFolderWithPath('/rename_folder/')
}).success
).toBe(true);
expect(p1.getComponentWithName('/newname/rename_comp')).not.toBe(undefined);
expect(p1.getComponentWithName('/rename_folder/rename_comp')).toBe(undefined);
expect(cp.getFolderWithPath('/rename_folder/')).toBe(undefined);
expect(cp.getFolderWithPath('/newname/')).not.toBe(undefined);
expect(UndoQueue.instance.undo().label).toBe('rename folder');
expect(p1.getComponentWithName('/newname/rename_comp')).toBe(undefined);
expect(p1.getComponentWithName('/rename_folder/rename_comp')).not.toBe(undefined);
expect(cp.getFolderWithPath('/rename_folder/')).not.toBe(undefined);
expect(cp.getFolderWithPath('/newname/')).toBe(undefined);
});
it('can undo drop on folder', function () {
// Component on folder
cp.dropOn({
type: 'component',
folder: cp.getFolderWithPath('/'),
component: p1.getComponentWithName('/dropundo'),
targetFolder: cp.getFolderWithPath('/drop/')
});
expect(p1.getComponentWithName('/drop/dropundo')).not.toBe(undefined);
expect(UndoQueue.instance.undo().label).toBe('move component to folder');
// expect(p1.getComponentWithName('/drop/dropundo')).toBe(undefined);
expect(p1.getComponentWithName('/dropundo')).not.toBe(undefined);
expect(cp.getFolderWithPath('/drop/').hasComponentWithName('dropundo')).toBe(false);
// Folder on folder
cp.dropOn({
type: 'folder',
folder: cp.getFolderWithPath('/drop/'),
targetFolder: cp.getFolderWithPath('/drop2/')
});
expect(cp.getFolderWithPath('/drop2/drop/')).not.toBe(undefined);
expect(UndoQueue.instance.undo().label).toBe('move folder to folder');
// expect(cp.getFolderWithPath('/drop2/drop/')).toBe(undefined);
});
it('can make correct drops of nested folders and undo', function () {
cp.dropOn({
type: 'folder',
folder: cp.getFolderWithPath('/nested-dropme/'),
targetFolder: cp.getFolderWithPath('/nested-target/')
});
expect(cp.getFolderWithPath('/nested-target/nested-dropme/')).not.toBe(undefined);
expect(p1.getComponentWithName('/nested-target/nested-dropme/test/b')).not.toBe(undefined);
expect(p1.getComponentWithName('/nested-dropme/test/b')).toBe(undefined);
// expect(cp.getFolderWithPath('/nested-dropme/')).toBe(undefined);
UndoQueue.instance.undo();
// expect(cp.getFolderWithPath('/nested-target/nested-dropme/')).toBe(undefined);
expect(p1.getComponentWithName('/nested-target/nested-dropme/test/b')).toBe(undefined);
expect(p1.getComponentWithName('/nested-dropme/test/b')).not.toBe(undefined);
expect(cp.getFolderWithPath('/nested-dropme/')).not.toBe(undefined);
});
it('can delete folder with content', function () {
// Delete folder
expect(
cp.performDelete({
type: 'folder',
folder: cp.getFolderWithPath('/delete-me/')
}).success
).toBe(true);
expect(cp.getFolderWithPath('/delete-me/')).toBe(undefined);
expect(cp.getFolderWithPath('/delete-me/with-content/')).toBe(undefined);
expect(p1.getComponentWithName('/delete-me/with-content/a')).toBe(undefined);
expect(p1.getComponentWithName('/delete-me/b')).toBe(undefined);
UndoQueue.instance.undo();
expect(cp.getFolderWithPath('/delete-me/')).not.toBe(undefined);
expect(cp.getFolderWithPath('/delete-me/with-content/')).not.toBe(undefined);
expect(p1.getComponentWithName('/delete-me/with-content/a')).not.toBe(undefined);
expect(p1.getComponentWithName('/delete-me/b')).not.toBe(undefined);
});
});

View File

@@ -0,0 +1,48 @@
const { ProjectModel } = require('@noodl-models/projectmodel');
describe('Conditional ports tests', function () {
beforeEach(() => {
ProjectModel.instance = ProjectModel.fromJSON({
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: 'Anim',
id: 'A-1',
parameters: {
type: 'typeA'
}
}
]
}
}
]
});
});
xit('can get correct ports after load', function () {
var ports = ProjectModel.instance.findNodeWithId('A-1').getPorts();
expect(ports.length).toBe(2);
expect(ports[1]).toEqual({
name: 'from',
plug: 'input',
type: 'number',
index: 1
});
});
xit('can react to param changes', function () {
ProjectModel.instance.findNodeWithId('A-1').setParameter('type', 'typeB');
var ports = ProjectModel.instance.findNodeWithId('A-1').getPorts();
expect(ports.length).toBe(2);
expect(ports[1]).toEqual({
name: 'to',
plug: 'input',
type: 'number',
index: 1
});
});
});

View File

@@ -0,0 +1,89 @@
const { ProjectModel } = require('@noodl-models/projectmodel');
describe('Dynamic ports from viewer tests', function () {
// Setup
var projectJSON = {
components: [
{
name: 'Root',
graph: {
connections: [
{
fromId: 'A',
fromProperty: 'dynaA',
toId: 'B',
toProperty: 'x'
},
{
fromId: 'B',
fromProperty: 'y',
toId: 'A',
toProperty: 'dynaB'
}
],
roots: [
{
type: 'rectangle',
id: 'A',
parameters: {
dynaA: 10,
dynaB: 'hej'
}
},
{
type: 'rectangle',
id: 'B'
}
]
}
}
]
};
var project = ProjectModel.fromJSON(projectJSON);
it('can rename inputs', function () {
var n = project.findNodeWithId('A');
n.setDynamicPorts([
{
name: 'dynaA',
type: 'number'
},
{
name: 'dynaB',
type: 'string'
}
]);
// Now renamed
n.setDynamicPorts(
[
{
name: 'bosseA',
type: 'number'
},
{
name: 'bosseB',
type: 'string'
}
],
{
renamed: {
plug: 'input',
patterns: ['{{*}}A', '{{*}}B'],
before: 'dyna',
after: 'bosse'
}
}
);
expect(n.parameters['bosseA']).toBe(10);
expect(n.parameters['bosseB']).toBe('hej');
expect(n.parameters['dynaA']).toBe(undefined);
expect(n.parameters['dynaB']).toBe(undefined);
expect(project.getComponentWithName('Root').graph.connections[0].fromProperty).toBe('bosseA');
expect(project.getComponentWithName('Root').graph.connections[1].toProperty).toBe('bosseB');
});
});

View File

@@ -0,0 +1,82 @@
const { ProjectModel } = require('@noodl-models/projectmodel');
describe('Expanded ports tests', function () {
beforeEach(() => {
ProjectModel.instance = ProjectModel.fromJSON(getProject());
});
xit('can get correct ports after load', function () {
var ports = ProjectModel.instance.findNodeWithId('EP-1').getPorts();
expect(ports.length).toBe(3);
expect(ports[1]).toEqual({
name: 'Affe.A',
index: 101,
plug: 'input',
type: 'number'
});
expect(ports[2]).toEqual({
name: 'Affe.B',
index: 102,
plug: 'input',
type: 'number'
});
});
xit('can react to param changes', function () {
ProjectModel.instance.findNodeWithId('EP-1').setParameter('Affe.A', 'something');
var ports = ProjectModel.instance.findNodeWithId('EP-1').getPorts();
expect(ports.length).toBe(2);
expect(ports[1]).toEqual({
name: 'Affe.A',
index: 101,
plug: 'input',
type: 'number'
});
});
xit('can rename ports and parameters are copied', function () {
ProjectModel.instance.findNodeWithId('EP-2').setParameter('Bosse.A', 'grr');
ProjectModel.instance.findNodeWithId('EP-2').renamePortWithName('Bosse', 'Oscar');
expect(ProjectModel.instance.findNodeWithId('EP-2').parameters['Oscar.A']).toBe('grr');
expect(ProjectModel.instance.findNodeWithId('EP-2').parameters['Bosse.A']).toBe(undefined);
});
function getProject() {
return {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: 'ExpandPorts',
id: 'EP-1',
ports: [
{
type: 'number',
name: 'Affe',
plug: 'input'
}
]
},
{
type: 'ExpandPorts',
id: 'EP-2',
ports: [
{
type: 'number',
name: 'Bosse',
plug: 'input'
}
]
}
]
}
}
]
};
}
});

View File

@@ -0,0 +1,9 @@
export * from './componentconnections';
export * from './componentinstances';
export * from './componentports';
export * from './componentspanel';
export * from './conditionalports';
export * from './dynamicports';
export * from './expandedports';
export * from './numberedports';
export * from './portchannels';

View File

@@ -0,0 +1,100 @@
const NodeGraphModel = require('@noodl-models/nodegraphmodel').NodeGraphModel;
const NodeGraphNode = require('@noodl-models/nodegraphmodel').NodeGraphNode;
describe('Numbered ports tests', function () {
var g1, e1, e2, e3;
beforeEach(() => {
g1 = new NodeGraphModel();
e1 = NodeGraphNode.fromJSON({
id: 'A',
type: 'nodeWithNumberedPorts'
});
g1.addRoot(e1);
});
xit('can add new ports dynamically', function () {
var ports = e1.getPorts();
expect(ports.length).toBe(1);
expect(ports[0].name).toBe('my number 0');
expect(ports[0].displayName).toBe('My number 0');
expect(ports[0].type).toBe('number');
expect(ports[0].group).toBe('My group');
e1.setParameter('my number 0', 10);
var ports = e1.getPorts();
expect(ports.length).toBe(2);
expect(ports[1].name).toBe('my number 1');
expect(ports[1].displayName).toBe('My number 1');
});
xit('can find highest parameter', function () {
// The number of parameters should be defined by the highest parameter that is
// not undefined
e1.setParameter('my number 1', 20);
var ports = e1.getPorts();
expect(ports.length).toBe(3);
// Setting to undefined should still be there because 'my number 1' is 20
e1.setParameter('my number 0', undefined);
var ports = e1.getPorts();
expect(ports.length).toBe(3);
});
xit('can detect connections', function () {
e2 = NodeGraphNode.fromJSON({
id: 'B',
type: 'nodeWithNumberedPorts'
});
g1.addRoot(e2);
g1.addConnection({
fromId: 'A',
fromProperty: 'my number 2',
toId: 'B',
toProperty: 'my number 1'
});
// A should now have 4 ports (connected to my number 2)
var ports = e1.getPorts();
expect(ports.length).toBe(4);
// B should have 3 ports (connected to my number 1)
var ports = e2.getPorts();
expect(ports.length).toBe(3);
});
xit('can generate selectors', function () {
e3 = NodeGraphNode.fromJSON({
id: 'C',
type: 'nodeWithNumberedPortsAndSelectors'
});
g1.addRoot(e3);
var ports = e3.getPorts();
expect(ports.length).toBe(2);
expect(ports[1].name).toBe('startAt');
expect(ports[1].group).toBe('My selectors');
expect(ports[1].type.enums.length).toBe(0);
expect(ports[1].type.name).toBe('enum');
// Add a dynamic port, should be listed in the selector
e3.setParameter('my number 0', 10);
var ports = e3.getPorts();
expect(ports.length).toBe(3);
expect(ports[2].type.enums.length).toBe(1);
expect(ports[2].type.enums[0].value).toBe('my number 0');
expect(ports[2].type.enums[0].label).toBe('My number 0');
// More
e3.setParameter('my number 1', 20);
var ports = e3.getPorts();
expect(ports.length).toBe(4);
expect(ports[3].type.enums.length).toBe(2);
expect(ports[3].type.enums[0].value).toBe('my number 0');
expect(ports[3].type.enums[0].label).toBe('My number 0');
expect(ports[3].type.enums[1].value).toBe('my number 1');
expect(ports[3].type.enums[1].label).toBe('My number 1');
});
});

View File

@@ -0,0 +1,191 @@
const { ProjectModel } = require('@noodl-models/projectmodel');
describe('Port channels tests', function () {
beforeEach(() => {
ProjectModel.instance = ProjectModel.fromJSON(getProject());
});
afterAll(() => {
//unregister the project model so subsequent tests arent affected
//(the channels can affect other tests)
ProjectModel.instance = undefined;
});
xit('can collect channel names', function () {
var ports = ProjectModel.instance.findNodeWithId('ER-1').getPorts();
expect(ports.length).toBe(1);
expect(ports[0].name).toBe('channel');
expect(ports[0].type).toEqual({
name: 'enum',
enums: ['channelA', 'channelB'],
allowEditOnly: true
});
});
xit('can collect ports when channel name is set', function () {
ProjectModel.instance.findNodeWithId('ER-1').setParameter('channel', 'channelA');
var ports = ProjectModel.instance.findNodeWithId('ER-1').getPorts();
expect(ports.length).toBe(4);
expect(ports[1]).toEqual({
name: 'p1',
type: '*',
plug: 'output',
index: 1
});
expect(ports[2]).toEqual({
name: 'p2',
type: '*',
plug: 'output',
index: 2
});
expect(ports[3]).toEqual({
name: 'p3',
type: '*',
plug: 'output',
index: 3
});
});
xit('can react to port name change', function () {
ProjectModel.instance.findNodeWithId('ER-1').setParameter('channel', 'channelA');
ProjectModel.instance.findNodeWithId('ES-1').renamePortWithName('p2', 'p2b');
var ports = ProjectModel.instance.findNodeWithId('ER-1').getPorts();
expect(ports.length).toBe(5);
expect(ports[1]).toEqual({
name: 'p1',
type: '*',
plug: 'output',
index: 1
});
expect(ports[2]).toEqual({
name: 'p2',
type: '*',
plug: 'output',
index: 2
});
expect(ports[3]).toEqual({
name: 'p2b',
type: '*',
plug: 'output',
index: 3
});
expect(ports[4]).toEqual({
name: 'p3',
type: '*',
plug: 'output',
index: 4
});
});
xit('can react to channel changes', function () {
ProjectModel.instance.findNodeWithId('ER-1').setParameter('channel', 'channelA');
ProjectModel.instance.findNodeWithId('ES-1').setParameter('channel', 'channelC');
var ports = ProjectModel.instance.findNodeWithId('ER-1').getPorts();
expect(ports.length).toBe(3);
expect(ports[0].name).toBe('channel');
expect(ports[0].type).toEqual({
name: 'enum',
enums: ['channelA', 'channelB', 'channelC'],
allowEditOnly: true
});
expect(ports[1]).toEqual({
name: 'p2',
type: '*',
plug: 'output',
index: 1
});
expect(ports[2]).toEqual({
name: 'p3',
type: '*',
plug: 'output',
index: 2
});
});
function getProject() {
return {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: 'Event Sender',
id: 'ES-1',
parameters: {
channel: 'channelA'
},
ports: [
{
name: 'p1',
type: '*',
plug: 'input'
},
{
name: 'p2',
type: '*',
plug: 'input'
}
]
},
{
type: 'Event Receiver',
id: 'ER-1'
}
]
}
},
{
name: 'comp2',
graph: {
roots: [
{
type: 'Event Sender',
id: 'ES-2',
parameters: {
channel: 'channelA'
},
ports: [
{
name: 'p2',
type: '*',
plug: 'input'
},
{
name: 'p3',
type: '*',
plug: 'input'
}
]
},
{
type: 'Event Sender',
id: 'ES-3',
parameters: {
channel: 'channelB'
},
ports: [
{
name: 'p3',
type: '*',
plug: 'input'
},
{
name: 'p4',
type: '*',
plug: 'input'
}
]
}
]
}
}
]
};
}
});

View File

@@ -0,0 +1,80 @@
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
// https://codehandbook.org/check-if-an-array-sorted-javascript/
// Check if an array is sorted in order.
function sorted(arr: number[]) {
let second_index: number;
for (let first_index = 0; first_index < arr.length; first_index++) {
second_index = first_index + 1;
if (arr[second_index] - arr[first_index] < 0) return false;
}
return true;
}
describe('Git clone progress tests', function () {
let tempDir: string | undefined;
beforeEach(async function () {
tempDir = app.getPath('temp') + '/noodlunittests-git-' + Utils.guid() + '/';
FileSystem.instance.makeDirectorySync(tempDir);
});
afterEach(function (done) {
FileSystem.instance.removeDirectoryRecursive(tempDir, done);
tempDir = undefined;
});
it('clone from "master" with progress', async function () {
const result = [];
// Clone the project
const git = new Git(mergeProject);
await git.clone({
url: 'https://github.com/github/testrepo.git',
directory: tempDir,
onProgress: (progress) => {
result.push(progress);
}
});
const values = result.map((x) => x.value);
const isValuesSorted = sorted(values);
expect(result.length).toBeGreaterThan(10);
expect(isValuesSorted).toBeTrue();
});
it('clone and fetch with progress', async function () {
const result = [];
// Create empty repo
const git = new Git(mergeProject);
await git.clone({
url: 'https://github.com/github/testrepo.git',
directory: tempDir,
onProgress: (progress) => {
result.push(progress);
}
});
await git.checkoutBranch('master', '6ff1be9c3819c93a2f41e0ddc09f252fcf154f34');
// Fetch from remote
await git.fetch({
onProgress: (progress) => {
result.push(progress);
}
});
const values = result.map((x) => x.value);
const isValuesSorted = sorted(values);
expect(result.length).toBeGreaterThan(10);
expect(isValuesSorted).toBeTrue();
});
});

View File

@@ -0,0 +1,59 @@
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
describe('Git diff tests', function () {
let git: Git;
let tempDir: string;
beforeEach(async function () {
// Logger.log(`[jest-before]: ${expect.getState().currentTestName}`)
tempDir = app.getPath('temp') + '/noodlunittests-git-' + Utils.guid() + '/';
FileSystem.instance.makeDirectorySync(tempDir);
git = new Git(mergeProject);
await git.initNewRepo(tempDir);
// await git.commit("initial commit"); //commit .gitattributes so we have a clean repo
});
afterEach(function (done) {
// Logger.log(`\r\n[jest-after]: ${expect.getState().currentTestName}`)
FileSystem.instance.removeDirectoryRecursive(tempDir, done);
tempDir = undefined;
});
it('can diff files between commits', async function () {
FileSystem.instance.writeFileSync(tempDir + 'initial.txt', 'hello world');
await git.commit('initial commit');
const commit0 = await git.getHeadCommitId();
FileSystem.instance.writeFileSync(tempDir + 'file.txt', 'text');
await git.commit('added file');
const commit1 = await git.getHeadCommitId();
let diff = await git.getFileDiff(commit0, commit1);
expect(diff).toEqual([
{
status: 'new',
path: 'file.txt'
}
]);
FileSystem.instance.writeFileSync(tempDir + 'file.txt', 'text2');
await git.commit('modified file');
const commit2 = await git.getHeadCommitId();
diff = await git.getFileDiff(commit1, commit2);
expect(diff).toEqual([
{
status: 'modified',
path: 'file.txt'
}
]);
});
});

View File

@@ -0,0 +1,160 @@
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
// TODO: Stash untracked files ?
async function readTextFile(path) {
return new Promise<string>((resolve, reject) => {
FileSystem.instance.readTextFile(path, (text) => {
resolve(text);
});
});
}
describe('Git local tests', function () {
let git: Git;
let tempDir: string | undefined;
beforeEach(async function () {
// Logger.log(`[jest-before]: ${expect.getState().currentTestName}`);
tempDir = app.getPath('temp') + '/noodlunittests-git-' + Utils.guid() + '/';
FileSystem.instance.makeDirectorySync(tempDir);
git = new Git(mergeProject);
await git.initNewRepo(tempDir);
// The new version doesnt make a first commit
FileSystem.instance.writeFileSync(tempDir + 'initial.txt', 'Hello World');
await git.commit('initial commit');
});
afterEach(function (done) {
// Logger.log(`\r\n[jest-after]: ${expect.getState().currentTestName}`);
FileSystem.instance.removeDirectoryRecursive(tempDir, done);
tempDir = undefined;
});
it('can handle merge conflicts in files', async function () {
//commit new file on main
const path = tempDir + 'test.txt';
FileSystem.instance.writeFileSync(path, 'original');
await git.commit('added new file');
//create new brach, change the file, and commit it
await git.createAndCheckoutBranch('test-branch');
FileSystem.instance.writeFileSync(path, 'test-branch');
await git.commit('modified file');
//move back to main, and change the file
await git.checkoutBranch('main');
FileSystem.instance.writeFileSync(path, 'main');
//and now merge in the test brach. Should automatically resolve the conflict to "ours"
await git.mergeToCurrentBranch('test-branch');
const file = await readTextFile(path);
expect(file).toBe('main');
// Status should show test.txt as modified
const status = await git.status();
expect(status).toEqual([{ status: 'modified', path: 'test.txt' }]);
});
it('can handle merge without conflicts in project.json', async function () {
//commit project on main
const path = tempDir + 'project.json';
FileSystem.instance.writeFileSync(path, JSON.stringify(simpleProject()));
await git.commit('added original project');
//create new brach, change the project, and commit it
await git.createAndCheckoutBranch('test-branch');
await git.checkoutBranch('test-branch');
const modifiedProjectTestBranch = simpleProject();
modifiedProjectTestBranch.components[0].graph.roots[0].parameters.text = 'test-branch';
FileSystem.instance.writeFileSync(path, JSON.stringify(modifiedProjectTestBranch));
await git.commit('modified project');
//move back to main, and merge in the test branch
await git.checkoutBranch('main');
await git.mergeToCurrentBranch('test-branch');
const proj = JSON.parse(await readTextFile(path));
const conflicts = proj.components[0].graph.roots[0].conflicts;
expect(conflicts).toBe(undefined);
expect(proj.components[0].graph.roots[0].parameters.text).toBe('test-branch');
//status should be empty
expect(await git.status()).toEqual([]);
});
it('can handle merge with conflicts in project.json', async function () {
//commit project on main
const path = tempDir + 'project.json';
FileSystem.instance.writeFileSync(path, JSON.stringify(simpleProject()));
await git.commit('added original project');
//create new brach, change the project, and commit it
await git.createAndCheckoutBranch('test-branch');
await git.checkoutBranch('test-branch');
const modifiedProjectTestBranch = simpleProject();
modifiedProjectTestBranch.components[0].graph.roots[0].parameters.text = 'test-branch';
FileSystem.instance.writeFileSync(path, JSON.stringify(modifiedProjectTestBranch));
await git.commit('modified project');
//move back to main, and change the project again
await git.checkoutBranch('main');
const modifiedProjectMaster = simpleProject();
modifiedProjectMaster.components[0].graph.roots[0].parameters.text = 'main';
FileSystem.instance.writeFileSync(path, JSON.stringify(modifiedProjectMaster));
//and now merge in the test brach. Should automatically result in conflicts in project
await git.mergeToCurrentBranch('test-branch');
const proj = JSON.parse(await readTextFile(path));
const conflicts = proj.components[0].graph.roots[0].conflicts;
expect(conflicts.length).toBe(1);
expect(conflicts[0].name).toBe('text');
expect(conflicts[0].ours).toBe('main');
expect(conflicts[0].theirs).toBe('test-branch');
//check that status is contains a modified project.json
expect(await git.status()).toEqual([{ status: 'modified', path: 'project.json' }]);
});
function simpleProject() {
return {
name: 'proj',
components: [
{
name: '/comp1',
graph: {
roots: [
{
id: 'c8451024-fe91-0cbe-b3ad-85d77dd01432',
type: 'Text',
x: 237,
y: 170,
parameters: {
text: 'original'
}
}
]
}
}
],
rootNodeId: 'c8451024-fe91-0cbe-b3ad-85d77dd01432',
version: '1'
};
}
});

View File

@@ -0,0 +1,44 @@
import Process from 'process';
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
describe('git local misc', () => {
//Read project.json from the filesystem using using node, and then read the same file from the HEAD commit using git. They should be equal.
//This will test the character encoding, and the test project contains a few special characters
it('reads files with the correct encoding', async () => {
const testFilePath = Process.cwd() + '/tests/testfs/git-repo-utf8/';
//create a temp folder with the test project.json file
const tempDir = app.getPath('temp') + '/noodlunittests-git-' + Utils.guid() + '/';
FileSystem.instance.makeDirectorySync(tempDir);
FileSystem.instance.copyRecursiveSync(testFilePath, tempDir);
//create a new git repo and commit the file
const git = new Git(mergeProject);
await git.initNewRepo(tempDir);
await git.commit('test');
//read project.json from the filesystem
const projectFromFS = JSON.parse(FileSystem.instance.readFileSync(tempDir + 'project.json'));
//and read the same file from the HEAD commit
const headCommitId = await git.getHeadCommitId();
const headCommit = await git.getCommitFromId(headCommitId);
expect(headCommit).toBeTruthy();
const projectJson = await headCommit.getFileAsString('project.json');
const project = JSON.parse(projectJson);
expect(project).toBeTruthy();
//and compare
expect(projectFromFS).toEqual(project);
//clean up
await new Promise((resolve) => {
FileSystem.instance.removeDirectoryRecursive(tempDir, resolve);
});
});
});

View File

@@ -0,0 +1,403 @@
import path from 'path';
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
// TODO: Stash untracked files ?
async function readTextFile(path) {
return new Promise<string>((resolve, reject) => {
FileSystem.instance.readTextFile(path, (text) => {
resolve(text);
});
});
}
describe('Git local tests', function () {
let git: Git;
let tempDir: string | undefined;
beforeEach(async function () {
// Logger.log(`[jest-before]: ${expect.getState().currentTestName}`);
tempDir = app.getPath('temp') + '/noodlunittests-git-' + Utils.guid() + '/';
FileSystem.instance.makeDirectorySync(tempDir);
git = new Git(mergeProject);
await git.initNewRepo(tempDir);
// The new version doesnt make a first commit
FileSystem.instance.writeFileSync(tempDir + 'initial.txt', 'Hello World');
await git.commit('initial commit');
});
afterEach(function (done) {
// Logger.log(`\r\n[jest-after]: ${expect.getState().currentTestName}`);
FileSystem.instance.removeDirectoryRecursive(tempDir, done);
tempDir = undefined;
});
it('new repo has correct config and no changes', async function () {
const text = await readTextFile(tempDir + '.git/config');
expect(text.includes('precomposeUnicode = true')).toBe(true);
expect(text.includes('[merge "noodl"]')).toBe(true);
expect(await git.status()).toEqual([]);
});
it('can get head commit', async function () {
const head = await git.getHeadCommitId();
expect(head).toHaveSize(40); // git commit hash length
});
it('can get remote commit', async function () {
//we have no remote, so it should be null
const head = await git.getRemoteHeadCommitId();
expect(head).toBe(null);
});
it('can commit a new file', async function () {
//add a new file
FileSystem.instance.writeFileSync(tempDir + 'test.txt', 'hej');
//status should reflect the new file
let status = await git.status();
expect(status.length).toBe(1);
expect(status[0].path).toBe('test.txt');
expect(status[0].status).toBe('new');
//commit the file
await git.commit('added new file');
//now git status should be empty
status = await git.status();
expect(status.length).toBe(0);
//and read it's content to make sure it wasn't modified
const text = await readTextFile(tempDir + 'test.txt');
expect(text).toBe('hej');
});
it('can reset untracked files', async function () {
//add a new file
const path = tempDir + 'test.txt';
FileSystem.instance.writeFileSync(path, 'hej');
expect(FileSystem.instance.fileExistsSync(path)).toBe(true);
await git.resetToHead();
expect(FileSystem.instance.fileExistsSync(path)).toBe(false);
const status = await git.status();
expect(status.length).toBe(0);
});
it('can reset changed files', async function () {
//add a new file
const path = tempDir + 'test.txt';
//add file and commit it
FileSystem.instance.writeFileSync(path, 'hej');
await git.commit('added new file');
//change the file content
FileSystem.instance.writeFileSync(path, 'hej2');
expect(await readTextFile(path)).toBe('hej2');
await git.resetToHead();
expect(await readTextFile(path)).toBe('hej');
//check that status is empty
const status = await git.status();
expect(status.length).toBe(0);
});
it('can create and checkout a new branch', async function () {
expect(await git.getCurrentBranchName()).toBe('main');
await git.createAndCheckoutBranch('test-branch');
await git.checkoutBranch('test-branch');
expect(await git.getCurrentBranchName()).toBe('test-branch');
//check that status is empty
const status = await git.status();
expect(status.length).toBe(0);
});
it('can list all branches', async function () {
expect(await git.getBranches()).toEqual([{ name: 'main', local: true, remote: false }]);
await git.createAndCheckoutBranch('test-branch-1');
await git.createAndCheckoutBranch('test-branch-2');
const branches = await git.getBranches();
expect(branches[0]).toEqual({ name: 'main', local: true, remote: false });
expect(branches[1]).toEqual({
name: 'test-branch-1',
local: true,
remote: false
});
expect(branches[2]).toEqual({
name: 'test-branch-2',
local: true,
remote: false
});
});
it('can list commits from different branches', async function () {
const path = tempDir + 'test.txt';
await git.createAndCheckoutBranch('test-branch-1');
await git.checkoutBranch('test-branch-1');
//commit new file on test-branch
FileSystem.instance.writeFileSync(path, 'text file');
await git.commit('added new file');
await git.checkoutBranch('main');
let commits = await git.getCommitsCurrentBranch();
expect(commits.length).toBe(1);
expect(commits[0].message).toBe('initial commit');
await git.checkoutBranch('test-branch-1');
commits = await git.getCommitsCurrentBranch();
//latest first
expect(commits.length).toBe(2);
expect(commits[0].message).toBe('added new file');
expect(commits[1].message).toBe('initial commit');
//back to main, shouldn't see the commit on the other branch
await git.checkoutBranch('main');
commits = await git.getCommitsCurrentBranch();
expect(commits.length).toBe(1);
expect(commits[0].message).toBe('initial commit');
});
it('can list commits', async function () {
//do a new commit
FileSystem.instance.writeFileSync(tempDir + 'test.txt', 'text file');
await git.commit('added new file');
const commits = await git.getCommitsCurrentBranch();
expect(commits.length).toBe(2);
//latest first
expect(commits[0].message).toBe('added new file');
expect(commits[0].parentCount).toBe(1);
expect(commits[1].message).toBe('initial commit');
expect(commits[1].parentCount).toBe(0);
});
it('throws nice error message when creating invalid branches', async function () {
await expectAsync(git.createAndCheckoutBranch('main')).toBeRejectedWithError('Branch already exists');
await expectAsync(git.createAndCheckoutBranch(')(*&^%$')).toBeRejectedWithError(
'Branch name contains invalid characters.'
);
});
it('can delete a local branch', async function () {
await git.createBranchFromHead('test');
await git.deleteBranch('test');
expect(await git.getBranches()).toEqual([{ name: 'main', local: true, remote: false }]);
});
it('fails when deleting a non-existing branch', async function () {
await expectAsync(git.deleteBranch('test')).toBeRejectedWithError("Branch doesn't exist");
});
it('remembers local changes per branch using the stash', async function () {
const path = tempDir + 'test.txt';
//commit a file
FileSystem.instance.writeFileSync(path, 'original');
await git.commit('added the file');
//make a local change
FileSystem.instance.writeFileSync(path, 'main');
//create a few branches and make changes on each, without committing
await git.createBranchFromHead('test-1');
await git.checkoutBranch('test-1');
FileSystem.instance.writeFileSync(path, 'test-1');
await git.createBranchFromHead('test-2');
await git.checkoutBranch('test-2');
FileSystem.instance.writeFileSync(path, 'test-2');
//checkout all branches and make sure the local changes are re-applied
await git.checkoutBranch('main');
expect(await readTextFile(path)).toBe('main');
await git.checkoutBranch('test-1');
expect(await readTextFile(path)).toBe('test-1');
await git.checkoutBranch('test-2');
expect(await readTextFile(path)).toBe('test-2');
//and back again, just to make sure :)
await git.checkoutBranch('test-1');
expect(await readTextFile(path)).toBe('test-1');
await git.checkoutBranch('main');
expect(await readTextFile(path)).toBe('main');
});
it('brings the local changes to a newly created branch', async function () {
const path = tempDir + 'test.txt';
//commit a file
FileSystem.instance.writeFileSync(path, 'original');
await git.commit('added the file');
//make a local change
FileSystem.instance.writeFileSync(path, 'change');
//create a new branch, it should get the changes
await git.createAndCheckoutBranch('test-1');
expect(await readTextFile(path)).toBe('change');
//and move it over to yet another new branch
await git.createAndCheckoutBranch('test-2');
expect(await readTextFile(path)).toBe('change');
//checkout all other branches and make sure they don't have any changes
await git.checkoutBranch('main');
FileSystem.instance.writeFileSync(path, 'original');
await git.checkoutBranch('test-1');
FileSystem.instance.writeFileSync(path, 'original');
//and that test-2 still have them
await git.checkoutBranch('test-2');
expect(await readTextFile(path)).toBe('change');
});
it('brings the local changes to a newly created branch - with project.json', async function () {
//commit project on main
const path = tempDir + 'project.json';
const projectJson = simpleProject();
FileSystem.instance.writeFileSync(path, JSON.stringify(projectJson));
await git.commit('added original project');
//modify project without committing
projectJson.components[0].graph.roots[0].parameters.text = 'modified';
FileSystem.instance.writeFileSync(path, JSON.stringify(projectJson));
//create a new brach and do a checkout
await git.createAndCheckoutBranch('test-branch');
//check that the previous uncommitted change is on the new branch
expect(await git.status()).toEqual([{ status: 'modified', path: 'project.json' }]);
const p = JSON.parse(await readTextFile(path));
expect(p).toEqual(projectJson);
//move back to main, and the local changes shouldn't be here anymore
await git.checkoutBranch('main');
expect(await git.status()).toEqual([]);
});
it('can create and checkout new branches when there are no local changes', async function () {
//commit a file
FileSystem.instance.writeFileSync(tempDir + 'my-file.txt', 'original');
await git.commit('added the file');
//create a new brach and do a checkout
await git.createAndCheckoutBranch('test-branch');
expect(await git.status()).toEqual([]);
expect(await git.getCurrentBranchName()).toEqual('test-branch');
const commits = await git.getCommitsCurrentBranch();
expect(commits.length).toEqual(2);
});
it('should leave local changes intact even if a checkout fails', async function () {
const path = tempDir + 'test.txt';
FileSystem.instance.writeFileSync(path, 'local change');
await expectAsync(git.checkoutBranch('non-existing-branch')).toBeRejected();
expect(await readTextFile(path)).toBe('local change');
});
it('should leave local changes intact even if a merge fails', async function () {
const path = tempDir + 'test.txt';
FileSystem.instance.writeFileSync(path, 'local change');
await expectAsync(git.mergeToCurrentBranch('non-existing-branch')).toBeRejected();
expect(await readTextFile(path)).toBe('local change');
});
it('should leave local changes intact even if creating a branch fails', async function () {
const path = tempDir + 'test.txt';
FileSystem.instance.writeFileSync(path, 'local change');
await expectAsync(git.createAndCheckoutBranch('invalid &*^%')).toBeRejected();
expect(await readTextFile(path)).toBe('local change');
});
it("ignores files that shouldn't be added to the repo", async function () {
FileSystem.instance.writeFileSync(tempDir + 'project-tmp.json', 'test');
FileSystem.instance.writeFileSync(tempDir + '.DS_Store', 'test');
expect(await git.status()).toEqual([]);
});
it('ahead and behind', async function () {
FileSystem.instance.writeFileSync(path.join(tempDir, 'a1.txt'), 'Hello World');
await git.commit('A1 commit');
FileSystem.instance.writeFileSync(path.join(tempDir, 'a2.txt'), 'Hello World');
await git.commit('A2 commit');
const a2 = await git.getHeadCommitId();
FileSystem.instance.writeFileSync(path.join(tempDir, 'a3.txt'), 'Hello World');
await git.commit('A3 commit');
const a3 = await git.getHeadCommitId();
const commits = await git.getCommitsCurrentBranch();
expect(commits.length).toBe(4); // 3 + initial
const { ahead, behind } = await git.aheadBehind(a2, a3);
expect(ahead).toEqual(0);
expect(behind).toEqual(1);
});
function simpleProject() {
return {
name: 'proj',
components: [
{
name: '/comp1',
graph: {
roots: [
{
id: 'c8451024-fe91-0cbe-b3ad-85d77dd01432',
type: 'Text',
x: 237,
y: 170,
parameters: {
text: 'original'
}
}
]
}
}
],
rootNodeId: 'c8451024-fe91-0cbe-b3ad-85d77dd01432',
version: '1'
};
}
});

View File

@@ -0,0 +1,105 @@
import path from 'path';
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
describe('Git log tests', function () {
let git: Git;
let tempDir: string | undefined;
beforeEach(async function () {
// Logger.log(`[jest-before]: ${expect.getState().currentTestName}`);
tempDir = app.getPath('temp') + '/noodlunittests-git-' + Utils.guid() + '/';
FileSystem.instance.makeDirectorySync(tempDir);
git = new Git(mergeProject);
await git.initNewRepo(tempDir);
});
afterEach(function (done) {
// Logger.log(`\r\n[jest-after]: ${expect.getState().currentTestName}`);
FileSystem.instance.removeDirectoryRecursive(tempDir, done);
tempDir = undefined;
});
it('make a few commits and check logs', async function () {
FileSystem.instance.writeFileSync(tempDir + 'file1.txt', 'text');
await git.setConfigValue('user.name', 'test');
await git.setConfigValue('user.email', 'test@test.test');
await git.commit('add file1.txt');
const commit0 = await git.getHeadCommitId();
FileSystem.instance.writeFileSync(tempDir + 'file2.txt', 'text');
await git.commit('add file2.txt');
FileSystem.instance.writeFileSync(tempDir + 'file3.txt', 'text');
await git.commit('add file3.txt');
const commit2 = await git.getHeadCommitId();
const commits = await git.getCommitsBetween(commit0, commit2);
expect(commits.length).toBe(2);
expect(commits[0].message).toEqual('add file3.txt');
expect(commits[0].author.name).toEqual('test');
expect(commits[0].author.email).toEqual('test@test.test');
expect(commits[1].message).toEqual('add file2.txt');
});
it('can get the commits included in a merge', async function () {
FileSystem.instance.writeFileSync(tempDir + 'file1.txt', 'text');
await git.commit('add file1.txt');
//create a new brach and do a checkout
await git.createAndCheckoutBranch('test-branch');
FileSystem.instance.writeFileSync(tempDir + 'file2.txt', 'text');
await git.commit('a commit');
const commit1Sha = await git.getHeadCommitId();
expect(commit1Sha).toBeTruthy();
FileSystem.instance.writeFileSync(tempDir + 'file3.txt', 'text');
await git.commit('another commit');
const commit2Sha = await git.getHeadCommitId();
expect(commit1Sha).toBeTruthy();
await git.checkoutBranch('main');
const mainHead = await git.getHeadCommitId();
const testBranchHead = await git.getHeadCommitOnBranch('test-branch');
expect(mainHead).toBeTruthy();
expect(testBranchHead).toBeTruthy();
// TODO: The order matters a lot, this will fail in the real test
const commits = await git.getCommitsBetween(mainHead, testBranchHead);
expect(commits.length).toEqual(2);
expect(commits[0].message).toEqual('another commit');
expect(commits[1].message).toEqual('a commit');
});
it('support .gitignore', async function () {
FileSystem.instance.writeFileSync(path.join(tempDir, 'test.txt'), 'hello');
FileSystem.instance.writeFileSync(path.join(tempDir, '.DS_Store'), 'asdasd');
const status1 = await git.status();
expect(status1.length).toBe(3);
expect(status1).toEqual([
{ status: 'new', path: '.gitattributes' },
{ status: 'new', path: '.gitignore' },
{ status: 'new', path: 'test.txt' }
]);
await git.commit('A added file');
FileSystem.instance.writeFileSync(path.join(tempDir, '.DS_Store'), 'asd');
const status2 = await git.status();
expect(status2.length).toBe(0);
});
});

View File

@@ -0,0 +1,66 @@
import fs from 'fs';
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
async function unzipFileToFolder(zipPath: string, tempDir: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
fs.readFile(zipPath, (err, data) => {
if (err) throw err;
FileSystem.instance.unzipToFolder(tempDir, data, (r) => {
if (r.result === 'success') {
resolve();
} else {
reject(r);
}
});
});
});
}
describe('Git tests - misc', () => {
let tempDir: string;
let remoteDir: string;
beforeEach(async function () {
tempDir = app.getPath('temp') + '/noodlunittests-git-' + Utils.guid() + '/';
remoteDir = app.getPath('temp') + '/noodlunittests-git-' + Utils.guid() + '/';
FileSystem.instance.makeDirectorySync(tempDir);
FileSystem.instance.makeDirectorySync(remoteDir);
});
afterEach(function (done) {
FileSystem.instance.removeDirectoryRecursive(tempDir, () => {
FileSystem.instance.removeDirectoryRecursive(remoteDir, done);
});
});
it('can merge text example with two branches', async function () {
const zipPath = process.cwd() + '/tests/testfs/git-merge-test-1.zip';
//unzip test project
await unzipFileToFolder(zipPath, tempDir);
const remoteGit = new Git(mergeProject);
await remoteGit.initNewRepo(remoteDir, { bare: true });
const git = new Git(mergeProject);
await git.openRepository(tempDir + 'git-merge-test-1');
await git.addRemote(remoteDir);
await git.push();
await git.checkoutBranch('branch-test');
await git.push();
const branchCommitId = await git.getHeadCommitOnBranch('main');
const c = await git.getCommitFromId(branchCommitId);
expect(c).toBeTruthy();
});
});

View File

@@ -0,0 +1,363 @@
import fs from 'fs';
import path from 'path';
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
async function readTextFile(path) {
return new Promise((resolve, reject) => {
FileSystem.instance.readTextFile(path, (text) => {
resolve(text);
});
});
}
describe('Git remote tests', function () {
let localGitA: Git, localGitB: Git;
let remoteGit: Git;
let localDirA: string;
let localDirB: string;
let remoteDir: string;
beforeEach(async function () {
// console.log(`[jest-before]: ${jasmine.currentTest.fullName}`);
remoteDir = path.join(app.getPath('temp'), '/noodlunittests-git-' + Utils.guid());
localDirA = path.join(app.getPath('temp'), '/noodlunittests-git-' + Utils.guid());
localDirB = path.join(app.getPath('temp'), '/noodlunittests-git-' + Utils.guid());
// Logger.log("remoteDir: " + remoteDir);
// Logger.log("localDirA: " + localDirA);
// Logger.log("localDirB: " + localDirB);
FileSystem.instance.makeDirectorySync(localDirA);
FileSystem.instance.makeDirectorySync(localDirB);
FileSystem.instance.makeDirectorySync(remoteDir);
localGitA = new Git(mergeProject);
localGitB = new Git(mergeProject);
remoteGit = new Git(mergeProject);
//init a bare repository as remote
await remoteGit.initNewRepo(remoteDir, { bare: true });
//init a new local repo and push it to A (mimics how a new project is created)
await localGitA.initNewRepo(localDirA);
// The new version doesnt make a first commit
FileSystem.instance.writeFileSync(localDirA + 'initial.txt', 'Hello World');
await localGitA.commit('initial commit');
await localGitA.addRemote(remoteDir);
await localGitA.push();
//and clone the project as B to another directory
await localGitB.clone({
url: remoteDir,
directory: localDirB,
onProgress: undefined
});
});
afterEach(function (done) {
// Logger.log(`\r\n[jest-after]: ${expect.getState().currentTestName}`);
FileSystem.instance.removeDirectoryRecursive(remoteDir, () => {
FileSystem.instance.removeDirectoryRecursive(localDirA, () => {
FileSystem.instance.removeDirectoryRecursive(localDirB, done);
});
});
});
it('can pull and merge remote commits', async function () {
//create new file on localA
FileSystem.instance.writeFileSync(path.join(localDirA, 'test.txt'), 'localA file');
await localGitA.commit('A added file');
await localGitA.push();
//pull it down on localB
await localGitB.pull({});
expect(await readTextFile(path.join(localDirB, 'test.txt'))).toBe('localA file');
//modify it on localA
FileSystem.instance.writeFileSync(path.join(localDirA, 'test.txt'), 'localA mod');
await localGitA.commit('A modified file');
await localGitA.push();
//modify it on localB
FileSystem.instance.writeFileSync(path.join(localDirB, 'test.txt'), 'localB mod');
await localGitB.commit('B modified file');
//pull it down
await localGitB.pull({});
//should have been resolved to latest localB modification
expect(await readTextFile(path.join(localDirB, 'test.txt'))).toBe('localB mod');
const commits = await localGitB.getCommitsCurrentBranch();
expect(commits.length).toEqual(5);
expect(commits[0].message).toEqual('Merge origin/main into main');
expect(commits[1].message).toEqual('B modified file');
expect(commits[2].message).toEqual('A modified file');
expect(commits[3].message).toEqual('A added file');
expect(commits[4].message).toEqual('initial commit');
});
it('can pull and merge remote commits when a file is added both on remote and locally', async function () {
//create new file on localA and push
FileSystem.instance.writeFileSync(path.join(localDirA, 'test.txt'), 'A file in localA');
FileSystem.instance.writeFileSync(path.join(localDirA, '.DS_Store'), 'asdasd');
await localGitA.commit('A added file');
await localGitA.push();
//add it in localB, and then pull
FileSystem.instance.writeFileSync(path.join(localDirB, 'test.txt'), 'A file in localB');
FileSystem.instance.writeFileSync(path.join(localDirB, '.DS_Store'), '453456');
await localGitB.pull({});
expect(await readTextFile(path.join(localDirB, 'test.txt'))).toBe('A file in localB');
// Check all the commits
const commits = await localGitB.getCommitsCurrentBranch();
expect(commits.length).toBe(2);
expect(commits[0].message).toBe('A added file');
expect(commits[1].message).toBe('initial commit');
const status = await localGitB.status();
expect(status.length).toBe(1);
expect(status[0]).toEqual({ status: 'modified', path: 'test.txt' });
});
it('can pull and merge remote commits when a commits and a file is added both on remote and locally', async function () {
await localGitB.pull({});
FileSystem.instance.writeFileSync(path.join(localDirB, 'b.txt'), 'asdasd');
await localGitB.commit('B commit');
await localGitB.push();
//create new file on localA and push
await localGitA.pull({});
FileSystem.instance.writeFileSync(path.join(localDirA, 'test.txt'), 'A file in localA');
FileSystem.instance.writeFileSync(path.join(localDirA, '.DS_Store'), 'asdasd');
await localGitA.commit('A added file');
await localGitA.push();
//add it in localB, and then pull
FileSystem.instance.writeFileSync(path.join(localDirB, 'test.txt'), 'A file in localB');
FileSystem.instance.writeFileSync(path.join(localDirB, '.DS_Store'), '453456');
await localGitB.pull({});
expect(await readTextFile(path.join(localDirB, 'test.txt'))).toBe('A file in localB');
// Check all the commits
const commits = await localGitB.getCommitsCurrentBranch();
expect(commits.length).toBe(3);
expect(commits[0].message).toBe('A added file');
expect(commits[1].message).toBe('B commit');
expect(commits[2].message).toBe('initial commit');
const status = await localGitB.status();
expect(status.length).toBe(1);
expect(status[0]).toEqual({ status: 'modified', path: 'test.txt' });
});
it('can reset to merge base when remote is not ahead', async function () {
await localGitA.createAndCheckoutBranch('A');
FileSystem.instance.writeFileSync(path.join(localDirA, 'test.txt'), 'file');
await localGitA.commit('A commit');
await localGitA.push();
await localGitB.fetch({});
await localGitB.checkoutRemoteBranch('A');
//B should now have the file
expect(await readTextFile(path.join(localDirB, 'test.txt'))).toBe('file');
//do a local uncommitted change
FileSystem.instance.writeFileSync(path.join(localDirB, 'test.txt'), 'modified');
//reset, and verify that it's back to original file
await localGitB.resetToMergeBase();
expect(await readTextFile(path.join(localDirB, 'test.txt'))).toBe('file');
let commits = await localGitB.getCommitsCurrentBranch();
expect(commits.length).toBe(2);
//do a local committed change
FileSystem.instance.writeFileSync(path.join(localDirB, 'test.txt'), 'modified');
await localGitB.commit('modified file');
commits = await localGitB.getCommitsCurrentBranch();
expect(commits.length).toBe(3);
//reset, and verify that it's back to original file, and commit is removed
await localGitB.resetToMergeBase();
expect(await readTextFile(path.join(localDirB, 'test.txt'))).toBe('file');
commits = await localGitB.getCommitsCurrentBranch();
expect(commits.length).toBe(2);
});
it('can reset to merge base when remote is ahead', async function () {
await localGitA.createAndCheckoutBranch('A');
FileSystem.instance.writeFileSync(path.join(localDirA, 'test.txt'), 'file');
await localGitA.commit('A commit');
await localGitA.push();
await localGitB.fetch({});
await localGitB.checkoutRemoteBranch('A');
//do a local committed change
FileSystem.instance.writeFileSync(path.join(localDirB, 'test.txt'), 'modified');
await localGitB.commit('modified file');
// TODO: Make sure the history is correct?
//... plus a local modification
FileSystem.instance.writeFileSync(path.join(localDirB, 'test.txt'), 'modified2');
//localA pushed new commit to remote
FileSystem.instance.writeFileSync(path.join(localDirA, 'test.txt'), 'file2');
await localGitA.commit('Another commit');
await localGitA.push();
//localB fetches, but doesn't merge in (doesn't pull)
await localGitB.fetch({});
//B resets, verify that it's back to original file, and commit is removed, and remote didn't get merged in
await localGitB.resetToMergeBase();
expect(await readTextFile(path.join(localDirB, 'test.txt'))).toBe('file');
const commits = await localGitB.getCommitsCurrentBranch();
expect(commits.length).toBe(3);
expect(commits[0].isRemoteAhead).toBe(true);
// Lets just make sure the commits are in the right order
expect(commits[0].message).toBe('Another commit');
expect(commits[1].message).toBe('A commit');
expect(commits[2].message).toBe('initial commit');
});
it('can handle merge with conflicts in project.json', async function () {
const localDirA_projectPath = path.join(localDirA, 'project.json');
const localDirB_projectPath = path.join(localDirB, 'project.json');
// Git A
// Write a simple project file to localDirA
fs.writeFileSync(localDirA_projectPath, JSON.stringify(simpleProject()));
// We now have 1 file, project.json
const statusA1 = await localGitA.status();
expect(statusA1.length).toEqual(1);
// Commit and add remote
await localGitA.commit('add project.json');
// Git B
// Do all the changes on Git B without remote
await localGitB.clone({ url: remoteDir, directory: localDirB });
// Write a simple project file with changes to localDirB
const modifiedProjectTestBranch = simpleProject();
modifiedProjectTestBranch.components[0].graph.roots[0].parameters.text = 'changed';
fs.writeFileSync(localDirB_projectPath, JSON.stringify(modifiedProjectTestBranch));
// We now have 1 file, project.json
const statusB1 = await localGitB.status();
expect(statusB1.length).toEqual(1);
// Commit and push to remote
await localGitB.commit('commit');
// Merge
await localGitA.push();
await localGitB.pull({});
await localGitB.push();
const proj = JSON.parse(await fs.promises.readFile(localDirB_projectPath, 'utf8'));
const conflicts = proj.components[0].graph.roots[0].conflicts;
expect(conflicts.length).toBe(1);
expect(conflicts[0].name).toBe('text');
expect(conflicts[0].ours).toBe('changed');
expect(conflicts[0].theirs).toBe('original');
});
it('Merge with conflicts everywhere, even in stash', async function () {
const localDirA_projectPath = path.join(localDirA, 'project.json');
const localDirB_projectPath = path.join(localDirB, 'project.json');
// Git A
// Write a simple project file to localDirA
fs.writeFileSync(localDirA_projectPath, JSON.stringify(simpleProject()));
// We now have 1 file, project.json
const statusA1 = await localGitA.status();
expect(statusA1.length).toEqual(1);
// Commit and add remote
await localGitA.commit('add project.json');
// Git B
// Do all the changes on Git B without remote
await localGitB.clone({ url: remoteDir, directory: localDirB });
// Write a simple project file with changes to localDirB
const modifiedProjectTestBranch = simpleProject();
modifiedProjectTestBranch.components[0].graph.roots[0].parameters.text = 'changed';
fs.writeFileSync(localDirB_projectPath, JSON.stringify(modifiedProjectTestBranch));
// We now have 1 file, project.json
const statusB1 = await localGitB.status();
expect(statusB1.length).toEqual(1);
// Commit and push to remote
await localGitB.commit('commit');
// Write a simple project file with changes to localDirB
const modifiedProjectTestBranch2 = simpleProject();
modifiedProjectTestBranch2.components[0].graph.roots[0].parameters.text = 'stashed';
fs.writeFileSync(localDirB_projectPath, JSON.stringify(modifiedProjectTestBranch2));
// Merge
await localGitA.push();
await localGitB.pull({});
await localGitB.push();
const proj = JSON.parse(await fs.promises.readFile(localDirB_projectPath, 'utf8'));
const entry = proj.components[0].graph.roots[0];
expect(entry.parameters.text).toBe('stashed');
});
});
function simpleProject() {
return {
name: 'proj',
components: [
{
name: '/comp1',
graph: {
roots: [
{
id: 'c8451024-fe91-0cbe-b3ad-85d77dd01432',
type: 'Text',
x: 237,
y: 170,
parameters: {
text: 'original'
}
}
]
}
}
],
rootNodeId: 'c8451024-fe91-0cbe-b3ad-85d77dd01432',
version: '1'
};
}

View File

@@ -0,0 +1,122 @@
import path from 'path';
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
// jest.setTimeout(10_000);
async function readTextFile(path) {
return new Promise((resolve, reject) => {
FileSystem.instance.readTextFile(path, (text) => {
resolve(text);
});
});
}
describe('Git remote squash tests', function () {
let localGitA: Git, localGitB: Git;
let remoteGit: Git;
let localDirA: string;
let localDirB: string;
let remoteDir: string;
beforeEach(async function () {
// console.log(`[jest-before]: ${jasmine.currentTest.fullName}`);
remoteDir = path.join(app.getPath('temp'), '/noodlunittests-git-' + Utils.guid());
localDirA = path.join(app.getPath('temp'), '/noodlunittests-git-' + Utils.guid());
localDirB = path.join(app.getPath('temp'), '/noodlunittests-git-' + Utils.guid());
// Logger.log("remoteDir: " + remoteDir);
// Logger.log("localDirA: " + localDirA);
// Logger.log("localDirB: " + localDirB);
FileSystem.instance.makeDirectorySync(localDirA);
FileSystem.instance.makeDirectorySync(localDirB);
FileSystem.instance.makeDirectorySync(remoteDir);
localGitA = new Git(mergeProject);
localGitB = new Git(mergeProject);
remoteGit = new Git(mergeProject);
//init a bare repository as remote
await remoteGit.initNewRepo(remoteDir, { bare: true });
//init a new local repo and push it to A (mimics how a new project is created)
await localGitA.initNewRepo(localDirA);
// The new version doesnt make a first commit
FileSystem.instance.writeFileSync(localDirA + 'initial.txt', 'Hello World');
await localGitA.commit('initial commit');
await localGitA.addRemote(remoteDir);
await localGitA.push();
//and clone the project as B to another directory
await localGitB.clone({
url: remoteDir,
directory: localDirB,
onProgress: undefined
});
});
afterEach(function (done) {
// Logger.log(`\r\n[jest-after]: ${expect.getState().currentTestName}`);
FileSystem.instance.removeDirectoryRecursive(remoteDir, () => {
FileSystem.instance.removeDirectoryRecursive(localDirA, () => {
FileSystem.instance.removeDirectoryRecursive(localDirB, done);
});
});
});
it('Push when there are remote changes (squash merge)', async function () {
await localGitB.createAndCheckoutBranch('squash-test');
FileSystem.instance.writeFileSync(path.join(localDirB, 'a.txt'), 'Hello World');
await localGitB.commit('commit 1');
FileSystem.instance.writeFileSync(path.join(localDirB, 'b.txt'), 'Hello World');
await localGitB.commit('commit 2');
FileSystem.instance.writeFileSync(path.join(localDirB, 'c.txt'), 'Hello World');
await localGitB.commit('commit 3');
await localGitB.push();
await localGitA.pull({});
await localGitA.mergeToCurrentBranch('origin/squash-test');
const commits = await localGitA.getCommitsCurrentBranch();
expect(commits[0].message).toBe("Squashed commit from branch 'origin/squash-test'");
expect(commits[1].message).toBe('initial commit');
});
it('Push when there are remote changes (merge)', async function () {
await localGitB.createAndCheckoutBranch('squash-test');
FileSystem.instance.writeFileSync(path.join(localDirB, 'a.txt'), 'Hello World');
await localGitB.commit('commit 1');
FileSystem.instance.writeFileSync(path.join(localDirB, 'b.txt'), 'Hello World');
await localGitB.commit('commit 2');
FileSystem.instance.writeFileSync(path.join(localDirB, 'c.txt'), 'Hello World');
await localGitB.commit('commit 3');
await localGitB.push();
await localGitA.pull({});
await localGitA.mergeToCurrentBranch('origin/squash-test', false);
const commits = await localGitA.getCommitsCurrentBranch();
expect(commits[0].message).toBe('commit 3');
expect(commits[1].message).toBe('commit 2');
expect(commits[2].message).toBe('commit 1');
expect(commits[3].message).toBe('initial commit');
});
});

View File

@@ -0,0 +1,325 @@
import path from 'path';
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
// jest.setTimeout(10_000);
async function readTextFile(path) {
return new Promise((resolve, reject) => {
FileSystem.instance.readTextFile(path, (text) => {
resolve(text);
});
});
}
describe('Git remote tests', function () {
let localGitA: Git, localGitB: Git;
let remoteGit: Git;
let localDirA: string;
let localDirB: string;
let remoteDir: string;
beforeEach(async function () {
// console.log(`[jest-before]: ${jasmine.currentTest.fullName}`);
remoteDir = path.join(app.getPath('temp'), '/noodlunittests-git-' + Utils.guid());
localDirA = path.join(app.getPath('temp'), '/noodlunittests-git-' + Utils.guid());
localDirB = path.join(app.getPath('temp'), '/noodlunittests-git-' + Utils.guid());
// Logger.log("remoteDir: " + remoteDir);
// Logger.log("localDirA: " + localDirA);
// Logger.log("localDirB: " + localDirB);
FileSystem.instance.makeDirectorySync(localDirA);
FileSystem.instance.makeDirectorySync(localDirB);
FileSystem.instance.makeDirectorySync(remoteDir);
localGitA = new Git(mergeProject);
localGitB = new Git(mergeProject);
remoteGit = new Git(mergeProject);
//init a bare repository as remote
await remoteGit.initNewRepo(remoteDir, { bare: true });
//init a new local repo and push it to A (mimics how a new project is created)
await localGitA.initNewRepo(localDirA);
// The new version doesnt make a first commit
FileSystem.instance.writeFileSync(localDirA + 'initial.txt', 'Hello World');
await localGitA.commit('initial commit');
await localGitA.addRemote(remoteDir);
await localGitA.push();
//and clone the project as B to another directory
await localGitB.clone({
url: remoteDir,
directory: localDirB,
onProgress: undefined
});
});
afterEach(function (done) {
// Logger.log(`\r\n[jest-after]: ${expect.getState().currentTestName}`);
FileSystem.instance.removeDirectoryRecursive(remoteDir, () => {
FileSystem.instance.removeDirectoryRecursive(localDirA, () => {
FileSystem.instance.removeDirectoryRecursive(localDirB, done);
});
});
});
it('can get remote head', async function () {
FileSystem.instance.writeFileSync(path.join(localDirA, 'a.txt'), 'Hello World');
await localGitA.commit('local commit');
const remoteHeadId1 = await localGitA.getRemoteHeadCommitId();
const localHeadId1 = await localGitA.getHeadCommitId();
expect(remoteHeadId1).not.toEqual(localHeadId1);
await localGitA.push();
const remoteHeadId2 = await localGitA.getRemoteHeadCommitId();
const localHeadId2 = await localGitA.getHeadCommitId();
expect(remoteHeadId2).toEqual(localHeadId2);
});
it('can push and fetch remote commits', async function () {
FileSystem.instance.writeFileSync(path.join(localDirA, 'a.txt'), 'Hello World');
await localGitA.commit('localA commit');
await localGitA.push();
await localGitB.fetch({});
const commits = await localGitB.getCommitsCurrentBranch();
expect(commits[0].isLocalAhead).toBe(false);
expect(commits[0].isRemoteAhead).toBe(true);
expect(commits[0].message).toEqual('localA commit');
expect(commits[0].isLocalAhead).toBe(false);
expect(commits[1].isRemoteAhead).toBe(false);
expect(commits[1].message).toEqual('initial commit');
expect(commits.length).toEqual(2);
});
it('can list local and remote branches', async function () {
await localGitA.createAndCheckoutBranch('A');
await localGitB.createAndCheckoutBranch('B');
await localGitA.push();
await localGitB.fetch({});
const branches = await localGitB.getBranches();
//alphabetical order
expect(branches[0]).toEqual({ name: 'A', remote: true, local: false });
expect(branches[1]).toEqual({ name: 'B', remote: false, local: true });
expect(branches[2]).toEqual({ name: 'main', remote: true, local: true });
expect(branches.length).toBe(3);
});
it('can checkout remote branch', async function () {
await localGitA.createAndCheckoutBranch('A');
FileSystem.instance.writeFileSync(path.join(localDirA, 'test.txt'), 'remote file');
await localGitA.commit('A commit');
await localGitA.push();
await localGitB.fetch({});
await localGitB.checkoutRemoteBranch('A');
expect(await readTextFile(path.join(localDirB, 'test.txt'))).toBe('remote file');
const commits = await localGitB.getCommitsCurrentBranch();
expect(commits[0].message).toEqual('A commit');
expect(commits.length).toEqual(2);
});
it('can list remote branch correctly when they have slashes in the name', async function () {
await localGitA.createAndCheckoutBranch('test/A');
FileSystem.instance.writeFileSync(path.join(localDirA, 'test.txt'), 'remote file');
await localGitA.commit('A commit');
await localGitA.push();
await localGitB.fetch({});
const branches = await localGitB.getBranches();
expect(branches[1]).toEqual({ name: 'test/A', remote: true, local: false });
});
it('can delete remote branch', async function () {
//A creates a new branch
await localGitA.createAndCheckoutBranch('A');
await localGitA.push();
//B fetches the new branch
await localGitB.fetch({});
let branches = await localGitB.getBranches();
expect(branches.length).toEqual(2);
//B deletes the branch
await localGitB.deleteRemoteBranch('A');
//.. and B now doesn't have it anymore
branches = await localGitB.getBranches();
expect(branches.length).toEqual(1);
//A should think it still exists on the remote
branches = await localGitA.getBranches();
expect(branches.find((b) => b.name === 'A').remote).toEqual(true);
//but after a fetch it should be local only
await localGitA.fetch({});
branches = await localGitA.getBranches();
const branchA = branches.find((b) => b.name === 'A');
expect(branchA.remote).toEqual(false);
expect(branchA.local).toEqual(true);
});
it('can delete local branch but leave remote intact', async function () {
await localGitA.createAndCheckoutBranch('A');
await localGitA.push();
await localGitB.fetch({});
await localGitB.checkoutRemoteBranch('A');
let branches = await localGitB.getBranches();
expect(branches.length).toEqual(2);
expect(branches[0]).toEqual({ name: 'A', local: true, remote: true });
await localGitB.checkoutBranch('main');
await localGitB.deleteBranch('A');
branches = await localGitB.getBranches();
expect(branches.length).toEqual(2);
expect(branches[0]).toEqual({ name: 'A', local: false, remote: true });
});
it('correctly identifies local vs remote commits - one ahead', async function () {
await localGitA.createAndCheckoutBranch('A');
FileSystem.instance.writeFileSync(path.join(localDirA, 'a.txt'), 'Hello World');
await localGitA.commit('A commit');
expect(await localGitA.push()).toBe(true);
FileSystem.instance.writeFileSync(path.join(localDirA, 'a1.txt'), 'Hello World');
await localGitA.commit('A1 commit');
const commits = await localGitA.getCommitsCurrentBranch();
expect(commits.length).toBe(3);
expect(commits[0].isLocalAhead).toBe(true);
expect(commits[1].isLocalAhead).toBeFalsy();
});
it('correctly identifies local vs remote commits - branch not pushed', async function () {
await localGitA.createAndCheckoutBranch('A');
FileSystem.instance.writeFileSync(path.join(localDirA, 'a.txt'), 'Hello World');
await localGitA.commit('A commit');
FileSystem.instance.writeFileSync(path.join(localDirA, 'a1.txt'), 'Hello World');
await localGitA.commit('A1 commit');
const commits = await localGitA.getCommitsCurrentBranch();
expect(commits.length).toBe(3);
expect(commits[0].isLocalAhead).toBe(true);
expect(commits[1].isLocalAhead).toBe(true);
});
it('Push when there are remote changes', async function () {
FileSystem.instance.writeFileSync(path.join(localDirA, 'a.txt'), 'Hello World');
await localGitA.commit('A commit');
await localGitA.push({});
FileSystem.instance.writeFileSync(path.join(localDirB, 'a.txt'), 'Hello World2');
await localGitB.commit('A commit');
try {
await localGitB.push({});
expect(true).toBe(false);
} catch (error) {
expect(error.toString()).toContain(
'Updates were rejected because there are new changes that you do not have locally.'
);
}
});
it('getCommitsBetween returns the correct commits (squash)', async function () {
FileSystem.instance.writeFileSync(path.join(localDirA, 'a.txt'), 'Hello World');
await localGitA.commit('A commit');
await localGitA.push();
await localGitB.pull({});
await localGitB.createAndCheckoutBranch('A');
FileSystem.instance.writeFileSync(path.join(localDirB, 'b.txt'), 'Hello World');
await localGitB.commit('B commit');
await localGitB.push();
const headCommitId = await localGitA.getHeadCommitId();
const branchCommitId = await localGitB.getHeadCommitId();
const allCommits = await localGitA.getCommitsCurrentBranch();
expect(allCommits.length).toBe(2);
expect(allCommits[0].message).toBe('A commit');
expect(allCommits[1].message).toBe('initial commit');
const commits = await localGitB.getCommitsBetween(branchCommitId, headCommitId);
expect(commits.length).toBe(1);
expect(commits[0].message).toBe('B commit');
await localGitA.fetch({});
await localGitA.mergeToCurrentBranch('origin/A');
const allCommits2 = await localGitA.getCommitsCurrentBranch();
expect(allCommits2.length).toBe(3);
expect(allCommits2[0].message).toBe("Squashed commit from branch 'origin/A'");
expect(allCommits2[1].message).toBe('A commit');
expect(allCommits2[2].message).toBe('initial commit');
});
it('getCommitsBetween returns the correct commits', async function () {
FileSystem.instance.writeFileSync(path.join(localDirA, 'a.txt'), 'Hello World');
await localGitA.commit('A commit');
await localGitA.push();
await localGitB.pull({});
await localGitB.createAndCheckoutBranch('A');
FileSystem.instance.writeFileSync(path.join(localDirB, 'b.txt'), 'Hello World');
await localGitB.commit('B commit');
await localGitB.push();
const headCommitId = await localGitA.getHeadCommitId();
const branchCommitId = await localGitB.getHeadCommitId();
const allCommits = await localGitA.getCommitsCurrentBranch();
expect(allCommits.length).toBe(2);
expect(allCommits[0].message).toBe('A commit');
expect(allCommits[1].message).toBe('initial commit');
const commits = await localGitB.getCommitsBetween(branchCommitId, headCommitId);
expect(commits.length).toBe(1);
expect(commits[0].message).toBe('B commit');
await localGitA.fetch({});
await localGitA.mergeToCurrentBranch('origin/A', false);
const allCommits2 = await localGitA.getCommitsCurrentBranch();
expect(allCommits2.length).toBe(3);
expect(allCommits2[0].message).toBe('B commit');
expect(allCommits2[1].message).toBe('A commit');
expect(allCommits2[2].message).toBe('initial commit');
});
});

View File

@@ -0,0 +1,107 @@
import fs from 'fs';
import path from 'path';
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
describe('Git stash tests', function () {
let localGitA: Git, localGitB: Git;
let remoteGit: Git;
let localDirA: string;
let localDirB: string;
let remoteDir: string;
beforeEach(async function () {
// Logger.log(`[jest-before]: ${expect.getState().currentTestName}`)
remoteDir = path.join(app.getPath('temp'), '/noodlunittests-git-' + Utils.guid());
localDirA = path.join(app.getPath('temp'), '/noodlunittests-git-' + Utils.guid());
localDirB = path.join(app.getPath('temp'), '/noodlunittests-git-' + Utils.guid());
// Logger.log("remoteDir: " + remoteDir);
// Logger.log("localDirA: " + localDirA);
// Logger.log("localDirB: " + localDirB);
FileSystem.instance.makeDirectorySync(localDirA);
FileSystem.instance.makeDirectorySync(localDirB);
FileSystem.instance.makeDirectorySync(remoteDir);
localGitA = new Git(mergeProject);
localGitB = new Git(mergeProject);
remoteGit = new Git(mergeProject);
//init a bare repository as remote
await remoteGit.initNewRepo(remoteDir, { bare: true });
//init a new local repo and push it to A (mimics how a new project is created)
await localGitA.initNewRepo(localDirA);
});
afterEach(function (done) {
// Logger.log(`\r\n[jest-after]: ${expect.getState().currentTestName}`)
FileSystem.instance.removeDirectoryRecursive(remoteDir, () => {
FileSystem.instance.removeDirectoryRecursive(localDirA, () => {
FileSystem.instance.removeDirectoryRecursive(localDirB, done);
});
});
});
/**
* Issue: https://app.asana.com/0/1202061493156140/1202351418844106/f
* >
* > Michael Cartner:
* > i mitt fall så var det två användare som öppnade samma 2.5 projekt.
* > Båda skapade då en .gitignore lokalt
* > Användare A commitade
* > Användare B pullade => felmeddelandet ovan
* >
* >
* > Related?: https://stackoverflow.com/questions/51275777/why-does-git-stash-pop-say-that-it-could-not-restore-untracked-files-from-stash
*/
it('pop-stash with merge issues', async function () {
// Git A
// 1. delete .gitignore
const statusA1 = await localGitA.status();
expect(statusA1.length).toEqual(2); // .gitignore and .gitattributes
fs.writeFileSync(localDirA + '/temp', 'temp');
fs.unlinkSync(localDirA + '/.gitignore');
await localGitA.commit('initial commit without .gitignore');
await localGitA.addRemote(remoteDir);
await localGitA.push();
// Git B
await localGitB.clone({ url: remoteDir, directory: localDirB });
await localGitB.fetch({});
const statusB1 = await localGitB.status();
expect(statusB1.length).toEqual(1);
await localGitB.commit('commit .gitignore');
await localGitB.push();
// Git A
// Recreate the repo (creating .gitignore)
localGitA = new Git(mergeProject);
await localGitA.openRepository(localDirA);
// Check that we have it
const statusA2 = await localGitA.status();
expect(statusA2.length).toEqual(1);
/**
* GitError: .gitignore already exists, no checkout
* error: could not restore untracked files from stash
*/
await localGitA.pull({});
const statusA3 = await localGitA.status();
expect(statusA3.length).toEqual(0);
});
});

View File

@@ -0,0 +1,51 @@
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
describe('Git stash tests', function () {
let git: Git;
let tempDir: string | undefined;
beforeEach(async function () {
// Logger.log(`[jest-before]: ${expect.getState().currentTestName}`)
tempDir = app.getPath('temp') + '/noodlunittests-git-' + Utils.guid() + '/';
FileSystem.instance.makeDirectorySync(tempDir);
git = new Git(mergeProject);
await git.initNewRepo(tempDir);
});
afterEach(function (done) {
// Logger.log(`\r\n[jest-after]: ${expect.getState().currentTestName}`)
FileSystem.instance.removeDirectoryRecursive(tempDir, done);
tempDir = undefined;
});
it('stash changes', async function () {
// cant stash when there are no commits
FileSystem.instance.writeFileSync(tempDir + 'file.txt', 'text');
await git.commit('initial commit');
FileSystem.instance.writeFileSync(tempDir + 'file1.txt', 'text');
expect(await git.stashPushChanges()).toBeTruthy();
const status1 = await git.status();
expect(status1).toEqual([]);
FileSystem.instance.writeFileSync(tempDir + 'file2.txt', 'text');
expect(await git.stashPopChanges()).toBe(true);
// NOTE: Got some issue on OSX where pop was called using spawn process.
// OSX didn't seem to like this and ignored the call, changed it to exec.
// Calling pop here again makes sure that the previous pop worked.
expect(await git.stashPopChanges()).toBe(false);
const status2 = await git.status();
expect(status2.length).toEqual(2);
});
});

View File

@@ -0,0 +1,76 @@
import { app } from '@electron/remote';
import { Git } from '@noodl/git';
import FileSystem from '@noodl-utils/filesystem';
import { mergeProject } from '@noodl-utils/projectmerger';
import Utils from '@noodl-utils/utils';
describe('Git status tests', function () {
let git: Git;
let tempDir: string | undefined;
beforeEach(async function () {
tempDir = app.getPath('temp') + '/noodlunittests-git-' + Utils.guid() + '/';
FileSystem.instance.makeDirectorySync(tempDir);
git = new Git(mergeProject);
await git.initNewRepo(tempDir);
});
afterEach(function (done) {
FileSystem.instance.removeDirectoryRecursive(tempDir, done);
tempDir = undefined;
});
it('create file', async function () {
FileSystem.instance.writeFileSync(tempDir + 'file.txt', 'text');
const status = await git.status();
expect(status).toEqual([
{
status: 'new',
path: '.gitattributes'
},
{
status: 'new',
path: '.gitignore'
},
{
status: 'new',
path: 'file.txt'
}
]);
});
it('create file, commit and update file', async function () {
FileSystem.instance.writeFileSync(tempDir + 'file.txt', 'text');
const status1 = await git.status();
expect(status1).toEqual([
{
status: 'new',
path: '.gitattributes'
},
{
status: 'new',
path: '.gitignore'
},
{
status: 'new',
path: 'file.txt'
}
]);
await git.commit('add file.txt');
FileSystem.instance.writeFileSync(tempDir + 'file.txt', 'text2');
const status2 = await git.status();
expect(status2).toEqual([
{
status: 'modified',
path: 'file.txt'
}
]);
});
});

View File

@@ -0,0 +1,13 @@
export * from './git-diff.spec';
export * from './git-local.spec';
export * from './git-local-merge.spec';
export * from './git-real-project-tests.spec';
export * from './git-log.spec';
export * from './git-clone-and-fetch-remote.spec';
export * from './git-remote-merge.spec';
export * from './git-remote-squash.spec';
export * from './git-remote.spec';
export * from './git-stash-merge.spec';
export * from './git-stash.spec';
export * from './git-status.spec';
export * from './git-local-misc.spec';

View File

@@ -0,0 +1,13 @@
// Setup the platform before anything else is loading
// This is a problem since we are calling the platform when importing
import '@noodl/platform-electron';
export * from './cloud';
export * from './components';
export * from './git';
export * from './nodegraph';
export * from './platform';
export * from './project';
export * from './projectmerger';
export * from './projectpatcher';
export * from './utils';

View File

@@ -0,0 +1,152 @@
/**
Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.
The location of `boot.js` can be specified and/or overridden in `jasmine.yml`.
[jasmine-gem]: http://github.com/pivotal/jasmine-gem
*/
(function () {
var jasmineRequire = window.jasmineRequire || require('./jasmine.js');
/**
* ## Require &amp; Instantiate
*
* Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
*/
var jasmine = jasmineRequire.core(jasmineRequire),
global = jasmine.getGlobal();
global.jasmine = jasmine;
/**
* Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
*/
jasmineRequire.html(jasmine);
/**
* Create the Jasmine environment. This is used to run all specs in a project.
*/
var env = jasmine.getEnv();
/**
* ## The Global Interface
*
* Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
*/
var jasmineInterface = jasmineRequire.interface(jasmine, env);
/**
* Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
*/
extend(global, jasmineInterface);
/**
* ## Runner Parameters
*
* More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
*/
var queryString = new jasmine.QueryString({
getWindowLocation: function () {
return window.location;
}
});
var filterSpecs = !!queryString.getParam('spec');
var config = {
failFast: queryString.getParam('failFast'),
oneFailurePerSpec: queryString.getParam('oneFailurePerSpec'),
hideDisabled: queryString.getParam('hideDisabled')
};
var random = queryString.getParam('random');
if (random !== undefined && random !== '') {
config.random = random;
}
var seed = queryString.getParam('seed');
if (seed) {
config.seed = seed;
}
/**
* ## Reporters
* The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
*/
var htmlReporter = new jasmine.HtmlReporter({
env: env,
navigateWithNewParam: function (key, value) {
return queryString.navigateWithNewParam(key, value);
},
addToExistingQueryString: function (key, value) {
return queryString.fullStringWithNewParam(key, value);
},
getContainer: function () {
return document.body;
},
createElement: function () {
return document.createElement.apply(document, arguments);
},
createTextNode: function () {
return document.createTextNode.apply(document, arguments);
},
timer: new jasmine.Timer(),
filterSpecs: filterSpecs
});
/**
* The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
*/
env.addReporter(jasmineInterface.jsApiReporter);
env.addReporter(htmlReporter);
/**
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
*/
var specFilter = new jasmine.HtmlSpecFilter({
filterString: function () {
return queryString.getParam('spec');
}
});
config.specFilter = function (spec) {
return specFilter.matches(spec.getFullName());
};
env.configure(config);
/**
* Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack.
*/
window.setTimeout = window.setTimeout;
window.setInterval = window.setInterval;
window.clearTimeout = window.clearTimeout;
window.clearInterval = window.clearInterval;
/**
* ## Execution
*
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
*/
var currentWindowOnload = window.onload;
window.onload = function () {
if (currentWindowOnload) {
currentWindowOnload();
}
htmlReporter.initialize();
env.execute();
};
/**
* Helper function for readability above.
*/
function extend(destination, source) {
for (var property in source) destination[property] = source[property];
return destination;
}
})();

View File

@@ -0,0 +1,719 @@
/*
Copyright (c) 2008-2021 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var jasmineRequire = window.jasmineRequire || require('./jasmine.js');
jasmineRequire.html = function (j$) {
j$.ResultsNode = jasmineRequire.ResultsNode();
j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
j$.QueryString = jasmineRequire.QueryString();
j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
};
jasmineRequire.HtmlReporter = function (j$) {
function ResultsStateBuilder() {
this.topResults = new j$.ResultsNode({}, '', null);
this.currentParent = this.topResults;
this.specsExecuted = 0;
this.failureCount = 0;
this.pendingSpecCount = 0;
}
ResultsStateBuilder.prototype.suiteStarted = function (result) {
this.currentParent.addChild(result, 'suite');
this.currentParent = this.currentParent.last();
};
ResultsStateBuilder.prototype.suiteDone = function (result) {
this.currentParent.updateResult(result);
if (this.currentParent !== this.topResults) {
this.currentParent = this.currentParent.parent;
}
if (result.status === 'failed') {
this.failureCount++;
}
};
ResultsStateBuilder.prototype.specStarted = function (result) {};
ResultsStateBuilder.prototype.specDone = function (result) {
this.currentParent.addChild(result, 'spec');
if (result.status !== 'excluded') {
this.specsExecuted++;
}
if (result.status === 'failed') {
this.failureCount++;
}
if (result.status == 'pending') {
this.pendingSpecCount++;
}
};
function HtmlReporter(options) {
var config = function () {
return (options.env && options.env.configuration()) || {};
},
getContainer = options.getContainer,
createElement = options.createElement,
createTextNode = options.createTextNode,
navigateWithNewParam = options.navigateWithNewParam || function () {},
addToExistingQueryString = options.addToExistingQueryString || defaultQueryString,
filterSpecs = options.filterSpecs,
htmlReporterMain,
symbols,
deprecationWarnings = [];
this.initialize = function () {
clearPrior();
htmlReporterMain = createDom(
'div',
{ className: 'jasmine_html-reporter' },
createDom(
'div',
{ className: 'jasmine-banner' },
createDom('a', {
className: 'jasmine-title',
href: 'http://jasmine.github.io/',
target: '_blank'
}),
createDom('span', { className: 'jasmine-version' }, j$.version)
),
createDom('ul', { className: 'jasmine-symbol-summary' }),
createDom('div', { className: 'jasmine-alert' }),
createDom('div', { className: 'jasmine-results' }, createDom('div', { className: 'jasmine-failures' }))
);
getContainer().appendChild(htmlReporterMain);
};
var totalSpecsDefined;
this.jasmineStarted = function (options) {
totalSpecsDefined = options.totalSpecsDefined || 0;
};
var summary = createDom('div', { className: 'jasmine-summary' });
var stateBuilder = new ResultsStateBuilder();
this.suiteStarted = function (result) {
stateBuilder.suiteStarted(result);
};
this.suiteDone = function (result) {
stateBuilder.suiteDone(result);
if (result.status === 'failed') {
failures.push(failureDom(result));
}
addDeprecationWarnings(result, 'suite');
};
this.specStarted = function (result) {
stateBuilder.specStarted(result);
};
var failures = [];
this.specDone = function (result) {
stateBuilder.specDone(result);
if (noExpectations(result)) {
var noSpecMsg = "Spec '" + result.fullName + "' has no expectations.";
if (result.status === 'failed') {
console.error(noSpecMsg);
} else {
console.warn(noSpecMsg);
}
}
if (!symbols) {
symbols = find('.jasmine-symbol-summary');
}
symbols.appendChild(
createDom('li', {
className: this.displaySpecInCorrectFormat(result),
id: 'spec_' + result.id,
title: result.fullName
})
);
if (result.status === 'failed') {
failures.push(failureDom(result));
}
addDeprecationWarnings(result, 'spec');
};
this.displaySpecInCorrectFormat = function (result) {
return noExpectations(result) && result.status === 'passed' ? 'jasmine-empty' : this.resultStatus(result.status);
};
this.resultStatus = function (status) {
if (status === 'excluded') {
return config().hideDisabled ? 'jasmine-excluded-no-display' : 'jasmine-excluded';
}
return 'jasmine-' + status;
};
this.jasmineDone = function (doneResult) {
var banner = find('.jasmine-banner');
var alert = find('.jasmine-alert');
var order = doneResult && doneResult.order;
var i;
alert.appendChild(
createDom('span', { className: 'jasmine-duration' }, 'finished in ' + doneResult.totalTime / 1000 + 's')
);
banner.appendChild(optionsMenu(config()));
if (stateBuilder.specsExecuted < totalSpecsDefined) {
var skippedMessage = 'Ran ' + stateBuilder.specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';
var skippedLink = addToExistingQueryString('spec', '');
alert.appendChild(
createDom(
'span',
{ className: 'jasmine-bar jasmine-skipped' },
createDom('a', { href: skippedLink, title: 'Run all specs' }, skippedMessage)
)
);
}
var statusBarMessage = '';
var statusBarClassName = 'jasmine-overall-result jasmine-bar ';
var globalFailures = (doneResult && doneResult.failedExpectations) || [];
var failed = stateBuilder.failureCount + globalFailures.length > 0;
if (totalSpecsDefined > 0 || failed) {
statusBarMessage +=
pluralize('spec', stateBuilder.specsExecuted) + ', ' + pluralize('failure', stateBuilder.failureCount);
if (stateBuilder.pendingSpecCount) {
statusBarMessage += ', ' + pluralize('pending spec', stateBuilder.pendingSpecCount);
}
}
if (doneResult.overallStatus === 'passed') {
statusBarClassName += ' jasmine-passed ';
} else if (doneResult.overallStatus === 'incomplete') {
statusBarClassName += ' jasmine-incomplete ';
statusBarMessage = 'Incomplete: ' + doneResult.incompleteReason + ', ' + statusBarMessage;
} else {
statusBarClassName += ' jasmine-failed ';
}
var seedBar;
if (order && order.random) {
seedBar = createDom(
'span',
{ className: 'jasmine-seed-bar' },
', randomized with seed ',
createDom(
'a',
{
title: 'randomized with seed ' + order.seed,
href: seedHref(order.seed)
},
order.seed
)
);
}
alert.appendChild(createDom('span', { className: statusBarClassName }, statusBarMessage, seedBar));
var errorBarClassName = 'jasmine-bar jasmine-errored';
var afterAllMessagePrefix = 'AfterAll ';
for (i = 0; i < globalFailures.length; i++) {
alert.appendChild(createDom('span', { className: errorBarClassName }, globalFailureMessage(globalFailures[i])));
}
function globalFailureMessage(failure) {
if (failure.globalErrorType === 'load') {
var prefix = 'Error during loading: ' + failure.message;
if (failure.filename) {
return prefix + ' in ' + failure.filename + ' line ' + failure.lineno;
} else {
return prefix;
}
} else {
return afterAllMessagePrefix + failure.message;
}
}
addDeprecationWarnings(doneResult);
for (i = 0; i < deprecationWarnings.length; i++) {
var context;
switch (deprecationWarnings[i].runnableType) {
case 'spec':
context = '(in spec: ' + deprecationWarnings[i].runnableName + ')';
break;
case 'suite':
context = '(in suite: ' + deprecationWarnings[i].runnableName + ')';
break;
default:
context = '';
}
alert.appendChild(
createDom(
'span',
{ className: 'jasmine-bar jasmine-warning' },
'DEPRECATION: ' + deprecationWarnings[i].message,
createDom('br'),
context
)
);
}
var results = find('.jasmine-results');
results.appendChild(summary);
summaryList(stateBuilder.topResults, summary);
if (failures.length) {
alert.appendChild(
createDom(
'span',
{ className: 'jasmine-menu jasmine-bar jasmine-spec-list' },
createDom('span', {}, 'Spec List | '),
createDom('a', { className: 'jasmine-failures-menu', href: '#' }, 'Failures')
)
);
alert.appendChild(
createDom(
'span',
{ className: 'jasmine-menu jasmine-bar jasmine-failure-list' },
createDom('a', { className: 'jasmine-spec-list-menu', href: '#' }, 'Spec List'),
createDom('span', {}, ' | Failures ')
)
);
find('.jasmine-failures-menu').onclick = function () {
setMenuModeTo('jasmine-failure-list');
return false;
};
find('.jasmine-spec-list-menu').onclick = function () {
setMenuModeTo('jasmine-spec-list');
return false;
};
setMenuModeTo('jasmine-failure-list');
var failureNode = find('.jasmine-failures');
for (i = 0; i < failures.length; i++) {
failureNode.appendChild(failures[i]);
}
}
};
return this;
function failureDom(result) {
var failure = createDom(
'div',
{ className: 'jasmine-spec-detail jasmine-failed' },
failureDescription(result, stateBuilder.currentParent),
createDom('div', { className: 'jasmine-messages' })
);
var messages = failure.childNodes[1];
for (var i = 0; i < result.failedExpectations.length; i++) {
var expectation = result.failedExpectations[i];
messages.appendChild(createDom('div', { className: 'jasmine-result-message' }, expectation.message));
messages.appendChild(createDom('div', { className: 'jasmine-stack-trace' }, expectation.stack));
}
if (result.failedExpectations.length === 0) {
messages.appendChild(createDom('div', { className: 'jasmine-result-message' }, 'Spec has no expectations'));
}
return failure;
}
function summaryList(resultsTree, domParent) {
var specListNode;
for (var i = 0; i < resultsTree.children.length; i++) {
var resultNode = resultsTree.children[i];
if (filterSpecs && !hasActiveSpec(resultNode)) {
continue;
}
if (resultNode.type === 'suite') {
var suiteListNode = createDom(
'ul',
{ className: 'jasmine-suite', id: 'suite-' + resultNode.result.id },
createDom(
'li',
{
className: 'jasmine-suite-detail jasmine-' + resultNode.result.status
},
createDom('a', { href: specHref(resultNode.result) }, resultNode.result.description)
)
);
summaryList(resultNode, suiteListNode);
domParent.appendChild(suiteListNode);
}
if (resultNode.type === 'spec') {
if (domParent.getAttribute('class') !== 'jasmine-specs') {
specListNode = createDom('ul', { className: 'jasmine-specs' });
domParent.appendChild(specListNode);
}
var specDescription = resultNode.result.description;
if (noExpectations(resultNode.result)) {
specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
}
if (resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') {
specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
}
specListNode.appendChild(
createDom(
'li',
{
className: 'jasmine-' + resultNode.result.status,
id: 'spec-' + resultNode.result.id
},
createDom('a', { href: specHref(resultNode.result) }, specDescription)
)
);
}
}
}
function optionsMenu(config) {
var optionsMenuDom = createDom(
'div',
{ className: 'jasmine-run-options' },
createDom('span', { className: 'jasmine-trigger' }, 'Options'),
createDom(
'div',
{ className: 'jasmine-payload' },
createDom(
'div',
{ className: 'jasmine-stop-on-failure' },
createDom('input', {
className: 'jasmine-fail-fast',
id: 'jasmine-fail-fast',
type: 'checkbox'
}),
createDom(
'label',
{ className: 'jasmine-label', for: 'jasmine-fail-fast' },
'stop execution on spec failure'
)
),
createDom(
'div',
{ className: 'jasmine-throw-failures' },
createDom('input', {
className: 'jasmine-throw',
id: 'jasmine-throw-failures',
type: 'checkbox'
}),
createDom(
'label',
{ className: 'jasmine-label', for: 'jasmine-throw-failures' },
'stop spec on expectation failure'
)
),
createDom(
'div',
{ className: 'jasmine-random-order' },
createDom('input', {
className: 'jasmine-random',
id: 'jasmine-random-order',
type: 'checkbox'
}),
createDom('label', { className: 'jasmine-label', for: 'jasmine-random-order' }, 'run tests in random order')
),
createDom(
'div',
{ className: 'jasmine-hide-disabled' },
createDom('input', {
className: 'jasmine-disabled',
id: 'jasmine-hide-disabled',
type: 'checkbox'
}),
createDom('label', { className: 'jasmine-label', for: 'jasmine-hide-disabled' }, 'hide disabled tests')
)
)
);
var failFastCheckbox = optionsMenuDom.querySelector('#jasmine-fail-fast');
failFastCheckbox.checked = config.failFast;
failFastCheckbox.onclick = function () {
navigateWithNewParam('failFast', !config.failFast);
};
var throwCheckbox = optionsMenuDom.querySelector('#jasmine-throw-failures');
throwCheckbox.checked = config.oneFailurePerSpec;
throwCheckbox.onclick = function () {
navigateWithNewParam('throwFailures', !config.oneFailurePerSpec);
};
var randomCheckbox = optionsMenuDom.querySelector('#jasmine-random-order');
randomCheckbox.checked = config.random;
randomCheckbox.onclick = function () {
navigateWithNewParam('random', !config.random);
};
var hideDisabled = optionsMenuDom.querySelector('#jasmine-hide-disabled');
hideDisabled.checked = config.hideDisabled;
hideDisabled.onclick = function () {
navigateWithNewParam('hideDisabled', !config.hideDisabled);
};
var optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'),
optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'),
isOpen = /\bjasmine-open\b/;
optionsTrigger.onclick = function () {
if (isOpen.test(optionsPayload.className)) {
optionsPayload.className = optionsPayload.className.replace(isOpen, '');
} else {
optionsPayload.className += ' jasmine-open';
}
};
return optionsMenuDom;
}
function failureDescription(result, suite) {
var wrapper = createDom(
'div',
{ className: 'jasmine-description' },
createDom('a', { title: result.description, href: specHref(result) }, result.description)
);
var suiteLink;
while (suite && suite.parent) {
wrapper.insertBefore(createTextNode(' > '), wrapper.firstChild);
suiteLink = createDom('a', { href: suiteHref(suite) }, suite.result.description);
wrapper.insertBefore(suiteLink, wrapper.firstChild);
suite = suite.parent;
}
return wrapper;
}
function suiteHref(suite) {
var els = [];
while (suite && suite.parent) {
els.unshift(suite.result.description);
suite = suite.parent;
}
return addToExistingQueryString('spec', els.join(' '));
}
function addDeprecationWarnings(result, runnableType) {
if (result && result.deprecationWarnings) {
for (var i = 0; i < result.deprecationWarnings.length; i++) {
var warning = result.deprecationWarnings[i].message;
if (!j$.util.arrayContains(warning)) {
deprecationWarnings.push({
message: warning,
runnableName: result.fullName,
runnableType: runnableType
});
}
}
}
}
function find(selector) {
return getContainer().querySelector('.jasmine_html-reporter ' + selector);
}
function clearPrior() {
// return the reporter
var oldReporter = find('');
if (oldReporter) {
getContainer().removeChild(oldReporter);
}
}
function createDom(type, attrs, childrenVarArgs) {
var el = createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(createTextNode(child));
} else {
if (child) {
el.appendChild(child);
}
}
}
for (var attr in attrs) {
if (attr == 'className') {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
}
function pluralize(singular, count) {
var word = count == 1 ? singular : singular + 's';
return '' + count + ' ' + word;
}
function specHref(result) {
return addToExistingQueryString('spec', result.fullName);
}
function seedHref(seed) {
return addToExistingQueryString('seed', seed);
}
function defaultQueryString(key, value) {
return '?' + key + '=' + value;
}
function setMenuModeTo(mode) {
htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode);
}
function noExpectations(result) {
var allExpectations = result.failedExpectations.length + result.passedExpectations.length;
return allExpectations === 0 && (result.status === 'passed' || result.status === 'failed');
}
function hasActiveSpec(resultNode) {
if (resultNode.type == 'spec' && resultNode.result.status != 'excluded') {
return true;
}
if (resultNode.type == 'suite') {
for (var i = 0, j = resultNode.children.length; i < j; i++) {
if (hasActiveSpec(resultNode.children[i])) {
return true;
}
}
}
}
}
return HtmlReporter;
};
jasmineRequire.HtmlSpecFilter = function () {
function HtmlSpecFilter(options) {
var filterString =
options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
var filterPattern = new RegExp(filterString);
this.matches = function (specName) {
return filterPattern.test(specName);
};
}
return HtmlSpecFilter;
};
jasmineRequire.ResultsNode = function () {
function ResultsNode(result, type, parent) {
this.result = result;
this.type = type;
this.parent = parent;
this.children = [];
this.addChild = function (result, type) {
this.children.push(new ResultsNode(result, type, this));
};
this.last = function () {
return this.children[this.children.length - 1];
};
this.updateResult = function (result) {
this.result = result;
};
}
return ResultsNode;
};
jasmineRequire.QueryString = function () {
function QueryString(options) {
this.navigateWithNewParam = function (key, value) {
options.getWindowLocation().search = this.fullStringWithNewParam(key, value);
};
this.fullStringWithNewParam = function (key, value) {
var paramMap = queryStringToParamMap();
paramMap[key] = value;
return toQueryString(paramMap);
};
this.getParam = function (key) {
return queryStringToParamMap()[key];
};
return this;
function toQueryString(paramMap) {
var qStrPairs = [];
for (var prop in paramMap) {
qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop]));
}
return '?' + qStrPairs.join('&');
}
function queryStringToParamMap() {
var paramStr = options.getWindowLocation().search.substring(1),
params = [],
paramMap = {};
if (paramStr.length > 0) {
params = paramStr.split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
var value = decodeURIComponent(p[1]);
if (value === 'true' || value === 'false') {
value = JSON.parse(value);
}
paramMap[decodeURIComponent(p[0])] = value;
}
}
return paramMap;
}
}
return QueryString;
};

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,178 @@
const { ProjectModel } = require('@noodl-models/projectmodel');
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
const WarningsModel = require('@noodl-models/warningsmodel').WarningsModel;
describe('Conflict warnings', function () {
var p;
beforeEach(() => {
window.NodeLibraryData = require('../nodegraph/nodelibrary');
NodeLibrary.instance.loadLibrary();
p = ProjectModel.fromJSON(getProject());
ProjectModel.instance = p;
});
it('can load project', function () {
expect(p).not.toBe(undefined);
});
it('can count total number of conflicts', function () {
p.getComponentWithName('comp1').graph.evaluateHealth();
p.getComponentWithName('comp2').graph.evaluateHealth();
p.getComponentWithName('comp3').graph.evaluateHealth();
expect(
WarningsModel.instance.getTotalNumberOfWarningsMatching(
(key, ref, warning) => warning.warning.type === 'conflict'
)
).toBe(4);
});
it('can count warnings for a components', function () {
const c = p.getComponentWithName('comp1');
c.graph.evaluateHealth();
let warnings = WarningsModel.instance.getNumberOfWarningsForComponent(c);
expect(warnings).toBe(1);
//conflict warnings are global, so test counting all non-global conflicts as well
warnings = WarningsModel.instance.getNumberOfWarningsForComponent(c, {
excludeGlobal: true
});
expect(warnings).toBe(0);
});
it('can detect param conflict warnings', function () {
var c = p.getComponentWithName('comp1');
c.graph.evaluateHealth();
var w = WarningsModel.instance.warnings['comp1'];
expect(w['node/A']['node-param-conflict-A']).not.toBe(undefined);
expect(WarningsModel.instance.getNumberOfWarningsForComponent(c)).toBe(1);
});
it('can detect type conflict warnings', function () {
var c = p.getComponentWithName('comp2');
c.graph.evaluateHealth();
var w = WarningsModel.instance.warnings['comp2'];
expect(w['node/B']['node-type-conflict']).not.toBe(undefined);
expect(WarningsModel.instance.getNumberOfWarningsForComponent(c)).toBe(1);
});
it('can clear warnings', function () {
// Clear the warning
var c = p.getComponentWithName('comp1');
WarningsModel.instance.setWarning(
{
component: c,
node: c.graph.roots[0],
key: 'node-param-conflict-A'
},
undefined
);
expect(WarningsModel.instance.hasComponentWarnings(c)).toBe(false);
// Clear warnings
var c = p.getComponentWithName('comp2');
WarningsModel.instance.clearWarningsForRef({
component: c,
node: c.graph.roots[0]
});
expect(WarningsModel.instance.hasComponentWarnings(c)).toBe(false);
});
it('can format warnings', function () {
var c = p.getComponentWithName('comp3');
c.graph.evaluateHealth();
var w = WarningsModel.instance.warnings['comp3'];
expect(w['node/C']['node-param-conflict-alignX']).not.toBe(undefined);
expect(w['node/C']['node-param-conflict-alignX'].warning.message).toEqual('Merge conflict at parameter AlignX');
expect(w['node/C']['node-param-conflict-x']).not.toBe(undefined);
expect(w['node/C']['node-param-conflict-x'].warning.message).toEqual('Merge conflict at parameter x');
});
it('clear warnings when project is unloaded', function () {
var c = p.getComponentWithName('comp1');
c.graph.evaluateHealth();
expect(WarningsModel.instance.hasComponentWarnings(c)).toBe(true);
ProjectModel.instance = undefined;
expect(WarningsModel.instance.warnings).toEqual({});
});
function getProject() {
return {
name: 'p',
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: 'group',
id: 'A',
conflicts: [
{
type: 'parameter',
name: 'A',
ours: 0,
theirs: 1
}
]
}
],
connections: []
}
},
{
name: 'comp2',
graph: {
roots: [
{
type: 'group',
id: 'B',
conflicts: [
{
type: 'typename',
ours: '/a',
theirs: '/b'
}
]
}
]
}
},
{
name: 'comp3',
graph: {
roots: [
{
type: 'group',
id: 'C',
conflicts: [
{
type: 'parameter',
name: 'x',
ours: {
value: 10,
unit: '%'
},
theirs: 10
},
{
type: 'parameter',
name: 'alignX',
ours: 'left',
theirs: 'right'
}
]
}
]
}
}
]
};
}
});

View File

@@ -0,0 +1,51 @@
const CreateNewNodePanel = require('@noodl-views/createnewnodepanel');
const { ProjectModel } = require('@noodl-models/projectmodel');
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
describe('Create new node panel unit tests', function () {
var p, cp;
xit('can setup panel', function () {
ProjectModel.instance = new ProjectModel();
p = ProjectModel.fromJSON(project);
cp = new CreateNewNodePanel({
model: p.getComponentWithName('Root').graph,
pos: {
x: 100,
y: 50
},
runtimeType: 'browser'
});
cp.render();
expect(cp).not.toBe(undefined);
});
xit('can create nodes', function () {
var c = p.getComponentWithName('Root');
cp.performCreate(NodeLibrary.instance.getNodeTypeWithName('group'));
expect(c.graph.roots.length).toBe(2);
expect(c.graph.roots[1].type).toBe(NodeLibrary.instance.getNodeTypeWithName('group'));
expect(c.graph.roots[1].x).toBe(100);
expect(c.graph.roots[1].y).toBe(50);
expect(c.graph.roots[1].version).toBe(2);
});
var project = {
components: [
{
name: 'Root',
graph: {
roots: [
{
id: 'A',
type: 'group'
}
]
}
}
]
};
});

View File

@@ -0,0 +1,101 @@
const NodeGraphModel = require('@noodl-models/nodegraphmodel').NodeGraphModel;
const NodeGraphNode = require('@noodl-models/nodegraphmodel').NodeGraphNode;
const { ComponentModel } = require('@noodl-models/componentmodel');
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
describe('Create status tests', function () {
var g1, c1, c2;
beforeEach(() => {
window.NodeLibraryData = require('../nodegraph/nodelibrary');
NodeLibrary.instance.loadLibrary();
});
it("can detect nodes that can't have/be children", function () {
g1 = new NodeGraphModel();
c1 = new ComponentModel({
graph: g1,
name: 'c1'
});
var n1 = NodeGraphNode.fromJSON(
{
type: 'group',
id: 'A'
},
g1
);
g1.addRoot(n1);
var n2 = NodeGraphNode.fromJSON(
{
type: 'animation',
id: 'B'
},
g1
);
g1.addRoot(n2);
var n3 = NodeGraphNode.fromJSON(
{
type: 'Component Children',
id: 'C'
},
g1
);
g1.addRoot(n3);
// Nodes that can have children
var status = c1.getCreateStatus({
parent: n1,
type: NodeLibrary.instance.getNodeTypeWithName('group')
});
expect(status.creatable).toBe(true);
// Animation nodes cannot be children
var status = c1.getCreateStatus({
parent: n1,
type: NodeLibrary.instance.getNodeTypeWithName('animation')
});
expect(status.creatable).toBe(false);
// Animation nodes cannot have children
var status = c1.getCreateStatus({
parent: n2,
type: NodeLibrary.instance.getNodeTypeWithName('group')
});
expect(status.creatable).toBe(false);
// Instance of itself
var status = c1.getCreateStatus({
type: c1
});
expect(status.creatable).toBe(false);
});
it('can detect circular references', function () {
c1 = ComponentModel.fromJSON({
name: 'comp1',
graph: {}
});
c2 = ComponentModel.fromJSON({
name: 'comp2',
graph: {}
});
c1.graph.addRoot(
NodeGraphNode.fromJSON(
{
type: c2,
id: 'C'
},
c1.graph
)
);
var status = c2.getCreateStatus({
type: c1
});
expect(status.creatable).toBe(false);
});
});

View File

@@ -0,0 +1,496 @@
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
const Exporter = require('@noodl-utils/exporter');
const { ProjectModel } = require('@noodl-models/projectmodel');
describe('export tests', function () {
xit('can export ports on components', function () {
ProjectModel.instance = ProjectModel.fromJSON(project1);
NodeLibrary.instance.registerModule(ProjectModel.instance);
ProjectModel.instance.setRootNode(ProjectModel.instance.findNodeWithId('Image-1'));
const json = Exporter.exportToJSON(ProjectModel.instance);
// Should not export types where the type has not been resolved
expect(json.components[0].ports.length).toBe(2);
expect(json.components[0].ports[0].name).toBe('out1');
expect(json.components[0].ports[0].plug).toBe('output');
expect(NodeLibrary.nameForPortType(json.components[0].ports[0].type)).toBe('string');
expect(json.components[0].ports[1].name).toBe('out2');
expect(json.components[0].ports[1].plug).toBe('output');
expect(NodeLibrary.nameForPortType(json.components[0].ports[1].type)).toBe('number');
NodeLibrary.instance.unregisterModule(ProjectModel.instance);
});
it('project settings are exported', function () {
ProjectModel.instance = ProjectModel.fromJSON(project2);
NodeLibrary.instance.registerModule(ProjectModel.instance);
ProjectModel.instance.setRootNode(ProjectModel.instance.findNodeWithId('Group-1'));
const json = Exporter.exportToJSON(ProjectModel.instance);
expect(json.settings.canvasWidth).toBe(303);
expect(json.settings.canvasHeight).toBe(404);
NodeLibrary.instance.unregisterModule(ProjectModel.instance);
});
function matchBundle(bundle, componentIndex) {
function arrayHasSameElements(a, b) {
//check if equal but ignore order
if (a.length !== b.length) return false;
return a.every((e) => b.includes(e));
}
return Object.values(componentIndex).some((b) => arrayHasSameElements(bundle, b));
}
it('can export an index that includes pages and for each nodes', function () {
ProjectModel.instance = ProjectModel.fromJSON({
components: [
{
name: '/root',
graph: {
roots: [
{
id: 'nav-stack',
type: 'Page Stack',
parameters: {
pages: [
{
id: 'p1',
label: 'Page1'
},
{
id: 'p2',
label: 'Page2'
}
],
'pageComp-p1': '/page1',
'pageComp-p2': '/page2'
}
},
{
id: 'n0',
type: '/shared-comp'
}
]
}
},
{
name: '/page1',
graph: {
roots: [
{
id: 'n0',
type: '/shared-comp'
}
]
}
},
{
name: '/page2',
graph: {}
},
{
name: '/shared-comp',
graph: {}
},
{
name: '/remaining-comp',
graph: {}
}
]
});
ProjectModel.instance.setRootNode(ProjectModel.instance.findNodeWithId('nav-stack'));
const json = Exporter.exportToJSON(ProjectModel.instance);
//make sure the root components are present directly in the export, not in a bundle
expect(json.components.find((c) => c.name === '/root'));
expect(json.components.find((c) => c.name === '/shared-comp'));
//this test assume the bundles are emitted in a specific order
//it makes the test tied to implementation specifics, so not great
const bundles = {
b2: {
components: ['/page1'],
dependencies: []
},
b3: {
components: ['/page2'],
dependencies: []
},
b4: {
components: ['/remaining-comp'],
dependencies: []
}
};
expect(json.componentIndex).toEqual(bundles);
});
it('can follow For Each nodes when collecting dependencies', function () {
ProjectModel.instance = ProjectModel.fromJSON({
components: [
{
name: '/root',
graph: {
roots: [
{
id: 'node1',
type: 'For Each',
parameters: {
template: '/for-each-comp'
}
}
]
}
},
{
name: '/for-each-comp',
graph: {}
}
]
});
const allComponents = ProjectModel.instance.getComponents();
const rootComponent = allComponents.find((c) => c.name === '/root');
const graph = Exporter._collectDependencyGraph(rootComponent, allComponents);
const deps = Exporter._flattenDependencyGraph(graph);
expect(deps.length).toBe(2);
expect(deps.find((c) => c.name === '/root'));
expect(deps.find((c) => c.name === '/for-each-comp'));
});
it("creates bundles that doesn't have duplicates", function () {
ProjectModel.instance = ProjectModel.fromJSON({
components: [
{
name: '/comp1',
graph: {
roots: [
{
id: 'nav-stack',
type: 'Page Stack',
parameters: {
pages: [
{
id: 'p1',
label: 'Page1'
},
{
id: 'p2',
label: 'Page2'
}
],
'pageComp-p1': '/page1',
'pageComp-p2': '/page2'
}
},
{
id: 'n0',
type: '/shared-comp'
}
]
}
},
{
name: '/page1',
id: 'page1',
graph: {
roots: [
{
id: 'n1',
type: '/shared-comp'
},
{
id: 'n2',
type: '/comp-used-on-both-pages'
}
]
}
},
{
name: '/page2',
id: 'page2',
graph: {
roots: [
{
id: 'n3',
type: '/comp-used-on-both-pages'
}
]
}
},
{
name: '/shared-comp',
graph: {}
},
{
name: '/comp-used-on-both-pages',
graph: {}
}
]
});
ProjectModel.instance.setRootNode(ProjectModel.instance.findNodeWithId('nav-stack'));
const json = Exporter.exportToJSON(ProjectModel.instance);
const componentCount = {};
for (const name in json.componentIndex) {
for (const comp of json.componentIndex[name].components) {
if (componentCount[comp]) componentCount[comp]++;
else componentCount[comp] = 1;
}
}
for (const name in componentCount) {
expect(componentCount[name]).toBe(1, 'Component ' + name + ' is in multiple bundles');
}
});
it('calculated dependencies for bundles', function () {
ProjectModel.instance = ProjectModel.fromJSON({
components: [
{
name: '/comp1',
graph: {
roots: [
{
id: 'nav-stack',
type: 'Page Stack',
parameters: {
pages: [
{
id: 'p1',
label: 'Page1'
},
{
id: 'p2',
label: 'Page2'
}
],
'pageComp-p1': '/page1',
'pageComp-p2': '/page2'
}
},
{
id: 'n0',
type: '/shared-comp'
}
]
}
},
{
name: '/page1',
id: 'page1',
graph: {
roots: [
{
id: 'n1',
type: '/shared-comp'
},
{
id: 'n2',
type: '/comp-used-on-both-pages'
}
]
}
},
{
name: '/page2',
id: 'page2',
graph: {
roots: [
{
id: 'n3',
type: '/comp-used-on-both-pages'
}
]
}
},
{
name: '/shared-comp',
graph: {}
},
{
name: '/comp-used-on-both-pages',
graph: {}
}
]
});
ProjectModel.instance.setRootNode(ProjectModel.instance.findNodeWithId('nav-stack'));
const allComponents = ProjectModel.instance.getComponents();
const rootComponent = allComponents.find((c) => c.name === '/comp1');
const componentIndex = Exporter.getComponentIndex(rootComponent, allComponents);
//this test assume the bundles are emitted in a specific order
//it makes the test tied to implementation specifics, so not great
const bundles = {
b0: {
components: ['/comp1'],
dependencies: ['b1']
},
b1: {
components: ['/shared-comp'],
dependencies: []
},
b2: {
components: ['/page1'],
dependencies: ['b1', 'b3']
},
b3: {
components: ['/comp-used-on-both-pages'],
dependencies: []
},
b4: {
components: ['/page2'],
dependencies: ['b3']
}
};
expect(componentIndex).toEqual(bundles);
});
xit('ignores project settings flagged to be excluded', function () {
ProjectModel.instance = ProjectModel.fromJSON({
components: [
{
name: '/comp2',
graph: {
roots: [
{
id: 'Group-1',
type: 'group'
}
]
}
}
],
settings: {
someSetting: 'test',
someSetting2: 'test2',
settingIgnoredInExport: 'test3'
}
});
NodeLibrary.instance.registerModule(ProjectModel.instance);
ProjectModel.instance.setRootNode(ProjectModel.instance.findNodeWithId('Group-1'));
const json = Exporter.exportToJSON(ProjectModel.instance);
expect(json.settings.someSetting).toBe('test');
expect(json.settings.someSetting2).toBe('test2');
expect(json.settings.settingIgnoredInExport).toBe(undefined);
NodeLibrary.instance.unregisterModule(ProjectModel.instance);
});
var project1 = {
components: [
{
name: '/comp1',
graph: {
roots: [
{
id: 'Image-1',
type: 'image',
parameters: {
image: 'pic1.png',
css: '%%%mycss {background:#ff00ff;}'
}
},
{
id: 'Comp-2',
type: '/comp2'
},
{
id: 'CO-1',
type: 'Component Outputs',
ports: [
{
name: 'out1',
type: '*',
plug: 'input'
},
{
name: 'out2',
type: '*',
plug: 'input'
}
]
}
],
connections: [
{
fromId: 'Image-1',
fromProperty: 'image',
toId: 'CO-1',
toProperty: 'out1'
},
{
fromId: 'Image-1',
fromProperty: 'screenX',
toId: 'CO-1',
toProperty: 'out2'
}
]
}
},
// Should be excluded
{
name: '/comp3',
graph: {
roots: [
{
id: 'Image-3',
type: 'image'
}
]
}
},
{
name: '/comp2',
graph: {
roots: [
{
id: 'Image-2',
type: 'image'
}
]
}
}
]
};
// Second project for cross project reference
var project2 = {
components: [
{
name: '/comp2',
graph: {
roots: [
{
id: 'Group-1',
type: 'group'
}
]
}
}
],
settings: {
canvasWidth: 303,
canvasHeight: 404
}
};
});

View File

@@ -0,0 +1,117 @@
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
const NodeGraphNode = require('@noodl-models/nodegraphmodel').NodeGraphNode;
const { ComponentModel } = require('@noodl-models/componentmodel');
describe('Component instances', function () {
var c;
beforeEach(() => {
window.NodeLibraryData = require('../nodegraph/nodelibrary');
NodeLibrary.instance.loadLibrary();
c = ComponentModel.fromJSON({
name: 'test',
graph: {
roots: [
{
type: 'group',
id: 'Group-1'
}
]
}
});
});
it('can load component', function () {
expect(c).not.toBe(undefined);
});
it('can detect children that can be attached', function () {
const n1 = NodeGraphNode.fromJSON({
type: 'image'
});
const n2 = NodeGraphNode.fromJSON({
type: 'animation'
});
const n3 = NodeGraphNode.fromJSON({
type: 'scaleModifier'
});
const n4 = NodeGraphNode.fromJSON({
type: 'image'
});
// Can accept an image
expect(c.graph.findNodeWithId('Group-1').canAcceptChildren([n1])).toBe(true);
// Not all nodes can be children
expect(c.graph.findNodeWithId('Group-1').canAcceptChildren([n1, n2])).toBe(false);
// Not all nodes have accepted categories
expect(c.graph.findNodeWithId('Group-1').canAcceptChildren([n1, n3])).toBe(false);
// Multiple nodes with same category should be OK
expect(c.graph.findNodeWithId('Group-1').canAcceptChildren([n1, n4])).toBe(true);
});
it('can report allow as child for components', function () {
var c2 = ComponentModel.fromJSON({
name: 'test',
graph: {}
});
// No root nodes that can be children
expect(c2.allowAsChild).toBe(false);
c2.graph.addRoot(
NodeGraphNode.fromJSON({
type: 'group'
})
);
// Group can be child
expect(c2.allowAsChild).toBe(true);
// Report correct category
expect(c2.category).toBe('visuals');
});
it('can report accepted children for components', function () {
expect(c.allowChildrenWithCategory).toBe(undefined);
c.graph.addRoot(
NodeGraphNode.fromJSON({
type: 'Component Children'
})
);
expect(c.allowChildrenWithCategory).toEqual(['visuals']);
c.graph.addRoot(
NodeGraphNode.fromJSON({
type: 'Component Modifier Children'
})
);
expect(c.allowChildrenWithCategory).toEqual(['modifiers', 'visuals']);
});
it('can detect allow export root', function () {
var c2 = ComponentModel.fromJSON({
name: 'test',
graph: {}
});
expect(c2.allowAsExportRoot).toBe(false);
c2.graph.addRoot(
NodeGraphNode.fromJSON({
type: 'group'
})
);
expect(c2.allowAsExportRoot).toBe(true);
});
});

View File

@@ -0,0 +1,11 @@
export * from './conflictwarnings';
export * from './createnewnode';
export * from './createstatus';
export * from './export';
export * from './hierarchy';
export * from './nodegrapheditor';
export * from './nodegraphmodel';
export * from './nodelibrary-spec';
export * from './propertyeditor';
export * from './typechangepropagation';
export * from './warnings-model-spec';

View File

@@ -0,0 +1,574 @@
const NodeGraphNode = require('@noodl-models/nodegraphmodel').NodeGraphNode;
const NodeGraphEditor = require('@noodl-views/nodegrapheditor').NodeGraphEditor;
const { ProjectModel } = require('@noodl-models/projectmodel');
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
const PopupLayer = require('@noodl-views/popuplayer');
const DebugInspector = require('@noodl-utils/debuginspector');
const ViewerConnection = require('../../src/editor/src/ViewerConnection');
describe('Node graph editor auto tests', function () {
var c1, g1;
// Records and store mouse calls to the node graph editor
var storeTimeout;
var recordedEvents = [];
function storeEvents() {
clearTimeout(storeTimeout);
storeTimeout = setTimeout(function () {
localStorage['recordedEvents'] = JSON.stringify(recordedEvents);
}, 1000);
}
function recordEvents() {
var types = ['mousedown', 'mousemove', 'mouseup'];
// mouse
var old = NodeGraphEditor.instance.mouse;
NodeGraphEditor.instance.mouse = function (type, pos, evt) {
var now = +new Date();
recordedEvents.push({
type: type,
time: now,
x: pos.x,
y: pos.y,
evt: evt
? {
button: evt.button,
shiftKey: evt.shiftKey ? true : undefined,
ctrlKey: evt.ctrlKey ? true : undefined,
spaceKey: evt.spaceKey ? true : undefined
}
: undefined
});
storeEvents();
return old.call(this, type, pos, evt);
};
// cut, copy, paste
var oldCut = NodeGraphEditor.instance.cut;
NodeGraphEditor.instance.cut = function () {
var now = +new Date();
recordedEvents.push({
type: 'cut',
time: now
});
return oldCut.call(this);
};
var oldCopy = NodeGraphEditor.instance.copy;
NodeGraphEditor.instance.copy = function () {
var now = +new Date();
recordedEvents.push({
type: 'copy',
time: now
});
return oldCopy.call(this);
};
var oldPaste = NodeGraphEditor.instance.paste;
NodeGraphEditor.instance.paste = function () {
var now = +new Date();
recordedEvents.push({
type: 'paste',
time: now
});
return oldPaste.call(this);
};
}
// Plays back previously stored mouse calls
function playEvents(events, done, realtime) {
var i = 0;
function play() {
var event = events[i];
var type = event.type;
if (type === 'cut') {
NodeGraphEditor.instance.cut();
} else if (type === 'copy') {
NodeGraphEditor.instance.copy();
} else if (type === 'paste') {
NodeGraphEditor.instance.paste();
} else {
NodeGraphEditor.instance.mouse(
event.type,
{
x: event.x,
y: event.y
},
event.evt
);
}
if (events[i + 1]) {
setTimeout(function () {
i++;
play();
}, events[i + 1].time - event.time);
} else {
done();
}
}
play();
}
// Sets up a fresh node graph editor
function setup() {
$('body').append(
'<div id="node-graph-editor" style="position:absolute; top:0px; right:0px; width:400px; height:800px; background-color:white;"></div>'
);
// Disable context menu
$('body').on('contextmenu', function () {
return false;
});
// NodeLibrary.instance = new NodeLibrary();
ProjectModel.instance = ProjectModel.fromJSON(project);
NodeLibrary.instance.registerModule(ProjectModel.instance);
c1 = ProjectModel.instance.getComponentWithName('Root');
g1 = c1.graph;
// Mocking panel instance
PopupLayer.instance = {
showTooltip: function () {},
hideTooltip: function () {},
showToast: function () {},
on: function () {},
hidePopup: function () {},
showPopup: function () {},
isDragging: function () {
return false;
},
showModal: function () {},
hideModal: function () {},
hideAllModalsAndPopups: function () {}
};
// Mocking viewer connection
ViewerConnection.instance = {
on: function () {},
sendNodeHighlighted: function () {}
};
NodeGraphEditor.instance = new NodeGraphEditor({
model: g1
});
NodeGraphEditor.instance.setPanAndScale({
scale: 1,
x: 0,
y: 0
});
NodeGraphEditor.instance.render();
$('#node-graph-editor').append(NodeGraphEditor.instance.el);
NodeGraphEditor.instance.resize({
x: 0,
y: 0,
width: 400,
height: 800
});
}
var project = {
components: [
{
name: 'Root',
ports: [],
visual: true,
visualRootId: '14e31556-f569-21bb-e948-65af515ae574',
canHaveVisualChildren: false,
graph: {
connections: [],
roots: []
}
}
]
};
function setupNodes() {
g1.addRoot(
NodeGraphNode.fromJSON({
id: '14e31556-f569-21bb-e948-65af515ae574',
type: 'group',
label: 'group1',
x: 49,
y: 75
})
);
g1.addRoot(
NodeGraphNode.fromJSON({
id: '33a2be5c-b341-27b4-292f-aee1b5bc30fe',
type: 'group',
label: 'group2',
x: 49,
y: 164
})
);
g1.addRoot(
NodeGraphNode.fromJSON({
id: '34ea0053-3334-d2cb-3a31-de577030102e',
type: 'group',
label: 'group3',
x: 49,
y: 267
})
);
}
function setupNodes2() {
g1.addRoot(
NodeGraphNode.fromJSON({
id: 'A',
type: 'group',
label: 'group4',
x: 49,
y: 367
})
);
}
function setupConnections() {
g1.addConnection({
fromId: '14e31556-f569-21bb-e948-65af515ae574',
fromProperty: 'screenX',
toId: '33a2be5c-b341-27b4-292f-aee1b5bc30fe',
toProperty: 'x'
});
}
function setupConnections2() {
g1.addConnection({
fromId: '34ea0053-3334-d2cb-3a31-de577030102e',
fromProperty: 'screenX',
toId: '33a2be5c-b341-27b4-292f-aee1b5bc30fe',
toProperty: 'x'
});
g1.addConnection({
fromId: '14e31556-f569-21bb-e948-65af515ae574',
fromProperty: 'screenX',
toId: '34ea0053-3334-d2cb-3a31-de577030102e',
toProperty: 'y'
});
}
function teardownNodes() {
g1.removeAllNodes();
}
// Closes and tears the node graph editor down
function teardown() {
$('#node-graph-editor').remove();
NodeGraphEditor.instance = undefined;
}
it('can setup view', function () {
setup();
expect(NodeGraphEditor.instance).not.toBe(undefined);
});
/* it('can record events',function(done) {
setupNodes();
// setupNodes2();
recordEvents();
});
return;*/
// Multi re arrange
xit('Multi rearrange nodes, attach, detach', function (done) {
setupNodes();
setupNodes2();
playEvents(require('../recordings/multirearrange.json'), function () {
expect(g1.roots.length).toBe(2);
var n1 = g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574');
var n2 = g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe');
var n3 = g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e');
var n4 = g1.findNodeWithId('A');
expect(n1.children[0]).toBe(n3);
expect(n4.children[0]).toBe(n2);
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
teardownNodes();
done();
});
});
xit('cut n paste', function (done) {
setupNodes();
playEvents(require('../recordings/cutnpaste.json'), function () {
expect(g1.roots.length).toBe(3);
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe')).not.toBe(undefined);
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e')).not.toBe(undefined);
expect(g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574')).not.toBe(undefined);
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
teardownNodes();
done();
});
});
xit('can undo attach, detach and move', function (done) {
setupNodes();
playEvents(require('../recordings/undoarrange.json'), function () {
// group3
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e').parent).toBe(undefined);
// group2 child to group1
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe').parent).toBe(
g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574')
);
NodeGraphEditor.instance.undo(); // Undo detach
// group3 child to group1
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e').parent).toBe(
g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574')
);
NodeGraphEditor.instance.undo(); // Undo attach
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e').parent).toBe(undefined);
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe').parent).toBe(undefined);
NodeGraphEditor.instance.undo(); // Undo move
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e').x).toBe(49);
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e').y).toBe(267);
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
teardownNodes();
done();
});
});
// Single re arrange
xit('single rearrange nodes, attach, detach', function (done) {
setupNodes();
playEvents(require('../recordings/singlerearrange.json'), function () {
var n = g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574');
expect(n.parent).toBe(undefined);
var n1 = g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe');
var n2 = g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e');
expect(n1.parent).toBe(n);
expect(n2.parent).toBe(n);
expect(n.children[0]).toBe(n2);
expect(n.children[1]).toBe(n1);
expect(g1.roots.length).toBe(1);
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
teardownNodes();
done();
});
});
// Multi select and mode nodes
xit('can multiselect and move nodes', function (done) {
setupNodes();
playEvents(require('../recordings/multimove.json'), function () {
var n1 = NodeGraphEditor.instance.model.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574');
var n2 = NodeGraphEditor.instance.model.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe');
expect(n1.x).toBe(50);
expect(n1.y).toBe(365);
expect(n2.x).toBe(50);
expect(n2.y).toBe(454);
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
teardownNodes();
done();
});
});
// Drag to move nodes
xit('can move nodes', function (done) {
setupNodes();
playEvents(require('../recordings/movenode.json'), function () {
var n = NodeGraphEditor.instance.model.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574');
expect(n.x).toBe(47);
expect(n.y).toBe(408);
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
teardownNodes();
done();
});
});
xit('can undo cut and paste', function (done) {
setupNodes();
playEvents(require('../recordings/undocutpaste.json'), function () {
expect(g1.roots.length).toBe(3);
expect(g1.roots[1].label).toBe('group2');
expect(g1.roots[2].label).toBe('group3');
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe')).toBe(undefined);
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e')).toBe(undefined);
NodeGraphEditor.instance.undo(); // Undo paste
expect(g1.roots.length).toBe(1);
NodeGraphEditor.instance.undo(); // Undo cut
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe')).not.toBe(undefined);
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e')).not.toBe(undefined);
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
teardownNodes();
done();
});
});
// Undo cut children
xit('Can undo cut children', function (done) {
setupNodes();
setupNodes2();
playEvents(require('../recordings/undocutchildren.json'), function () {
expect(g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574').children[0]).toBe(g1.findNodeWithId('A'));
NodeGraphEditor.instance.undo();
expect(g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574').children[0]).toBe(
g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe')
);
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe').children[0]).toBe(
g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e')
);
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
teardownNodes();
done();
});
});
// Undo cut connections
xit('Can undo cut connections', function (done) {
setupNodes();
setupConnections();
setupConnections2();
playEvents(require('../recordings/undocutconnections.json'), function () {
expect(g1.connections.length).toBe(0);
NodeGraphEditor.instance.undo();
expect(g1.connections.length).toBe(3);
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
teardownNodes();
done();
});
});
// Can delete and undo connection
xit('can delete and undo delete connection', function (done) {
setupNodes();
setupConnections();
playEvents(require('../recordings/deletecon.json'), function () {
expect(g1.connections.length).toBe(0);
NodeGraphEditor.instance.undo();
expect(g1.connections.length).toBe(1);
expect(g1.connections[0].fromId).toBe('14e31556-f569-21bb-e948-65af515ae574');
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
teardownNodes();
done();
});
});
// Delete
xit('can delete nodes and undo', function (done) {
setupNodes();
setupConnections();
playEvents(require('../recordings/deleteandundo.json'), function () {
NodeGraphEditor.instance.delete();
expect(g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574')).toBe(undefined);
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe')).toBe(undefined);
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e')).not.toBe(undefined);
expect(g1.connections.length).toBe(0);
NodeGraphEditor.instance.undo();
expect(g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574')).not.toBe(undefined);
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe')).not.toBe(undefined);
expect(g1.connections.length).toBe(1);
teardownNodes();
done();
});
});
xit('Can create inspectors', function (done) {
setupNodes();
setupConnections();
setupConnections2();
playEvents(
require('../recordings/connectiondebugger.json'),
function () {
var model = DebugInspector.InspectorsModel.instanceForProject(ProjectModel.instance);
var inspectors = model.getInspectors();
expect(inspectors[1]).toEqual({
connectionKey: '14e31556-f569-21bb-e948-65af515ae574screenX33a2be5c-b341-27b4-292f-aee1b5bc30fex',
pinned: true,
position: 0.390625,
type: 'connection',
connection: {
fromId: '14e31556-f569-21bb-e948-65af515ae574',
fromProperty: 'screenX',
toId: '33a2be5c-b341-27b4-292f-aee1b5bc30fe',
toProperty: 'x'
}
});
expect(inspectors[0]).toEqual({
connectionKey: '34ea0053-3334-d2cb-3a31-de577030102escreenX33a2be5c-b341-27b4-292f-aee1b5bc30fex',
pinned: true,
position: 0.515625,
type: 'connection',
connection: {
fromId: '34ea0053-3334-d2cb-3a31-de577030102e',
fromProperty: 'screenX',
toId: '33a2be5c-b341-27b4-292f-aee1b5bc30fe',
toProperty: 'x'
}
});
expect(inspectors.length).toBe(2);
teardownNodes();
done();
},
true
); // Play in real time
});
// Multi select and mode nodes
it('can tear down', function () {
teardown();
expect(NodeGraphEditor.instance).toBe(undefined);
});
});

View File

@@ -0,0 +1,173 @@
const NodeGraphModel = require('@noodl-models/nodegraphmodel').NodeGraphModel;
const NodeGraphNode = require('@noodl-models/nodegraphmodel').NodeGraphNode;
describe('Node graph basic tests', function () {
it('fires nodeAdded, nodeRemoved and connectionAdded, connectionRemoved events', function () {
const g1 = new NodeGraphModel();
var handlers = jasmine.createSpyObj('handlers', [
'nodeAdded',
'nodeRemoved',
'connectionAdded',
'connectionRemoved'
]);
g1.on('nodeAdded', handlers.nodeAdded);
g1.on('nodeRemoved', handlers.nodeRemoved);
g1.on('connectionAdded', handlers.connectionAdded);
g1.on('connectionRemoved', handlers.connectionRemoved);
const n1 = NodeGraphNode.fromJSON({
type: 'layer',
id: 'A'
});
g1.addRoot(n1);
expect(handlers.nodeAdded.calls.argsFor(0)[0].model).toBe(n1);
const n2 = NodeGraphNode.fromJSON({
type: 'layer',
id: 'B'
});
g1.addRoot(n2);
expect(handlers.nodeAdded.calls.argsFor(1)[0].model).toBe(n2);
const con = {
fromId: 'A',
fromProperty: 'color',
toId: 'B',
toProperty: 'color'
};
g1.addConnection(con);
expect(handlers.connectionAdded.calls.argsFor(0)[0].model).toBe(con);
g1.removeConnection(con);
expect(handlers.connectionRemoved.calls.argsFor(0)[0].model).toBe(con);
g1.removeNode(n2);
expect(handlers.nodeRemoved.calls.argsFor(0)[0].model).toBe(n2);
});
it('can change the id on all nodes', () => {
const g1 = new NodeGraphModel();
const n1 = NodeGraphNode.fromJSON({
type: 'layer',
id: 'A'
});
g1.addRoot(n1);
const n2 = NodeGraphNode.fromJSON({
type: 'layer',
id: 'B'
});
g1.addRoot(n2);
const con = {
fromId: 'A',
fromProperty: 'color',
toId: 'B',
toProperty: 'color'
};
g1.addConnection(con);
g1.rekeyAllIds();
expect(n1.id).not.toEqual('A');
expect(n2.id).not.toEqual('B');
expect(con.fromId).toEqual(n1.id);
expect(con.toId).toEqual(n2.id);
});
describe('tracks what nodes are in the graph correctly', () => {
it('can add and remove one node', () => {
const graph = new NodeGraphModel();
//add a root
expect(graph.findNodeWithId('A')).toBeFalsy();
const n1 = NodeGraphNode.fromJSON({
type: 'test',
id: 'A'
});
graph.addRoot(n1);
expect(graph.findNodeWithId('A')).toBeTruthy();
//and then remove it
graph.removeNode(n1);
expect(graph.findNodeWithId('A')).toBeFalsy();
});
it('can add and remove a node with children', () => {
const graph = new NodeGraphModel();
//add a new root with children and verify that both nodes are found
const n2 = NodeGraphNode.fromJSON({
type: 'test',
id: 'A',
children: [
{
type: 'test',
id: 'B'
}
]
});
graph.addRoot(n2);
expect(graph.findNodeWithId('A')).toBe(n2);
expect(graph.findNodeWithId('B')).toBe(n2.children[0]);
//remove the root and verify that both nodes are removed
graph.removeNode(n2);
expect(graph.findNodeWithId('A')).toBeFalsy();
expect(graph.findNodeWithId('B')).toBeFalsy();
});
it('can add a child node to a root', () => {
const graph = new NodeGraphModel();
const root = NodeGraphNode.fromJSON({
type: 'test',
id: 'A'
});
graph.addRoot(root);
const child = NodeGraphNode.fromJSON({
type: 'test',
id: 'B'
});
root.addChild(child);
expect(graph.findNodeWithId('A')).toBe(root);
expect(graph.findNodeWithId('B')).toBe(child);
const child2 = NodeGraphNode.fromJSON({
type: 'test',
id: 'C'
});
root.insertChild(child2, 0);
expect(graph.findNodeWithId('C')).toBe(child2);
});
it('can remove a child node to a root', () => {
const graph = new NodeGraphModel();
const n2 = NodeGraphNode.fromJSON({
type: 'test',
id: 'A',
children: [
{
type: 'test',
id: 'B'
}
]
});
graph.addRoot(n2);
graph.removeNode(n2.children[0]);
expect(graph.findNodeWithId('B')).toBeFalsy();
expect(graph.findNodeWithId('A')).toBe(n2);
});
});
});

View File

@@ -0,0 +1,318 @@
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
const BasicNodeType = require('@noodl-models/nodelibrary/BasicNodeType').BasicNodeType;
const UnknownNodeType = require('@noodl-models/nodelibrary/UnknownNodeType').UnknownNodeType;
const { ComponentModel } = require('@noodl-models/componentmodel');
const { ProjectModel } = require('@noodl-models/projectmodel');
describe('Node library tests', function () {
beforeEach(() => {
//reload fresh library for every test
window.NodeLibraryData = require('../nodegraph/nodelibrary');
for (const module of NodeLibrary.instance.modules) {
NodeLibrary.instance.unregisterModule(module);
}
NodeLibrary.instance.loadLibrary();
});
it('can detect compatibale port types', function () {
// Equal types
expect(NodeLibrary.instance.canCastPortTypes('string', 'string')).toBe(true);
expect(
NodeLibrary.instance.canCastPortTypes(
{
name: 'string'
},
'string'
)
).toBe(true);
expect(
NodeLibrary.instance.canCastPortTypes('string', {
name: 'string'
})
).toBe(true);
// Not equal and cannot cast
expect(
NodeLibrary.instance.canCastPortTypes('string', {
name: 'signal'
})
).toBe(false);
// Star types are compatible with everything
expect(
NodeLibrary.instance.canCastPortTypes('*', {
name: 'number'
})
).toBe(true);
// Casting
expect(
NodeLibrary.instance.canCastPortTypes('number', {
name: 'string'
})
).toBe(true);
});
it('can find compatible port types', function () {
// Single type
expect(
NodeLibrary.instance.findCompatiblePortType([
{
direction: 'to',
type: 'string'
}
])
).toEqual({
name: 'string'
});
// Multiple connections, same type, with modifiers
expect(
NodeLibrary.instance.findCompatiblePortType([
{
direction: 'to',
type: 'string'
},
{
direction: 'from',
type: {
name: 'string',
multiline: true
}
}
])
).toEqual({
name: 'string',
multiline: true
});
// Is enums merged
expect(
NodeLibrary.instance.findCompatiblePortType([
{
direction: 'to',
type: {
name: 'enum',
enums: ['a', 'b']
}
},
{
direction: 'from',
type: {
name: 'enum',
enums: ['b', 'c']
}
}
])
).toEqual({
name: 'enum',
enums: ['a', 'b', 'c']
});
// Find compatible type and merge
expect(
NodeLibrary.instance.findCompatiblePortType([
{
direction: 'to',
type: {
name: 'number',
allowConnectionsOnly: true
}
},
{
direction: 'to',
type: 'boolean'
}
])
).toEqual({
name: 'number',
allowConnectionsOnly: true
});
// Find compatible type, mixed direction
expect(
NodeLibrary.instance.findCompatiblePortType([
{
direction: 'to',
type: {
name: 'number',
allowEditOnly: true
}
},
{
direction: 'to',
type: 'boolean'
},
{
direction: 'from',
type: 'number'
}
])
).toEqual({
name: 'number',
allowEditOnly: true
});
// Find compatible type, mixed direction
expect(
NodeLibrary.instance.findCompatiblePortType([
{
direction: 'to',
type: {
name: 'number'
}
},
{
direction: 'to',
type: 'boolean'
},
{
direction: 'from',
type: 'number'
},
{
direction: 'to',
type: 'color'
}
])
).toEqual({
name: 'string'
});
// Can't find compatible type
expect(
NodeLibrary.instance.findCompatiblePortType([
{
direction: 'to',
type: {
name: 'number',
allowEditOnly: true
}
},
{
direction: 'to',
type: 'boolean'
},
{
direction: 'from',
type: 'number'
},
{
direction: 'to',
type: 'referece'
}
])
).toBe(undefined);
});
it('can reload library', function () {
var p = ProjectModel.fromJSON(getProject());
NodeLibrary.instance.registerModule(p);
var n1 = p.components[0].graph.findNodeWithId('A');
var n2 = p.components[0].graph.findNodeWithId('B');
var n3 = p.components[0].graph.findNodeWithId('C');
var n4 = p.components[0].graph.findNodeWithId('D');
expect(n1.type instanceof BasicNodeType).toBe(true);
expect(n1.type.name).toBe('image');
expect(n2.type instanceof UnknownNodeType).toBe(true);
// Update library
NodeLibraryData.nodetypes.push({
name: 'newimage',
allowAsChild: true,
allowAsExclusiveRootOnly: true,
category: 'visuals',
ports: [
{
name: 'x',
type: {
name: 'number'
},
plug: 'input'
}
]
});
NodeLibrary.instance.reload();
// Both should be OK now
expect(n1.type instanceof BasicNodeType).toBe(true);
expect(n1.type.name).toBe('image');
expect(n2.type instanceof BasicNodeType).toBe(true);
expect(n2.type.name).toBe('newimage');
NodeLibrary.instance.unregisterModule(p);
});
it('can get types by name', () => {
expect(NodeLibrary.instance.getNodeTypeWithName('comp1')).toBeUndefined();
//get basic type from node library
expect(NodeLibrary.instance.getNodeTypeWithName('image')).toBeInstanceOf(BasicNodeType);
//get component type from a project
const p = ProjectModel.fromJSON(getProject());
NodeLibrary.instance.registerModule(p);
expect(NodeLibrary.instance.getNodeTypeWithName('comp1')).toBeInstanceOf(ComponentModel);
//add a new component and get the type
const newComponent = ComponentModel.fromJSON({
name: 'comp2',
graph: {
roots: [
{
id: 'B',
type: 'image'
}
]
}
});
p.addComponent(newComponent);
expect(NodeLibrary.instance.getNodeTypeWithName('comp2')).toBe(newComponent);
//remove the component again
p.removeComponent(newComponent);
expect(NodeLibrary.instance.getNodeTypeWithName('comp2')).toBeUndefined();
//unregister project and make sure the components aren't available anymore
NodeLibrary.instance.unregisterModule(p);
expect(NodeLibrary.instance.getNodeTypeWithName('comp1')).toBeUndefined();
expect(NodeLibrary.instance.getNodeTypeWithName('comp2')).toBeUndefined();
});
function getProject() {
return {
components: [
{
name: 'comp1',
graph: {
roots: [
{
id: 'A',
type: 'image'
},
{
id: 'B',
type: 'newimage'
},
{
id: 'C',
type: 'Event Sender',
parameters: {
channel: 'Channel0'
}
},
{
id: 'D',
type: 'Event Receiver'
}
]
}
}
]
};
}
});

View File

@@ -0,0 +1,751 @@
function createTestNodeLibrary() {
return {
projectsettings: {
dynamicports: [],
ports: [
{
name: 'settingIgnoredInExport',
type: 'string',
ignoreInExport: true
},
{
name: 'someSetting',
type: 'string',
ignoreInExport: false
},
{
name: 'someSetting2',
type: 'string'
}
]
},
typecasts: [
{
from: 'boolean',
to: []
},
{
from: 'number',
to: ['string', 'boolean']
},
{
from: 'string',
to: ['number', 'boolean', 'color']
}
],
dynamicports: [
{
type: 'conditionalports',
name: 'basic'
},
{
type: 'expand',
name: 'basic'
},
{
type: 'portchannel',
name: 'event-sender-channel',
channelPortname: 'channel',
typename: 'Event Sender',
ignorePorts: ['channel']
},
{
type: 'numbered',
name: 'basic-number',
port: {
type: 'number'
}
},
{
type: 'regexp',
name: 'expression-js',
filters: [
{
type: 'replace',
comment: 'Removed javascript style comments',
regexp: '((\\/\\/.*$)|(\\/\\*[\\s\\S]*?\\*\\/))',
args: 'mg',
with: ''
},
{
type: 'replace',
regexp: '"[^"]+"',
args: 'g',
with: ''
},
{
type: 'replace',
regexp: "'[^']+'",
args: 'g',
with: ''
},
{
type: 'replace',
regexp:
'(break|case|class|catch|const|continue|debugger|default|delete|do|else|export|extends|finally|for|function|if|import|in|instanceof|let|new|return|super|switch|this|throw|try|typeof|var|void|while|with|yield)',
args: 'g',
with: ''
},
{
type: 'replace',
regexp: '\\s',
args: 'g',
with: ''
},
{
type: 'ignore',
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*(?=\\()'
},
{
type: 'ignore',
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*(?=\\=)'
},
{
type: 'ports',
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*(?=\\:boolean)',
args: 'g',
port: {
type: {
name: 'boolean'
},
plug: 'input'
}
},
{
type: 'replace',
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*\\:boolean',
args: 'g'
},
{
type: 'ports',
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*(?=\\:string)',
args: 'g',
port: {
type: {
name: 'string'
},
plug: 'input'
}
},
{
type: 'replace',
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*\\:string',
args: 'g'
},
{
type: 'ports',
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*(?=\\:number)',
args: 'g',
port: {
type: {
name: 'number'
},
plug: 'input'
}
},
{
type: 'replace',
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*\\:number',
args: 'g'
},
{
type: 'ports',
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*',
args: 'g',
port: {
type: {
name: '=',
default: 'string',
allowedTypes: ['string', 'boolean', 'number']
},
plug: 'input'
}
}
]
}
],
nodetypes: [
{
name: 'Component Outputs',
haveComponentPorts: true,
color: 'component',
ports: []
},
{
name: 'Component Inputs',
color: 'component',
haveComponentPorts: true,
ports: []
},
{
name: 'Component Children',
color: 'component',
category: 'visuals',
allowSingleInstanceOnly: true,
haveComponentChildren: ['visuals']
},
{
name: 'Component Modifier Children',
color: 'component',
category: 'modifiers',
allowSingleInstanceOnly: true,
haveComponentChildren: ['modifiers']
},
{
name: 'group',
version: 2,
allowAsChild: true,
allowAsExportRoot: true,
category: 'visuals',
allowChildrenWithCategory: ['visuals'],
ports: [
{
name: 'compref',
type: 'component',
plug: 'input'
},
{
group: 'test',
name: 'x',
type: {
name: 'number',
units: ['px', '%']
},
default: {
value: 10,
unit: '%'
},
plug: 'input'
},
{
group: 'test',
name: 'y',
type: {
name: 'number'
},
plug: 'input'
},
{
name: 'opacity',
type: 'number',
plug: 'input'
},
{
displayName: 'AlignX',
name: 'alignX',
type: {
name: 'enum',
enums: [
{
value: 'left',
label: 'Left'
},
{
value: 'center',
label: 'Center'
},
{
value: 'right',
label: 'Right'
}
]
},
plug: 'input'
},
{
name: 'alignY',
type: {
name: 'string',
enums: ['top', 'center', 'bottom']
},
plug: 'input'
},
{
name: 'scaleX',
type: 'number',
plug: 'input'
},
{
name: 'scaleY',
type: 'number',
plug: 'input'
},
{
name: 'width',
type: 'number',
plug: 'input'
},
{
name: 'height',
type: 'number',
plug: 'input'
},
{
name: 'fill',
type: {
name: 'string',
enums: ['parent', 'width', 'height', 'aspectFill', 'aspectFit']
},
plug: 'input'
},
{
name: 'pivotX',
type: 'number',
plug: 'input'
},
{
name: 'pivotY',
type: 'number',
plug: 'input'
},
{
name: 'layoutX',
type: 'number',
plug: 'input'
},
{
name: 'layoutY',
type: 'number',
plug: 'input'
},
{
name: 'depth',
type: {
name: 'number',
casts: ['boolean']
},
plug: 'input'
},
{
name: 'rotationX',
type: 'number',
plug: 'input'
},
{
name: 'rotationY',
type: 'number',
plug: 'input'
},
{
name: 'rotationZ',
type: 'number',
plug: 'input'
},
{
name: 'touchPropagation',
type: {
name: 'boolean',
casts: ['number']
},
plug: 'input'
},
{
name: 'layout',
type: {
name: 'string',
enums: ['stackVertical', 'stackHorizontal', 'flowVertical', 'flowHorizontal', 'none']
},
plug: 'input'
},
{
name: 'clip',
type: 'boolean',
plug: 'input'
},
{
group: 'test',
name: 'screenX',
type: 'number',
plug: 'output'
},
{
group: 'test',
name: 'screenY',
type: 'number',
plug: 'output'
},
{
name: 'width',
type: 'number',
plug: 'output'
},
{
name: 'height',
type: 'number',
plug: 'output'
},
{
name: 'this',
type: 'reference',
plug: 'output'
},
{
name: 'clipOut',
type: 'boolean',
plug: 'output'
}
]
},
{
name: 'image',
allowAsChild: true,
allowAsExclusiveRootOnly: true,
category: 'visuals',
ports: [
{
name: 'x',
type: {
name: 'number'
},
plug: 'input'
},
{
name: 'y',
type: {
name: 'number'
},
plug: 'input'
},
{
name: 'opacity',
type: 'number',
plug: 'input'
},
{
name: 'alignX',
type: {
name: 'string',
enums: ['left', 'center', 'right']
},
plug: 'input'
},
{
name: 'alignY',
type: {
name: 'string',
enums: ['top', 'center', 'bottom']
},
plug: 'input'
},
{
name: 'scaleX',
type: 'number',
plug: 'input'
},
{
name: 'scaleY',
type: 'number',
plug: 'input'
},
{
name: 'width',
type: 'number',
plug: 'input'
},
{
name: 'height',
type: 'number',
plug: 'input'
},
{
name: 'fill',
type: {
name: 'string',
enums: ['parent', 'width', 'height', 'aspectFill', 'aspectFit']
},
plug: 'input'
},
{
name: 'pivotX',
type: 'number',
plug: 'input'
},
{
name: 'pivotY',
type: 'number',
plug: 'input'
},
{
name: 'layoutX',
type: 'number',
plug: 'input'
},
{
name: 'layoutY',
type: 'number',
plug: 'input'
},
{
name: 'depth',
type: {
name: 'number',
casts: ['boolean']
},
plug: 'input'
},
{
name: 'rotationX',
type: 'number',
plug: 'input'
},
{
name: 'rotationY',
type: 'number',
plug: 'input'
},
{
name: 'rotationZ',
type: 'number',
plug: 'input'
},
{
name: 'touchPropagation',
type: {
name: 'boolean',
casts: ['number']
},
plug: 'input'
},
{
name: 'blendMode',
type: {
name: 'string',
enums: ['normal', 'solid', 'additive', 'multiply']
},
plug: 'input'
},
{
name: 'color',
type: 'color',
plug: 'input'
},
{
name: 'image',
type: {
name: 'string'
},
allowEditOnly: true,
plug: 'input/output'
},
{
name: 'image2',
type: {
name: 'image'
},
plug: 'input'
},
{
name: 'font',
type: {
name: 'font'
},
plug: 'input'
},
{
name: 'css',
type: {
name: 'string',
codeeditor: 'css'
},
allowEditOnly: true,
plug: 'input'
},
{
name: 'shader',
type: 'shader',
plug: 'input'
},
{
group: 'gruppen',
name: 'screenX',
type: 'number',
plug: 'output'
},
{
name: 'screenY',
type: 'number',
plug: 'output'
},
{
name: 'width',
type: 'number',
plug: 'output'
},
{
name: 'height',
type: 'number',
plug: 'output'
},
{
name: 'this',
type: 'reference',
plug: 'output'
}
]
},
{
name: 'animation'
},
{
name: 'scaleModifier',
category: 'modifiers'
},
{
name: 'nodeWithNumberedPorts',
dynamicports: [
{
name: 'numbered/basic-number',
prefix: 'my number',
displayPrefix: 'My number',
port: {
group: 'My group'
}
}
]
},
{
name: 'nodeWithNumberedPortsAndSelectors',
dynamicports: [
{
name: 'numbered/basic-number',
prefix: 'my number',
displayPrefix: 'My number',
port: {
group: 'My group'
},
selectors: [
{
name: 'startAt',
displayName: 'Start at',
group: 'My selectors'
}
]
}
]
},
{
name: 'javascript expression',
usePortAsLabel: 'expression',
dynamicports: [
{
name: 'regexp/expression-js',
port: 'expression'
}
],
ports: [
{
name: 'expression',
type: {
name: 'string',
multiline: true
},
plug: 'input'
},
{
name: 'result',
type: '=',
plug: 'output'
}
]
},
{
name: 'Event Sender',
ports: [
{
name: 'channel',
type: 'string',
plug: 'input'
}
]
},
{
name: 'Event Receiver',
dynamicports: [
{
name: 'portchannel/event-sender-channel',
channelPort: {
name: 'channel',
displayName: 'Channel',
plug: 'input'
},
port: {
type: '*',
plug: 'output'
}
}
]
},
{
name: 'Anim',
dynamicports: [
{
name: 'conditionalports/basic',
condition: 'type = typeA',
ports: [
{
name: 'from',
plug: 'input',
type: 'number'
}
]
},
{
name: 'conditionalports/basic',
condition: 'type != typeA',
ports: [
{
name: 'to',
plug: 'input',
type: 'number'
}
]
}
],
ports: [
{
name: 'type',
type: 'string',
plug: 'input'
}
]
},
{
name: 'ExpandPorts',
dynamicports: [
{
name: 'expand/basic',
indexStep: 100,
template: [
{
name: '{{portname}}.A',
plug: 'input',
type: 'number',
index: 1
}
]
},
{
name: 'expand/basic',
indexStep: 100,
condition: "'{{portname}}.A' = test OR '{{portname}}.A' NOT SET",
template: [
{
name: '{{portname}}.B',
plug: 'input',
type: 'number',
index: 2
}
]
}
]
}
]
};
}
module.exports = createTestNodeLibrary();

View File

@@ -0,0 +1,92 @@
const { PropertyEditor } = require('@noodl-views/panels/propertyeditor/propertyeditor');
const { NodeGraphNodeRename } = require('@noodl-views/panels/propertyeditor');
const { ProjectModel } = require('@noodl-models/projectmodel');
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
const { UndoQueue } = require('@noodl-models/undo-queue-model');
describe('Property editor panel unit tests', function () {
var pe, n, c;
beforeEach(() => {
ProjectModel.instance = ProjectModel.fromJSON(project);
NodeLibrary.instance.registerModule(ProjectModel.instance);
c = ProjectModel.instance.getComponentWithName('Root');
n = c.graph.findNodeWithId('A');
pe = new PropertyEditor({
model: n
});
pe.render();
});
afterEach((done) => {
//some tests schedule renders with a setTimeout
//so schedule one ourselves before we clean up so the console
//isn't filled with errors
setTimeout(() => {
ProjectModel.instance = undefined;
done();
}, 1);
});
it('can delete node and undo', function () {
pe.performDelete();
expect(c.graph.findNodeWithId('A')).toBe(undefined);
expect(c.graph.connections.length).toBe(0);
UndoQueue.instance.undo();
expect(c.graph.findNodeWithId('A')).not.toBe(undefined);
expect(c.graph.connections.length).toBe(1);
});
it('can rename and undo', function () {
NodeGraphNodeRename(n, 'test');
expect(c.graph.findNodeWithId('A').label).toBe('test');
UndoQueue.instance.undo();
expect(c.graph.findNodeWithId('A').label).toBe('group');
});
it('can edit parameter and undo', function () {
pe.portsView.setParameter('alpha', 0.5);
expect(c.graph.findNodeWithId('A').parameters['alpha']).toBe(0.5);
UndoQueue.instance.undo();
expect(c.graph.findNodeWithId('A').parameters['alpha']).toBe(undefined);
});
var project = {
components: [
{
name: 'Root',
graph: {
roots: [
{
id: 'A',
type: 'group'
},
{
id: 'B',
type: 'group'
}
],
connections: [
{
fromId: 'A',
toId: 'B',
fromProperty: 'x',
toProperty: 'y'
}
]
}
}
]
};
});

View File

@@ -0,0 +1,234 @@
const { ProjectModel } = require('@noodl-models/projectmodel');
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
const { ComponentModel } = require('@noodl-models/componentmodel');
describe('Type change propagation', function () {
var p;
it('can load project', function () {
p = ProjectModel.fromJSON(project1);
NodeLibrary.instance.registerModule(p);
expect(p).not.toBe(undefined);
});
xit('can remove connections and type will propagate', function () {
// Remove connections
var comp2 = p.getComponentWithName('/comp2');
comp2.graph.removeConnection(comp2.graph.connections[0]);
comp2.graph.removeConnection(comp2.graph.connections[0]);
// Component types should now have propagated and been set to undefined
var comp1 = p.getComponentWithName('/comp1');
expect(NodeLibrary.nameForPortType(comp1.findPortWithName('in1').type)).toBe(undefined);
expect(NodeLibrary.nameForPortType(comp1.findPortWithName('out2').type)).toBe(undefined);
});
xit('can reconnect and type will propagate', function () {
// Add connections
var comp2 = p.getComponentWithName('/comp2');
var comp1 = p.getComponentWithName('/comp1');
comp2.graph.addConnection({
fromId: 'a05abb49-625e-8bd6-7183-7ea07a46e1d4',
fromProperty: 'screenX',
toId: '6783bee9-1225-5840-354f-348a3d270b6d',
toProperty: 'out2'
});
comp2.graph.addConnection({
fromId: 'b1a816da-5023-18a2-59ba-9c58be5cd073',
fromProperty: 'in1',
toId: 'a05abb49-625e-8bd6-7183-7ea07a46e1d4',
toProperty: 'image'
});
// The types should have propagetd to comp1
expect(NodeLibrary.nameForPortType(comp1.findPortWithName('in1').type)).toBe('string');
expect(NodeLibrary.nameForPortType(comp1.findPortWithName('out2').type)).toBe('number');
});
xit('removing a type results in undefined type', function () {
var n1 = p.getComponentWithName('/comp').graph.findNodeWithId('A');
p.getComponentWithName('/comp').graph.evaluateHealth();
expect(n1.getHealth().healthy).toBe(false);
expect(n1.type.localName).toBe('notfound');
expect(n1.type.fullName).toBe('/notfound');
p.addComponent(
ComponentModel.fromJSON({
name: '/notfound',
graph: {}
})
);
p.getComponentWithName('/comp').graph.evaluateHealth();
expect(n1.getHealth().healthy).toBe(true); // Node type should now be found
});
xit('can remove type and create new one with new name', function () {
var n1 = p.getComponentWithName('/comp').graph.findNodeWithId('A');
var comp = p.getComponentWithName('/notfound');
p.removeComponent(comp);
p.getComponentWithName('/comp').graph.evaluateHealth();
expect(n1.getHealth().healthy).toBe(false); // Health should be bad again
var comp2 = ComponentModel.fromJSON({
name: '/notfound',
graph: {}
});
p.addComponent(comp2);
p.getComponentWithName('/comp').graph.evaluateHealth();
expect(n1.getHealth().healthy).toBe(true); // The new type should be resolved
expect(n1.type).toBe(comp2);
expect(n1.toJSON().type).toBe('/notfound');
});
xit('can rename components, type is changed', function () {
var n1 = p.getComponentWithName('/comp').graph.findNodeWithId('A');
// rename the type
p.renameComponentWithName('/notfound', '/notfound2');
expect(n1.getHealth().healthy).toBe(true); // The new type should be resolved
expect(n1.typename).toBe('/notfound2');
});
var project1 = {
components: [
{
name: '/comp',
graph: {
roots: [
{
id: 'A',
type: '/notfound'
}
]
}
},
{
name: '/comp1',
graph: {
connections: [
{
fromId: '7f6b3382-b5c9-ae85-3614-3a3d0b31e4ce',
fromProperty: 'out2',
toId: 'd72cfb77-8515-8612-efed-c92e3f533b8b',
toProperty: 'out2'
},
{
fromId: '15a67805-e2ee-1fa4-b86a-4efc04e7028d',
fromProperty: 'in1',
toId: '7f6b3382-b5c9-ae85-3614-3a3d0b31e4ce',
toProperty: 'in1'
}
],
roots: [
{
id: 'd72cfb77-8515-8612-efed-c92e3f533b8b',
type: 'Component Outputs',
x: 274,
y: 185,
parameters: {},
children: [],
ports: [
{
name: 'out2',
type: '*',
plug: 'input'
}
]
},
{
id: '7f6b3382-b5c9-ae85-3614-3a3d0b31e4ce',
type: '/comp2',
x: 530,
y: 185,
parameters: {},
children: []
},
{
id: '15a67805-e2ee-1fa4-b86a-4efc04e7028d',
type: 'Component Inputs',
x: 792,
y: 186,
parameters: {},
children: [],
ports: [
{
name: 'in1',
type: '*',
plug: 'output'
}
]
}
]
}
},
{
name: '/comp2',
visual: true,
visualRootId: 'a05abb49-625e-8bd6-7183-7ea07a46e1d4',
canHaveVisualChildren: false,
graph: {
connections: [
{
fromId: 'a05abb49-625e-8bd6-7183-7ea07a46e1d4',
fromProperty: 'this',
toId: '6783bee9-1225-5840-354f-348a3d270b6d',
toProperty: 'out2'
},
{
fromId: 'b1a816da-5023-18a2-59ba-9c58be5cd073',
fromProperty: 'in1',
toId: 'a05abb49-625e-8bd6-7183-7ea07a46e1d4',
toProperty: 'x'
}
],
roots: [
{
id: '6783bee9-1225-5840-354f-348a3d270b6d',
type: 'Component Outputs',
x: 686,
y: 150,
parameters: {},
children: [],
ports: [
{
name: 'out2',
type: '*',
plug: 'input'
}
]
},
{
id: 'a05abb49-625e-8bd6-7183-7ea07a46e1d4',
type: 'image',
x: 426,
y: 147,
parameters: {},
children: []
},
{
id: 'b1a816da-5023-18a2-59ba-9c58be5cd073',
type: 'Component Inputs',
x: 169,
y: 148,
parameters: {},
children: [],
ports: [
{
name: 'in1',
type: '*',
plug: 'output'
}
]
}
]
}
}
],
name: 'proj1'
};
});

View File

@@ -0,0 +1,32 @@
const WarningsModel = require('@noodl-models/warningsmodel').WarningsModel;
describe('Warnings model', function () {
beforeEach(() => {
//since warningsmodel is global there can be warnings left over from other tests, make sure to clear them
WarningsModel.instance.clearAllWarnings();
});
it('can remove warnings using a callback for matching', function () {
WarningsModel.instance.setWarning(
{
key: 'test-warning-remove'
},
{
message: 'test warning'
}
);
WarningsModel.instance.setWarning(
{
key: 'test-warning'
},
{
message: 'test warning'
}
);
expect(WarningsModel.instance.getTotalNumberOfWarnings()).toBe(2);
WarningsModel.instance.clearWarningsForRefMatching((ref) => ref.key.includes('remove'));
expect(WarningsModel.instance.getTotalNumberOfWarnings()).toBe(1);
});
});

View File

@@ -0,0 +1,80 @@
var FileSystem = require('@noodl-utils/filesystem'),
Utils = require('@noodl-utils/utils'),
Process = require('process'),
path = require('path');
const remote = require('@electron/remote');
const App = remote.app;
// TODO: Skipped because the folder contained package.json, which was picked up by lerna.
xdescribe('File system', function () {
/* it("can handle component rename",function() {
var base = JSON.parse( fs.readFileSync(Process.cwd() + '/tests/testfs/merge-tests/base-merge-project-Tue--19-Jan-2021-11-26-44-GMT.json') );
var ours = JSON.parse( fs.readFileSync(Process.cwd() + '/tests/testfs/merge-tests/ours-merge-project-Tue--19-Jan-2021-11-26-44-GMT.json') );
var remote = JSON.parse( fs.readFileSync(Process.cwd() + '/tests/testfs/merge-tests/remote-merge-project-Tue--19-Jan-2021-11-26-44-GMT.json') );
var res = ProjectMerger.mergeProject(base, ours, remote);
expect(res.components[3].name).toBe("/UI Components/UI Elements/Rider Section - List Item");
})
return;*/
it('can sync dirs', function () {
const tempDir = App.getPath('temp') + '/noodlunittests-filesystem-' + Utils.guid() + '/';
FileSystem.instance.makeDirectorySync(tempDir);
FileSystem.instance.copyRecursiveSync(Process.cwd() + '/tests/testfs/fs_sync_dir_tests/dst1', tempDir + '/dst1');
FileSystem.instance.syncDirsRecursiveSync(
Process.cwd() + '/tests/testfs/fs_sync_dir_tests/src1',
tempDir + '/dst1'
);
expect(fs.existsSync(tempDir + '/dst1/popout-will-be-removed.svg')).toBe(false);
expect(fs.existsSync(tempDir + '/dst1/should-be-removed/test.js')).toBe(false);
expect(fs.existsSync(tempDir + '/dst1/project.json')).toBe(true);
expect(fs.existsSync(tempDir + '/dst1/loginsplash.jpg')).toBe(true);
expect(fs.existsSync(tempDir + '/dst1/test.js')).toBe(true);
expect(fs.existsSync(tempDir + '/dst1/test/ajax-loader.gif')).toBe(true);
expect(fs.existsSync(tempDir + '/dst1/one/delete-me/Roboto-Black.ttf')).toBe(false);
expect(fs.existsSync(tempDir + '/dst1/one/two/loginsplash2.jpg')).toBe(true);
});
it('can remove dirs without a slash ending', function (done) {
const tempDir = App.getPath('temp') + '/noodlunittests-filesystem-' + Utils.guid();
FileSystem.instance.makeDirectorySync(tempDir);
FileSystem.instance.copyRecursiveSync(Process.cwd() + '/tests/testfs/fs_sync_dir_tests/dst1', tempDir);
FileSystem.instance.removeDirectoryRecursive(tempDir, () => {
expect(fs.existsSync(tempDir)).toBe(false);
done();
});
});
it('can remove dirs with a slash ending', function (done) {
const tempDir = App.getPath('temp') + '/noodlunittests-filesystem-' + Utils.guid() + '/';
FileSystem.instance.makeDirectorySync(tempDir);
FileSystem.instance.copyRecursiveSync(Process.cwd() + '/tests/testfs/fs_sync_dir_tests/dst1', tempDir);
FileSystem.instance.removeDirectoryRecursive(tempDir, () => {
expect(fs.existsSync(tempDir)).toBe(false);
done();
});
});
it('can copy dirs and ignore specific files', function () {
const tempDir = App.getPath('temp') + '/noodlunittests-filesystem-' + Utils.guid() + '/';
FileSystem.instance.makeDirectorySync(tempDir);
FileSystem.instance.copyRecursiveSync(Process.cwd() + '/tests/testfs/fs_sync_dir_tests/src1', tempDir, {
filter(src) {
return !src.includes(path.sep + 'test' + path.sep);
}
});
expect(fs.existsSync(tempDir + 'test/ajax-loader.gif')).toBe(false);
expect(fs.existsSync(tempDir + 'loginsplash.jpg')).toBe(true);
expect(fs.existsSync(tempDir + 'project.json')).toBe(true);
expect(fs.existsSync(tempDir + 'test.js')).toBe(true);
});
});

View File

@@ -0,0 +1 @@
export * from './filesystem';

View File

@@ -0,0 +1,4 @@
// export * from './projectcloudsync'; //this is the old version control system
export * from './projectimport';
export * from './projectmodel';
export * from './projectvalidator';

View File

@@ -0,0 +1,358 @@
const ProjectImporter = require('@noodl-utils/projectimporter');
const FileSystem = require('@noodl-utils/filesystem');
const { ProjectModel } = require('@noodl-models/projectmodel');
const Utils = require('@noodl-utils/utils');
const Process = require('process');
const ncp = require('ncp').ncp;
const fs = require('fs');
const { projectFromDirectory } = require('@noodl-models/projectmodel.editor');
const remote = require('@electron/remote');
const App = remote.app;
describe('Project import and export unit tests', function () {
function expectFilesToExist(direntry, paths, callback) {
var filesToCheck = paths.length;
var success = true;
function done() {
filesToCheck--;
if (filesToCheck === 0) callback(success);
}
for (var i in paths) {
FileSystem.instance.fileExists(direntry + '/' + paths[i], function (exists) {
if (!exists) success = false;
done();
});
}
}
it('can import project with styles and variants', function (done) {
ProjectImporter.instance.listComponentsAndDependencies(
Process.cwd() + '/tests/testfs/import_proj5',
function (imports) {
expect(imports.styles.colors).toEqual([
{
name: 'Primary'
},
{
name: 'Light Gray'
},
{
name: 'Dark Gray'
},
{
name: 'Primary Dark'
},
{
name: 'Dark'
},
{
name: 'Primary Light'
}
]);
expect(imports.styles.text).toEqual([
{
name: 'Body Text',
fileDependencies: ['fonts/Roboto/Roboto-Regular.ttf']
},
{
name: 'Button Label',
fileDependencies: ['fonts/Roboto/Roboto-Regular.ttf']
},
{
name: 'Label Text',
fileDependencies: ['fonts/Roboto/Roboto-Regular.ttf']
}
]);
expect(imports.variants).toEqual([
{
name: 'Basic',
typename: 'net.noodl.controls.button',
fileDependencies: ['fonts/Roboto/Roboto-Medium.ttf'],
styleDependencies: {
colors: ['Primary', 'Primary Light', 'Primary Dark', 'Light Gray'],
text: ['Button Label']
}
},
{
name: 'Search Field',
typename: 'net.noodl.controls.textinput',
fileDependencies: ['fonts/Roboto/Roboto-Medium.ttf'],
styleDependencies: {
colors: ['Light Gray', 'Dark', 'Primary'],
text: ['Body Text', 'Label Text']
}
}
]);
done();
}
);
});
it('can check for collissions (with styles and variants)', function (done) {
projectFromDirectory(Process.cwd() + '/tests/testfs/import_proj5', function (project) {
ProjectModel.instance = project;
// Now check for collistions with project to import
ProjectImporter.instance.listComponentsAndDependencies(
Process.cwd() + '/tests/testfs/import_proj5',
function (imports) {
ProjectImporter.instance.checkForCollisions(imports, function (collisions) {
expect(collisions.components.length).toBe(2);
expect(collisions.modules.length).toBe(1);
expect(collisions.resources.length).toBe(13);
expect(collisions.variants.length).toBe(2);
expect(collisions.styles.colors.length).toBe(6);
expect(collisions.styles.text.length).toBe(3);
done();
});
}
);
});
});
it('can list components and dependencies', function (done) {
ProjectImporter.instance.listComponentsAndDependencies(
Process.cwd() + '/tests/testfs/import_proj1',
function (imports) {
console.log(imports);
expect(imports.components).toEqual([
{
name: '/comp1',
dependencies: [],
fileDependencies: ['assets/bear.jpg'],
styleDependencies: {
text: [],
colors: []
},
variantDependencies: []
},
{
name: '/comp2',
dependencies: [],
fileDependencies: [],
styleDependencies: {
text: [],
colors: []
},
variantDependencies: []
},
{
name: '/Main',
dependencies: ['/comp1'],
fileDependencies: ['Fontfabric - Nexa-Bold.otf'],
styleDependencies: {
text: [],
colors: []
},
variantDependencies: []
}
]);
expect(imports.styles).toEqual({
text: [],
colors: []
});
//the order of these are different on mac and windows, so sort them
imports.resources.sort((a, b) => {
return a.name.localeCompare(b.name);
});
expect(imports.resources).toEqual([
{
name: 'assets/bear.jpg'
},
{
name: 'assets/bikeyellowbuilding.jpg'
},
{
name: 'bear.jpg'
},
{
name: 'Fontfabric - Nexa-Bold.otf'
}
]);
expect(imports.variants).toEqual([]);
expect(imports.modules).toEqual([]);
done();
}
);
});
it('can check for collissions (1)', function (done) {
projectFromDirectory(Process.cwd() + '/tests/testfs/import_proj1', function (project) {
ProjectModel.instance = project;
// Now check for collistions with project to import
ProjectImporter.instance.listComponentsAndDependencies(
Process.cwd() + '/tests/testfs/import_proj2',
function (imports) {
ProjectImporter.instance.checkForCollisions(imports, function (collisions) {
expect(collisions.components.length).toBe(1);
expect(collisions.components[0].name).toBe('/Main');
expect(collisions.resources).toEqual([]);
expect(collisions.modules).toEqual([]);
expect(collisions.variants).toEqual([]);
expect(collisions.styles).toEqual({
colors: [],
text: []
});
done();
});
}
);
});
});
it('can check for collissions (2)', function (done) {
projectFromDirectory(Process.cwd() + '/tests/testfs/import_proj1', function (project) {
ProjectModel.instance = project;
// Now check for collistions with project to import
ProjectImporter.instance.listComponentsAndDependencies(
Process.cwd() + '/tests/testfs/import_proj3',
function (imports) {
ProjectImporter.instance.checkForCollisions(imports, function (collisions) {
expect(collisions.resources).toEqual([
{
name: 'Fontfabric - Nexa-Bold.otf'
},
{
name: 'assets/bear.jpg'
}
]);
expect(collisions.components).toEqual([]);
expect(collisions.modules).toEqual([]);
expect(collisions.variants).toEqual([]);
expect(collisions.styles).toEqual({
colors: [],
text: []
});
done();
});
}
);
});
});
it('can import project with files', function (done) {
var tempDir = App.getPath('temp') + '/noodlunittests-' + Utils.guid() + '/';
FileSystem.instance.makeDirectory(tempDir, function (r) {
if (r.result !== 'success') {
throw 'gaah';
}
ncp(Process.cwd() + '/tests/testfs/import_proj1', tempDir + '/import_proj1', function (err) {
if (err) {
throw err;
}
projectFromDirectory(tempDir + '/import_proj1', function (project) {
ProjectModel.instance = project;
ProjectImporter.instance.listComponentsAndDependencies(
Process.cwd() + '/tests/testfs/import_proj3',
function (imports) {
ProjectImporter.instance.import(Process.cwd() + '/tests/testfs/import_proj3', imports, function () {
expect(ProjectModel.instance.getComponentWithName('/Main2')).not.toBe(undefined);
// Check that files have been copied properly
expectFilesToExist(
tempDir + '/import_proj1',
['assets/bear.jpg', 'Fontfabric - Nexa-Bold.otf', 'newfile.jpg'],
function (success) {
expect(success).toBe(true);
done();
}
);
});
}
);
});
});
});
});
it('can import project with styles, variants and modules', function (done) {
var tempDir = App.getPath('temp') + '/noodlunittests-' + Utils.guid() + '/';
FileSystem.instance.makeDirectory(tempDir, function (r) {
if (r.result !== 'success') {
throw 'gaah';
}
ncp(Process.cwd() + '/tests/testfs/import_proj1', tempDir + '/import_proj1', function (err) {
if (err) {
throw err;
}
projectFromDirectory(tempDir + '/import_proj1', function (project) {
ProjectModel.instance = project;
ProjectImporter.instance.listComponentsAndDependencies(
Process.cwd() + '/tests/testfs/import_proj5',
function (imports) {
ProjectImporter.instance.import(Process.cwd() + '/tests/testfs/import_proj5', imports, function () {
const styles = ProjectModel.instance.getMetaData('styles');
expect(Object.keys(styles.colors).sort()).toEqual([
'Dark',
'Dark Gray',
'Light Gray',
'Primary',
'Primary Dark',
'Primary Light'
]);
expect(Object.keys(styles.text).sort()).toEqual(['Body Text', 'Button Label', 'Label Text']);
expect(
ProjectModel.instance.findVariant('Basic', {
localName: 'net.noodl.controls.button'
})
).not.toBe(undefined);
expect(
ProjectModel.instance.findVariant('Search Field', {
localName: 'net.noodl.controls.textinput'
})
).not.toBe(undefined);
expect(fs.existsSync(tempDir + '/import_proj1/noodl_modules/material-icons')).toBe(true);
done();
});
}
);
});
});
});
});
it('ignores .git', function (done) {
const path = Process.cwd() + '/tests/testfs/import_proj4/';
//add a .git folder with a file inside
FileSystem.instance.makeDirectorySync(path + '.git');
FileSystem.instance.writeFileSync(path + '.git/test', 'test');
ProjectImporter.instance.listComponentsAndDependencies(path, (imports) => {
expect(imports.components.length).toBe(1);
expect(imports.resources.length).toBe(0);
//remove the .git folder
FileSystem.instance.removeFileSync(path + '.git/test');
FileSystem.instance.removeDirectoryRecursiveSync(path + '.git');
done();
});
});
});

View File

@@ -0,0 +1,175 @@
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
const { ProjectModel } = require('@noodl-models/projectmodel');
const WarningsModel = require('@noodl-models/warningsmodel').WarningsModel;
describe('Project model tests', function () {
it('can upgrade from 0 to 1', function () {
var p = ProjectModel.fromJSON(project0);
// Project upgrader from 0 to 1 should replace = types with * types
var json = JSON.stringify(p.toJSON());
expect(json.indexOf('=')).toBe(-1);
expect(json.match(/\*/g).length).toBe(2);
});
xit('can apply patch', function () {
var p = ProjectModel.fromJSON(project1);
// Apply patch
p.applyPatch({
askPermission: false,
notifyUser: false,
nodePatches: [
{
nodeId: 'A',
typename: 'group',
version: 3,
params: {
x: 20,
alignX: null,
alignY: 'top'
}
}
]
});
expect(p.findNodeWithId('A').type instanceof NodeLibrary.BasicNodeType).toBe(true);
expect(p.findNodeWithId('A').type.name).toBe('group');
expect(p.findNodeWithId('A').version).toBe(3);
expect(p.findNodeWithId('A').getParameter('x')).toBe(20);
expect(p.findNodeWithId('A').getParameter('alignX')).toBe(undefined);
expect(p.findNodeWithId('A').getParameter('alignY')).toBe('top');
// Ask permission, should not apply patch immediately
p.applyPatch({
key: 'a',
askPermission: true,
notifyUser: false,
nodePatches: [
{
nodeId: 'A',
params: {
alignY: 'bottom'
}
}
],
dismissPatches: [
{
nodeId: 'A',
params: {
alignY: null
}
}
]
});
expect(WarningsModel.instance.warnings['/']['/']['patch-a']).not.toBe(undefined); // Warnings should be generated
expect(p.findNodeWithId('A').getParameter('alignY')).toBe('top');
// Emulate user clicking patch
WarningsModel.instance.warnings['/']['/']['patch-a'].warning.onPatch();
expect(p.findNodeWithId('A').getParameter('alignY')).toBe('bottom');
// Ask permission, should not apply patch immediately
p.applyPatch({
key: 'a',
askPermission: true,
notifyUser: false,
nodePatches: [],
dismissPatches: [
{
nodeId: 'A',
params: {
alignY: null
}
}
]
});
// Emulate user clicking dismiss
WarningsModel.instance.warnings['/']['/']['patch-a'].warning.onDismiss();
expect(p.findNodeWithId('A').getParameter('alignY')).toBe(undefined);
});
xit('can apply settings patch', function () {
var p = ProjectModel.fromJSON(project1);
p.applyPatch({
askPermission: false,
notifyUser: false,
settingsPatch: {
s1: null,
s2: 'Wohoo'
}
});
expect(p.getSettings().s1).toBe(undefined);
expect(p.getSettings().s2).toBe('Wohoo');
});
// Project that should be patched
var project1 = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
id: 'A',
type: 'image',
parameters: {
x: 10,
alignX: 'left'
}
}
]
},
settings: {
s1: 'Hello'
}
}
]
};
// Old project model that should be upgraded
var project0 = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
id: 'ES-1',
type: 'Event Sender',
ports: [
{
type: '=',
name: 'a port'
}
]
}
]
}
},
{
name: 'comp2',
graph: {
roots: [
{
id: 'ES-1',
type: 'Event Sender',
ports: [
{
type: {
name: '='
},
name: 'a port'
}
]
}
]
}
}
]
};
});

View File

@@ -0,0 +1,47 @@
var ProjectValidator = require('@noodl-utils/projectvalidator');
// Project settings
describe('Project validator', function () {
it('can validate missing components', function () {
const proj = {};
const validator = new ProjectValidator();
validator.validate(proj);
expect(validator.hasErrors()).toBe(true);
expect(validator.errors[0].msg).toBe('Project is missing name');
expect(validator.errors[1].msg).toBe('Project is missing components');
});
it('can validate dangling connections and fix', function () {
const proj = {
name: 'C',
components: [
{
name: 'A',
graph: {
roots: [],
connections: [
{
fromId: 'a',
fromProperty: 'hej',
toId: 'b',
toProperty: 'hej'
}
]
}
}
]
};
const validator = new ProjectValidator();
validator.validate(proj);
expect(validator.hasErrors()).toBe(true);
expect(validator.errors[0].msg).toBe('Dangling connection at A missing source missing target ');
validator.fix();
validator.clearErrors();
validator.validate(proj);
expect(validator.hasErrors()).toBe(false);
expect(proj.components[0].graph.connections.length).toBe(0);
});
});

View File

@@ -0,0 +1,5 @@
export * from './projectmerger-comments';
export * from './projectmerger-diff';
export * from './projectmerger-states';
export * from './projectmerger-variants';
export * from './projectmerger';

View File

@@ -0,0 +1,302 @@
var ProjectMerger = require('@noodl-utils/projectmerger');
function mergeComments(base, ours, theirs) {
const baseComponent = {
components: [
{
name: 'comp1',
graph: {
roots: [],
comments: base
}
}
]
};
const ourComponent = {
components: [
{
name: 'comp1',
graph: {
roots: [],
comments: ours
}
}
]
};
const theirComponent = {
components: [
{
name: 'comp1',
graph: {
roots: [],
comments: theirs
}
}
]
};
return ProjectMerger.mergeProject(baseComponent, ourComponent, theirComponent);
}
// Project settings
describe('Project merger - comments', function () {
it('can merge new comments', function () {
const ours = [
{
id: 'c1',
text: 'hej',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const theirs = [
{
id: 'c2',
text: 'hopp',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const res = mergeComments(undefined, ours, theirs);
expect(res.components[0].graph.comments).toEqual([
{
id: 'c1',
text: 'hej',
x: 0,
y: 0,
width: 100,
height: 100
},
{
id: 'c2',
text: 'hopp',
x: 0,
y: 0,
width: 100,
height: 100
}
]);
});
it('can merge updated comments - theirs updated', function () {
const base = [
{
id: 'c1',
text: 'hej',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const ours = [
{
id: 'c1',
text: 'hej',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const theirs = [
{
id: 'c1',
text: 'hopp',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const res = mergeComments(base, ours, theirs);
expect(res.components[0].graph.comments).toEqual([
{
id: 'c1',
text: 'hopp',
x: 0,
y: 0,
width: 100,
height: 100
}
]);
});
it('can merge updated comments - ours updated', function () {
const base = [
{
id: 'c1',
text: 'hej',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const ours = [
{
id: 'c1',
text: 'hopp',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const theirs = [
{
id: 'c1',
text: 'hej',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const res = mergeComments(base, ours, theirs);
expect(res.components[0].graph.comments).toEqual([
{
id: 'c1',
text: 'hopp',
x: 0,
y: 0,
width: 100,
height: 100
}
]);
});
it('can merge deleted comments - ours deleted', function () {
const base = [
{
id: 'c1',
text: 'hej',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const ours = [];
const theirs = [
{
id: 'c1',
text: 'hej',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const res = mergeComments(base, ours, theirs);
expect(res.components[0].graph.comments).toEqual([]);
});
it('can merge deleted comments - theirs deleted', function () {
const base = [
{
id: 'c1',
text: 'hej',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const ours = [
{
id: 'c1',
text: 'hej',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const theirs = [];
const res = mergeComments(base, ours, theirs);
expect(res.components[0].graph.comments).toEqual([]);
});
it('can merge deleted and changed comments - ours modified, theirs deleted', function () {
const base = [
{
id: 'c1',
text: 'hej',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const ours = [
{
id: 'c1',
text: 'hopp',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const theirs = [];
const res = mergeComments(base, ours, theirs);
expect(res.components[0].graph.comments).toEqual([
{
id: 'c1',
text: 'hopp',
x: 0,
y: 0,
width: 100,
height: 100
}
]);
});
it('can merge deleted and changed comments - ours deleted, theirs modified', function () {
const base = [
{
id: 'c1',
text: 'hej',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const ours = [];
const theirs = [
{
id: 'c1',
text: 'hopp',
x: 0,
y: 0,
width: 100,
height: 100
}
];
const res = mergeComments(base, ours, theirs);
expect(res.components[0].graph.comments).toEqual([
{
id: 'c1',
text: 'hopp',
x: 0,
y: 0,
width: 100,
height: 100
}
]);
});
});

View File

@@ -0,0 +1,155 @@
var { diffProject } = require('@noodl-utils/projectmerger.diff');
var fs = require('fs');
var Process = require('process');
// Project settings
describe('Project merger diff', function () {
it('can diff two identical projects without reporting any changes', function () {
const base = JSON.parse(
fs.readFileSync(
Process.cwd() + '/tests/testfs/merge-tests/move-nodes/base-merge-project-Wed--03-Feb-2021-11-07-55-GMT.json'
)
);
const diff = diffProject(base, base);
expect(diff.components.changed.length).toBe(0, 'no changed components');
expect(diff.components.created.length).toBe(0, 'no created components');
expect(diff.components.deleted.length).toBe(0, 'no deleted components');
expect(diff.components.unchanged.length).toBe(base.components.length, 'all components unchanged');
});
it('can diff with one node changes', function () {
const base = JSON.parse(
fs.readFileSync(
Process.cwd() + '/tests/testfs/merge-tests/move-nodes/base-merge-project-Wed--03-Feb-2021-11-07-55-GMT.json'
)
);
const current = JSON.parse(JSON.stringify(base));
//modify a text node
current.components[0].graph.roots[0].children[0].parameters.text = 'Hello 2';
const diff = diffProject(base, current);
expect(diff.components.changed.length).toBe(1, 'should have one changed component');
expect(diff.components.created.length).toBe(0, 'no created components');
expect(diff.components.deleted.length).toBe(0, 'no deleted components');
expect(diff.components.unchanged.length).toBe(base.components.length - 1);
});
it('diff ignores metadata', function () {
const base = JSON.parse(
fs.readFileSync(
Process.cwd() + '/tests/testfs/merge-tests/move-nodes/base-merge-project-Wed--03-Feb-2021-11-07-55-GMT.json'
)
);
const current = JSON.parse(JSON.stringify(base));
// change metadata in component
current.components[0].metadata.canvasPos.x = 50;
current.components[0].graph.roots[0].dynamicports = [
{
name: 'testy',
type: 'string'
}
];
//change metadata in node
current.components[0].graph.roots[0].metadata = {
sourceCodePorts: ['test']
};
const diff = diffProject(base, current);
expect(diff.components.changed.length).toBe(0, 'no changed components');
expect(diff.components.created.length).toBe(0, 'no created components');
expect(diff.components.deleted.length).toBe(0, 'no deleted components');
expect(diff.components.unchanged.length).toBe(base.components.length, 'all components unchanged');
});
it('can diff variants', function () {
const base = {
components: [],
variants: [
{
name: 'A',
typename: 'A',
parameters: {
p1: 10
}
},
{
name: 'A',
typename: 'B',
parameters: {
p1: 'test'
}
},
{
name: 'C',
typename: 'C',
parameters: {
p1: 10
}
}
]
};
const current = {
components: [],
variants: [
{
name: 'A',
typename: 'A',
parameters: {
p2: 20
}
},
{
name: 'B',
typename: 'A',
parameters: {
p1: 'test'
}
},
{
name: 'C',
typename: 'C',
parameters: {
p1: 10
}
}
]
};
const diff = diffProject(base, current);
expect(diff.variants.changed.length).toBe(1);
expect(diff.variants.created.length).toBe(1);
expect(diff.variants.unchanged.length).toBe(1);
});
it('can diff settings', function () {
const base = {
components: [],
settings: {
htmlTitle: 'A',
headCode: 'lolol',
someSetting: 'abc'
}
};
const current = {
components: [],
settings: {
htmlTitle: 'B',
bodyScroll: true,
someSetting: 'abc'
}
};
const diff = diffProject(base, current);
expect(diff.settings.changed.length).toBe(1);
expect(diff.settings.created.length).toBe(1);
expect(diff.settings.deleted.length).toBe(1);
expect(diff.settings.unchanged.length).toBe(1);
});
});

View File

@@ -0,0 +1,557 @@
var ProjectMerger = require('@noodl-utils/projectmerger');
var NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
var fs = require('fs');
var Process = require('process');
// Project settings
describe('Project merger (states and transitions)', function () {
it('can merge add state transitions to empty ancestor', function () {
var a = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A'
}
],
connections: []
}
}
]
};
var o = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
stateParameters: {
hover: {
p1: 'changed'
}
}
}
],
connections: []
}
}
]
};
var t = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A'
}
],
connections: []
}
}
]
};
var res = ProjectMerger.mergeProject(a, o, t);
expect(res.components[0].graph.roots[0]).toEqual({
type: '0',
id: 'A',
stateParameters: {
hover: {
p1: 'changed'
}
}
});
});
it('can merge state parameters', function () {
var a = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
parameters: {},
stateParameters: {
hover: {
p1: 'some-string',
p3: 'remove-me'
}
}
}
],
connections: []
}
}
]
};
var o = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
stateParameters: {
hover: {
p1: 'changed',
p2: 'added-string',
p3: 'remove-me'
},
pressed: {
p4: 'new-param'
}
}
}
],
connections: []
}
}
]
};
var t = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
parameters: {},
stateParameters: {
hover: {
p1: 10 // Should conflict
}
}
}
],
connections: []
}
}
]
};
var res = ProjectMerger.mergeProject(a, o, t);
expect(res.components[0].graph.roots[0]).toEqual({
type: '0',
id: 'A',
parameters: undefined,
stateParameters: {
hover: {
p1: 'changed',
p2: 'added-string',
p3: undefined
},
pressed: {
p4: 'new-param'
}
},
ports: [],
conflicts: [
{
type: 'stateParameter',
state: 'hover',
name: 'p1',
ours: 'changed',
theirs: 10
}
],
children: undefined
});
});
it('can merge add state transitions to empty ancestor', function () {
var a = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
parameters: {}
}
],
connections: []
}
}
]
};
var o = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
stateTransitions: {
hover: {
p1: {
dur: 0,
curve: 'changed'
},
p2: {
dur: 0,
curve: 'added'
}
}
}
}
],
connections: []
}
}
]
};
var t = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
parameters: {}
}
],
connections: []
}
}
]
};
var res = ProjectMerger.mergeProject(a, o, t);
expect(res.components[0].graph.roots[0]).toEqual({
type: '0',
id: 'A',
stateTransitions: {
hover: {
p1: {
dur: 0,
curve: 'changed'
},
p2: {
dur: 0,
curve: 'added'
}
}
}
});
});
it('can merge state transitions', function () {
var a = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
parameters: {},
stateTransitions: {
hover: {
p1: {
dur: 0,
curve: [1, 1, 1, 1]
},
p3: 'remove-me'
}
}
}
],
connections: []
}
}
]
};
var o = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
stateTransitions: {
hover: {
p1: {
dur: 0,
curve: 'changed'
},
p2: {
dur: 0,
curve: 'added'
},
p3: 'remove-me'
},
pressed: {
p4: {
dur: 0,
curve: [0, 1, 0, 1]
}
}
}
}
],
connections: []
}
}
]
};
var t = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
parameters: {},
stateTransitions: {
hover: {
p1: {
dur: 0,
curve: [0, 0, 0, 1]
}
}
}
}
],
connections: []
}
}
]
};
var res = ProjectMerger.mergeProject(a, o, t);
expect(res.components[0].graph.roots[0]).toEqual({
type: '0',
id: 'A',
parameters: undefined,
stateTransitions: {
hover: {
p1: {
dur: 0,
curve: 'changed'
},
p2: {
dur: 0,
curve: 'added'
},
p3: undefined
},
pressed: {
p4: {
dur: 0,
curve: [0, 1, 0, 1]
}
}
},
ports: [],
conflicts: [
{
type: 'stateTransition',
state: 'hover',
name: 'p1',
ours: {
dur: 0,
curve: 'changed'
},
theirs: {
dur: 0,
curve: [0, 0, 0, 1]
}
}
],
children: undefined
});
});
it('can merge default state transitions', function () {
var a = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
parameters: {}
}
],
connections: []
}
}
]
};
var o = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
defaultStateTransitions: {
neutral: {
dur: 100,
curve: [0, 0, 1, 1]
}
}
}
],
connections: []
}
}
]
};
var t = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
parameters: {},
defaultStateTransitions: {
neutral: {
dur: 200,
curve: [1, 1, 1, 1]
}
}
}
],
connections: []
}
}
]
};
var res = ProjectMerger.mergeProject(a, o, t);
expect(res.components[0].graph.roots[0]).toEqual({
type: '0',
id: 'A',
parameters: undefined,
defaultStateTransitions: {
neutral: {
dur: 100,
curve: [0, 0, 1, 1]
}
},
ports: [],
conflicts: [
{
type: 'defaultStateTransition',
state: 'neutral',
ours: {
dur: 100,
curve: [0, 0, 1, 1]
},
theirs: {
dur: 200,
curve: [1, 1, 1, 1]
}
}
],
children: undefined
});
});
it('can merge variant', function () {
var a = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
parameters: {}
}
],
connections: []
}
}
]
};
var o = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
variant: 'VariantA'
}
],
connections: []
}
}
]
};
var t = {
components: [
{
name: 'comp1',
graph: {
roots: [
{
type: '0',
id: 'A',
parameters: {},
variant: 'VariantB'
}
],
connections: []
}
}
]
};
var res = ProjectMerger.mergeProject(a, o, t);
expect(res.components[0].graph.roots[0]).toEqual({
type: '0',
id: 'A',
parameters: undefined,
variant: 'VariantA',
ports: [],
conflicts: [
{
type: 'variant',
ours: 'VariantA',
theirs: 'VariantB'
}
],
children: undefined
});
});
});

View File

@@ -0,0 +1,153 @@
var ProjectMerger = require('@noodl-utils/projectmerger');
var NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
var fs = require('fs');
var Process = require('process');
// Project settings
describe('Project merger (variants)', function () {
it('can merge variants (conflicts)', function () {
var a = {
components: [],
variants: [
{
typename: 'Group',
name: 'A',
parameters: {
test: 'hej'
}
}
]
};
var o = {
components: [],
variants: [
{
typename: 'Group',
name: 'A',
parameters: {
test: 'hej2'
},
stateParameters: {
hover: {
test: 'a'
}
},
stateTransitions: {
hover: {
p1: {
dur: 500,
curve: [0, 0, 0, 1]
}
}
},
defaultStateTransitions: {
hover: {
dur: 500,
curve: [0, 0, 0, 1]
}
}
}
]
};
var t = {
components: [],
variants: [
{
typename: 'Group',
name: 'A',
parameters: {
test: 'hej3'
},
stateParameters: {
hover: {
test: 'b'
}
},
stateTransitions: {
hover: {
p1: {
dur: 0,
curve: [0, 0, 0, 1]
}
}
},
defaultStateTransitions: {
hover: {
dur: 0,
curve: [0, 0, 0, 1]
}
}
}
]
};
var res = ProjectMerger.mergeProject(a, o, t);
console.log(res);
expect(res.variants[0]).toEqual({
typename: 'Group',
name: 'A',
parameters: {
test: 'hej2'
},
stateParameters: {
hover: {
test: 'a'
}
},
stateTransitions: {
hover: {
p1: {
dur: 500,
curve: [0, 0, 0, 1]
}
}
},
defaultStateTransitions: {
hover: {
dur: 500,
curve: [0, 0, 0, 1]
}
},
conflicts: [
{
type: 'parameter',
name: 'test',
ours: 'hej2',
theirs: 'hej3'
},
{
type: 'stateParameter',
state: 'hover',
name: 'test',
ours: 'a',
theirs: 'b'
},
{
type: 'stateTransition',
state: 'hover',
name: 'p1',
ours: {
dur: 500,
curve: [0, 0, 0, 1]
},
theirs: {
dur: 0,
curve: [0, 0, 0, 1]
}
},
{
type: 'defaultStateTransition',
state: 'hover',
ours: {
dur: 500,
curve: [0, 0, 0, 1]
},
theirs: {
dur: 0,
curve: [0, 0, 0, 1]
}
}
]
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
export * from './projectpatcher-events';

View File

@@ -0,0 +1,88 @@
import { applyPatches } from '@noodl-models/ProjectPatches/applypatches';
// Project settings
describe('Project patcher - events', function () {
it('can patch send event nodes', function () {
const project = {
"name": "event-patcher-test",
"components": [
{
"name": "/App",
"graph": {
"connections": [
],
"roots": [
{
"id": "4143a274-208c-debf-bd59-e399e0f8ee82",
"type": "Event Sender",
"x": -327.46819153012916,
"y": 305.4376377651178,
"parameters": {
"channelName": "achannel"
},
"ports": [
{
"name": "One",
"plug": "input",
"type": {
"name": "*",
"allowConnectionOnly": true
},
"group": "Payload",
"index": 1
},
{
"name": "Two",
"plug": "input",
"type": {
"name": "*",
"allowConnectionOnly": true
},
"group": "Payload",
"index": 2
}
],
"dynamicports": [],
"children": []
}
]
},
}
]
}
const after = {
"name": "event-patcher-test",
"components": [
{
"name": "/App",
"graph": {
"connections": [
],
"roots": [
{
"id": "4143a274-208c-debf-bd59-e399e0f8ee82",
"type": "Event Sender",
"x": -327.46819153012916,
"y": 305.4376377651178,
"parameters": {
"channelName": "achannel",
"payload": "One,Two"
},
"ports": [],
"dynamicports": [
],
"children": []
},
]
}
}
]
}
applyPatches(project)
expect(project).toEqual(after)
})
});

View File

@@ -0,0 +1,261 @@
[
{ "type": "over", "time": 1439213679751, "x": 79, "y": 332, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213679752, "x": 79, "y": 332, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213679765, "x": 135, "y": 334, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213679783, "x": 157, "y": 334, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213679810, "x": 162, "y": 334, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213679852, "x": 162, "y": 335, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213679869, "x": 159, "y": 338, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213679886, "x": 159, "y": 344, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213679925, "x": 159, "y": 354, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213679937, "x": 163, "y": 363, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213679954, "x": 173, "y": 381, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213679972, "x": 192, "y": 409, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213679989, "x": 208, "y": 426, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680005, "x": 222, "y": 431, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680022, "x": 228, "y": 432, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680039, "x": 238, "y": 434, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680056, "x": 246, "y": 434, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680074, "x": 254, "y": 433, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680092, "x": 258, "y": 432, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680109, "x": 260, "y": 432, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680126, "x": 262, "y": 431, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680144, "x": 263, "y": 431, "evt": { "button": 0 } },
{ "type": "down", "time": 1439213680172, "x": 263, "y": 431, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680180, "x": 263, "y": 430, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680198, "x": 263, "y": 427, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680228, "x": 263, "y": 419, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680230, "x": 261, "y": 403, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680248, "x": 255, "y": 378, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680265, "x": 246, "y": 338, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680281, "x": 224, "y": 278, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680299, "x": 199, "y": 219, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680316, "x": 181, "y": 179, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680337, "x": 166, "y": 150, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680349, "x": 158, "y": 130, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680366, "x": 153, "y": 118, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680570, "x": 127, "y": 62, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680572, "x": 126, "y": 60, "evt": { "button": 0 } },
{ "type": "up", "time": 1439213680588, "x": 126, "y": 60, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680589, "x": 125, "y": 59, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680668, "x": 125, "y": 60, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680685, "x": 122, "y": 69, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680702, "x": 119, "y": 79, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680719, "x": 117, "y": 88, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680737, "x": 117, "y": 96, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680754, "x": 116, "y": 99, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680772, "x": 116, "y": 100, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680807, "x": 115, "y": 101, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680837, "x": 115, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680854, "x": 115, "y": 104, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680871, "x": 112, "y": 107, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680889, "x": 111, "y": 111, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680906, "x": 110, "y": 116, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680923, "x": 110, "y": 118, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680941, "x": 110, "y": 120, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680964, "x": 110, "y": 121, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680981, "x": 109, "y": 122, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213680998, "x": 109, "y": 123, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681016, "x": 108, "y": 124, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681033, "x": 107, "y": 124, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681051, "x": 107, "y": 125, "evt": { "button": 0 } },
{ "type": "down", "time": 1439213681068, "x": 107, "y": 125, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681108, "x": 108, "y": 124, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681125, "x": 111, "y": 122, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681142, "x": 117, "y": 117, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681159, "x": 123, "y": 113, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681180, "x": 130, "y": 108, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681197, "x": 135, "y": 105, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681212, "x": 140, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681229, "x": 143, "y": 99, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681247, "x": 145, "y": 97, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681264, "x": 148, "y": 96, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681281, "x": 150, "y": 94, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681298, "x": 152, "y": 94, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681314, "x": 154, "y": 92, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681331, "x": 155, "y": 91, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681348, "x": 158, "y": 90, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681364, "x": 159, "y": 89, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681381, "x": 160, "y": 89, "evt": { "button": 0 } },
{ "type": "up", "time": 1439213681564, "x": 160, "y": 89, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681643, "x": 160, "y": 90, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681660, "x": 162, "y": 95, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681677, "x": 167, "y": 103, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681694, "x": 176, "y": 112, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681715, "x": 190, "y": 123, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681731, "x": 202, "y": 130, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681751, "x": 217, "y": 138, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681764, "x": 233, "y": 145, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681781, "x": 243, "y": 149, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681814, "x": 249, "y": 152, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681816, "x": 258, "y": 154, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681832, "x": 260, "y": 154, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681849, "x": 263, "y": 155, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681866, "x": 266, "y": 156, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681883, "x": 269, "y": 157, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681900, "x": 275, "y": 159, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681917, "x": 281, "y": 162, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681934, "x": 285, "y": 163, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681953, "x": 287, "y": 163, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213681968, "x": 288, "y": 163, "evt": { "button": 0 } },
{ "type": "down", "time": 1439213681982, "x": 288, "y": 163, "evt": { "button": 0 } },
{ "type": "up", "time": 1439213682060, "x": 288, "y": 163, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682141, "x": 268, "y": 184, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682159, "x": 268, "y": 185, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682459, "x": 269, "y": 185, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682476, "x": 270, "y": 185, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682598, "x": 259, "y": 187, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682600, "x": 246, "y": 186, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682637, "x": 221, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682651, "x": 214, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682669, "x": 208, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682686, "x": 201, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682703, "x": 192, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682720, "x": 185, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682737, "x": 175, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682755, "x": 168, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682772, "x": 165, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682789, "x": 164, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682806, "x": 162, "y": 181, "evt": { "button": 0 } },
{ "type": "down", "time": 1439213682878, "x": 162, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682879, "x": 163, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682893, "x": 168, "y": 179, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682912, "x": 181, "y": 175, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682928, "x": 206, "y": 171, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682947, "x": 235, "y": 168, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682963, "x": 264, "y": 161, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682978, "x": 291, "y": 157, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213682998, "x": 313, "y": 150, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683014, "x": 326, "y": 142, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683033, "x": 337, "y": 134, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683053, "x": 347, "y": 127, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683066, "x": 356, "y": 120, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683081, "x": 363, "y": 114, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683098, "x": 370, "y": 109, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683114, "x": 373, "y": 106, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683131, "x": 375, "y": 104, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683148, "x": 376, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683388, "x": 375, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683416, "x": 373, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683429, "x": 372, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683448, "x": 371, "y": 102, "evt": { "button": 0 } },
{ "type": "up", "time": 1439213683700, "x": 371, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683796, "x": 371, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683826, "x": 370, "y": 104, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683829, "x": 367, "y": 106, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683850, "x": 360, "y": 114, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683865, "x": 350, "y": 123, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683885, "x": 333, "y": 138, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683898, "x": 312, "y": 157, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683915, "x": 286, "y": 182, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213683933, "x": 261, "y": 206, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684058, "x": 205, "y": 256, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684069, "x": 200, "y": 257, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684086, "x": 198, "y": 258, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684103, "x": 196, "y": 260, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684120, "x": 194, "y": 262, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684145, "x": 192, "y": 262, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684155, "x": 191, "y": 263, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684172, "x": 188, "y": 263, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684189, "x": 188, "y": 264, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684206, "x": 187, "y": 265, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684228, "x": 186, "y": 265, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684284, "x": 186, "y": 266, "evt": { "button": 0 } },
{ "type": "down", "time": 1439213684564, "x": 186, "y": 266, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684604, "x": 186, "y": 265, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684621, "x": 186, "y": 262, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684638, "x": 186, "y": 257, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684655, "x": 186, "y": 252, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684680, "x": 186, "y": 246, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684689, "x": 186, "y": 242, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684707, "x": 186, "y": 239, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684724, "x": 186, "y": 236, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684740, "x": 186, "y": 234, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684758, "x": 186, "y": 232, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684774, "x": 186, "y": 230, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684791, "x": 186, "y": 229, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684812, "x": 186, "y": 228, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684829, "x": 186, "y": 227, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684845, "x": 186, "y": 226, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684863, "x": 186, "y": 224, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684879, "x": 186, "y": 223, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684895, "x": 186, "y": 222, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213684924, "x": 186, "y": 221, "evt": { "button": 0 } },
{ "type": "up", "time": 1439213685004, "x": 186, "y": 221, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685116, "x": 187, "y": 221, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685133, "x": 188, "y": 221, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685150, "x": 192, "y": 221, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685166, "x": 205, "y": 217, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685183, "x": 216, "y": 212, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685201, "x": 230, "y": 208, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685218, "x": 242, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685235, "x": 251, "y": 198, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685253, "x": 257, "y": 196, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685270, "x": 261, "y": 194, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685287, "x": 263, "y": 193, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685310, "x": 265, "y": 192, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685325, "x": 266, "y": 192, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685342, "x": 266, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685365, "x": 267, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685381, "x": 270, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685399, "x": 276, "y": 188, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685415, "x": 279, "y": 188, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685432, "x": 280, "y": 187, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685450, "x": 281, "y": 186, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213685524, "x": 282, "y": 186, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686036, "x": 282, "y": 185, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686053, "x": 282, "y": 184, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686071, "x": 282, "y": 183, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686088, "x": 282, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686105, "x": 282, "y": 179, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686122, "x": 283, "y": 177, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686139, "x": 283, "y": 174, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686180, "x": 281, "y": 169, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686190, "x": 280, "y": 167, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686221, "x": 280, "y": 166, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686252, "x": 280, "y": 165, "evt": { "button": 0 } },
{ "type": "down", "time": 1439213686500, "x": 280, "y": 165, "evt": { "button": 0 } },
{ "type": "up", "time": 1439213686596, "x": 280, "y": 165, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686788, "x": 280, "y": 164, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686806, "x": 280, "y": 161, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686822, "x": 280, "y": 152, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686839, "x": 277, "y": 146, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686857, "x": 276, "y": 140, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686874, "x": 275, "y": 132, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686890, "x": 275, "y": 127, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686908, "x": 275, "y": 124, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686924, "x": 275, "y": 122, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686941, "x": 275, "y": 119, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213686965, "x": 275, "y": 118, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687004, "x": 275, "y": 117, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687043, "x": 275, "y": 116, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687099, "x": 275, "y": 115, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687118, "x": 275, "y": 114, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687134, "x": 275, "y": 112, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687151, "x": 275, "y": 111, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687169, "x": 276, "y": 111, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687628, "x": 276, "y": 110, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687645, "x": 276, "y": 109, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687665, "x": 276, "y": 108, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687681, "x": 276, "y": 107, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687698, "x": 276, "y": 106, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687715, "x": 276, "y": 105, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687735, "x": 276, "y": 103, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687751, "x": 277, "y": 101, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687766, "x": 278, "y": 100, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687784, "x": 278, "y": 98, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687802, "x": 278, "y": 97, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213687819, "x": 278, "y": 95, "evt": { "button": 0 } },
{ "type": "down", "time": 1439213688116, "x": 278, "y": 95, "evt": { "button": 0 } },
{ "type": "up", "time": 1439213688227, "x": 278, "y": 95, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213688768, "x": 277, "y": 95, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213688782, "x": 267, "y": 99, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213688800, "x": 246, "y": 105, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213688816, "x": 223, "y": 109, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213688832, "x": 198, "y": 116, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213688848, "x": 167, "y": 124, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213688866, "x": 133, "y": 133, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213688905, "x": 55, "y": 157, "evt": { "button": 0 } },
{ "type": "move", "time": 1439213688917, "x": 15, "y": 167, "evt": { "button": 0 } },
{ "type": "out", "time": 1439213688935, "x": -17, "y": 175, "evt": { "button": 0 } }
]

View File

@@ -0,0 +1,223 @@
[
{ "type": "over", "time": 1454322937752, "x": 67, "y": 408, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322937754, "x": 67, "y": 408, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322937767, "x": 152, "y": 420, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322937784, "x": 205, "y": 431, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322937801, "x": 223, "y": 437, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322937818, "x": 224, "y": 439, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322937836, "x": 225, "y": 440, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938035, "x": 225, "y": 440, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938052, "x": 226, "y": 441, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938068, "x": 227, "y": 441, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938085, "x": 235, "y": 442, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938102, "x": 255, "y": 442, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938120, "x": 266, "y": 439, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938137, "x": 268, "y": 436, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938154, "x": 268, "y": 433, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938171, "x": 268, "y": 429, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938189, "x": 267, "y": 426, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938206, "x": 265, "y": 423, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938223, "x": 264, "y": 420, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938240, "x": 261, "y": 418, "evt": { "button": 0 } },
{ "type": "down", "time": 1454322938243, "x": 261, "y": 418, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938258, "x": 261, "y": 417, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938275, "x": 260, "y": 416, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938292, "x": 259, "y": 414, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938314, "x": 255, "y": 411, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938327, "x": 248, "y": 402, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938343, "x": 235, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938361, "x": 220, "y": 375, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938378, "x": 204, "y": 360, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938396, "x": 186, "y": 345, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938413, "x": 172, "y": 331, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938431, "x": 160, "y": 320, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938449, "x": 150, "y": 309, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938464, "x": 140, "y": 299, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938482, "x": 130, "y": 288, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938500, "x": 118, "y": 277, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938517, "x": 109, "y": 267, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938533, "x": 98, "y": 258, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938550, "x": 90, "y": 250, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938567, "x": 81, "y": 242, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938584, "x": 74, "y": 236, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938600, "x": 67, "y": 228, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938617, "x": 60, "y": 220, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938634, "x": 54, "y": 215, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938651, "x": 50, "y": 211, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938668, "x": 46, "y": 207, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938685, "x": 45, "y": 205, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938702, "x": 43, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938719, "x": 42, "y": 202, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938739, "x": 42, "y": 201, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938756, "x": 41, "y": 201, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938774, "x": 41, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938791, "x": 40, "y": 199, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938809, "x": 39, "y": 199, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938826, "x": 37, "y": 197, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322938844, "x": 36, "y": 195, "evt": { "button": 0 } },
{ "type": "up", "time": 1454322939139, "x": 36, "y": 195, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322939162, "x": 35, "y": 195, "evt": { "button": 0 } },
{ "type": "copy", "time": 1454322939962 },
{ "type": "paste", "time": 1454322940457 },
{ "type": "move", "time": 1454322941043, "x": 35, "y": 196, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941059, "x": 35, "y": 197, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941077, "x": 36, "y": 198, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941093, "x": 36, "y": 199, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941111, "x": 37, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941128, "x": 39, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941144, "x": 41, "y": 201, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941162, "x": 43, "y": 202, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941179, "x": 46, "y": 204, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941196, "x": 51, "y": 206, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941213, "x": 57, "y": 207, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941230, "x": 65, "y": 210, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941247, "x": 74, "y": 212, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941264, "x": 82, "y": 214, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941284, "x": 91, "y": 217, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941298, "x": 102, "y": 221, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941316, "x": 109, "y": 222, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941333, "x": 115, "y": 222, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941350, "x": 120, "y": 222, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941367, "x": 123, "y": 223, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941384, "x": 126, "y": 224, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941401, "x": 130, "y": 225, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941418, "x": 132, "y": 225, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941436, "x": 134, "y": 225, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941454, "x": 135, "y": 225, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941471, "x": 136, "y": 225, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941500, "x": 137, "y": 225, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941516, "x": 138, "y": 225, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941533, "x": 139, "y": 225, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941552, "x": 140, "y": 225, "evt": { "button": 0 } },
{ "type": "down", "time": 1454322941739, "x": 140, "y": 225, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941835, "x": 140, "y": 226, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941852, "x": 140, "y": 227, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941869, "x": 140, "y": 228, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941889, "x": 140, "y": 231, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941905, "x": 140, "y": 233, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941922, "x": 140, "y": 236, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941939, "x": 140, "y": 242, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941957, "x": 140, "y": 246, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941974, "x": 140, "y": 252, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322941991, "x": 140, "y": 259, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942008, "x": 140, "y": 267, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942027, "x": 139, "y": 275, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942042, "x": 139, "y": 281, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942060, "x": 139, "y": 287, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942076, "x": 139, "y": 295, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942092, "x": 139, "y": 302, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942109, "x": 139, "y": 309, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942125, "x": 139, "y": 315, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942143, "x": 139, "y": 323, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942160, "x": 139, "y": 328, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942177, "x": 139, "y": 333, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942195, "x": 139, "y": 338, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942211, "x": 139, "y": 342, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942228, "x": 139, "y": 345, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942245, "x": 139, "y": 349, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942262, "x": 139, "y": 352, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942279, "x": 139, "y": 354, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942296, "x": 139, "y": 356, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942314, "x": 139, "y": 361, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942330, "x": 141, "y": 367, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942347, "x": 143, "y": 371, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942364, "x": 143, "y": 375, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942381, "x": 146, "y": 378, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942398, "x": 148, "y": 380, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942416, "x": 149, "y": 383, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942433, "x": 150, "y": 384, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942451, "x": 150, "y": 385, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942468, "x": 151, "y": 385, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942485, "x": 151, "y": 386, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942502, "x": 152, "y": 386, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942520, "x": 152, "y": 387, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942539, "x": 153, "y": 387, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942560, "x": 153, "y": 388, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942594, "x": 153, "y": 388, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942620, "x": 153, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942637, "x": 154, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322942738, "x": 155, "y": 389, "evt": { "button": 0 } },
{ "type": "up", "time": 1454322943149, "x": 155, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322943314, "x": 156, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322943332, "x": 159, "y": 388, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322943349, "x": 169, "y": 387, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322943366, "x": 187, "y": 382, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322943383, "x": 211, "y": 377, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322943401, "x": 233, "y": 374, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322943419, "x": 256, "y": 367, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322943435, "x": 270, "y": 362, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322943452, "x": 278, "y": 358, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322943469, "x": 281, "y": 357, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322943487, "x": 281, "y": 356, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322943516, "x": 281, "y": 355, "evt": { "button": 0 } },
{ "type": "down", "time": 1454322944259, "x": 281, "y": 355, "evt": { "button": 0 } },
{ "type": "up", "time": 1454322944371, "x": 281, "y": 355, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944507, "x": 281, "y": 357, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944524, "x": 279, "y": 364, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944540, "x": 277, "y": 375, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944557, "x": 274, "y": 392, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944574, "x": 271, "y": 410, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944591, "x": 271, "y": 430, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944609, "x": 273, "y": 451, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944626, "x": 275, "y": 471, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944642, "x": 280, "y": 489, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944660, "x": 286, "y": 499, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944677, "x": 292, "y": 508, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944695, "x": 297, "y": 513, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944712, "x": 301, "y": 517, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944729, "x": 304, "y": 520, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944747, "x": 306, "y": 521, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944766, "x": 306, "y": 522, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944787, "x": 307, "y": 522, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322944986, "x": 306, "y": 522, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945011, "x": 306, "y": 523, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945035, "x": 305, "y": 523, "evt": { "button": 0 } },
{ "type": "down", "time": 1454322945050, "x": 305, "y": 523, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945053, "x": 305, "y": 524, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945074, "x": 304, "y": 524, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945086, "x": 303, "y": 524, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945104, "x": 300, "y": 524, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945120, "x": 294, "y": 522, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945138, "x": 280, "y": 516, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945155, "x": 257, "y": 508, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945172, "x": 226, "y": 499, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945189, "x": 189, "y": 490, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945206, "x": 156, "y": 480, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945225, "x": 129, "y": 473, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945240, "x": 105, "y": 465, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945258, "x": 90, "y": 460, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945276, "x": 83, "y": 457, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945292, "x": 75, "y": 455, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945309, "x": 70, "y": 452, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945326, "x": 64, "y": 448, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945343, "x": 56, "y": 443, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945361, "x": 51, "y": 440, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945378, "x": 46, "y": 435, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945395, "x": 41, "y": 431, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945412, "x": 39, "y": 427, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945430, "x": 39, "y": 424, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945446, "x": 39, "y": 421, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945463, "x": 39, "y": 417, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945480, "x": 39, "y": 413, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945498, "x": 37, "y": 410, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945515, "x": 36, "y": 406, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945531, "x": 34, "y": 402, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945549, "x": 32, "y": 399, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945566, "x": 30, "y": 396, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945583, "x": 29, "y": 395, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945602, "x": 29, "y": 394, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945622, "x": 28, "y": 393, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945636, "x": 28, "y": 392, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945659, "x": 28, "y": 391, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945676, "x": 27, "y": 391, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945693, "x": 27, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322945710, "x": 26, "y": 389, "evt": { "button": 0 } },
{ "type": "up", "time": 1454322945939, "x": 26, "y": 389, "evt": { "button": 0 } },
{ "type": "cut", "time": 1454322946856 },
{ "type": "move", "time": 1454322947698, "x": 25, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322947715, "x": 24, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322947732, "x": 20, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322947749, "x": 11, "y": 387, "evt": { "button": 0 } },
{ "type": "move", "time": 1454322947766, "x": 0, "y": 384, "evt": { "button": 0 } },
{ "type": "out", "time": 1454322947784, "x": -13, "y": 380, "evt": { "button": 0 } }
]

View File

@@ -0,0 +1,63 @@
[
{ "type": "over", "time": 1439214702638, "x": 5, "y": 178, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702638, "x": 5, "y": 178, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702654, "x": 7, "y": 176, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702685, "x": 8, "y": 176, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702778, "x": 8, "y": 175, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702795, "x": 9, "y": 174, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702817, "x": 10, "y": 173, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702830, "x": 13, "y": 168, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702850, "x": 15, "y": 160, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702864, "x": 18, "y": 146, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702882, "x": 21, "y": 135, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702899, "x": 22, "y": 122, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702916, "x": 23, "y": 114, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702933, "x": 23, "y": 106, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702955, "x": 23, "y": 101, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702967, "x": 23, "y": 96, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214702984, "x": 23, "y": 92, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703001, "x": 23, "y": 87, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703019, "x": 23, "y": 83, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703070, "x": 23, "y": 72, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703088, "x": 25, "y": 69, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703105, "x": 25, "y": 65, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703122, "x": 26, "y": 64, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703140, "x": 27, "y": 63, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703160, "x": 28, "y": 62, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703176, "x": 30, "y": 60, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703196, "x": 30, "y": 59, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703210, "x": 31, "y": 59, "evt": { "button": 0 } },
{ "type": "down", "time": 1439214703317, "x": 31, "y": 59, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703319, "x": 32, "y": 59, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703326, "x": 34, "y": 65, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703344, "x": 40, "y": 77, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703359, "x": 46, "y": 90, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703377, "x": 56, "y": 108, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703395, "x": 66, "y": 125, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703410, "x": 74, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703429, "x": 81, "y": 147, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703457, "x": 86, "y": 156, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703460, "x": 93, "y": 165, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703478, "x": 99, "y": 173, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703495, "x": 104, "y": 179, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703512, "x": 109, "y": 186, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703530, "x": 115, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703548, "x": 119, "y": 196, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703564, "x": 125, "y": 201, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703581, "x": 128, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703598, "x": 130, "y": 205, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703616, "x": 131, "y": 205, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703634, "x": 132, "y": 205, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703652, "x": 133, "y": 206, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703669, "x": 134, "y": 207, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703687, "x": 136, "y": 208, "evt": { "button": 0 } },
{ "type": "up", "time": 1439214703875, "x": 136, "y": 208, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214703882, "x": 136, "y": 208, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214704043, "x": 136, "y": 207, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214704098, "x": 133, "y": 207, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214704116, "x": 124, "y": 216, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214704133, "x": 103, "y": 234, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214704150, "x": 73, "y": 261, "evt": { "button": 0 } },
{ "type": "move", "time": 1439214704167, "x": 32, "y": 300, "evt": { "button": 0 } },
{ "type": "out", "time": 1439214704185, "x": -20, "y": 352, "evt": { "button": 0 } }
]

View File

@@ -0,0 +1,37 @@
[
{ "type": "over", "time": 1439215250633, "x": 0, "y": 115, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250634, "x": 0, "y": 115, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250650, "x": 1, "y": 115, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250666, "x": 3, "y": 117, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250683, "x": 7, "y": 117, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250700, "x": 13, "y": 118, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250737, "x": 28, "y": 121, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250794, "x": 36, "y": 123, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250804, "x": 36, "y": 125, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250821, "x": 37, "y": 126, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250839, "x": 37, "y": 127, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250855, "x": 37, "y": 128, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250872, "x": 37, "y": 129, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250889, "x": 37, "y": 131, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215250914, "x": 37, "y": 132, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215251063, "x": 35, "y": 140, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215251066, "x": 35, "y": 141, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215251224, "x": 35, "y": 142, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215251241, "x": 34, "y": 142, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215251259, "x": 34, "y": 143, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215251276, "x": 34, "y": 144, "evt": { "button": 0 } },
{ "type": "down", "time": 1439215251482, "x": 34, "y": 144, "evt": { "button": 0 } },
{ "type": "up", "time": 1439215251585, "x": 34, "y": 144, "evt": { "button": 0 } },
{ "type": "down", "time": 1439215252281, "x": 34, "y": 144, "evt": { "button": 0 } },
{ "type": "up", "time": 1439215252385, "x": 34, "y": 144, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215252857, "x": 33, "y": 144, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215252874, "x": 31, "y": 146, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215252915, "x": 28, "y": 147, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215252925, "x": 26, "y": 148, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215252943, "x": 22, "y": 148, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215252960, "x": 20, "y": 149, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215252978, "x": 17, "y": 150, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215252995, "x": 10, "y": 151, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215253012, "x": 2, "y": 156, "evt": { "button": 0 } },
{ "type": "out", "time": 1439215253030, "x": -12, "y": 163, "evt": { "button": 0 } }
]

View File

@@ -0,0 +1,147 @@
[
{ "type": "move", "time": 1401786837103, "x": 1, "y": 138, "evt": {} },
{ "type": "move", "time": 1401786837119, "x": 9, "y": 133, "evt": {} },
{ "type": "move", "time": 1401786837136, "x": 17, "y": 127, "evt": {} },
{ "type": "move", "time": 1401786837153, "x": 26, "y": 123, "evt": {} },
{ "type": "move", "time": 1401786837170, "x": 32, "y": 121, "evt": {} },
{ "type": "move", "time": 1401786837187, "x": 37, "y": 120, "evt": {} },
{ "type": "move", "time": 1401786837204, "x": 41, "y": 119, "evt": {} },
{ "type": "move", "time": 1401786837221, "x": 44, "y": 119, "evt": {} },
{ "type": "move", "time": 1401786837238, "x": 47, "y": 118, "evt": {} },
{ "type": "move", "time": 1401786837255, "x": 51, "y": 118, "evt": {} },
{ "type": "move", "time": 1401786837272, "x": 54, "y": 116, "evt": {} },
{ "type": "move", "time": 1401786837288, "x": 57, "y": 116, "evt": {} },
{ "type": "move", "time": 1401786837305, "x": 60, "y": 115, "evt": {} },
{ "type": "move", "time": 1401786837356, "x": 66, "y": 115, "evt": {} },
{ "type": "move", "time": 1401786837358, "x": 69, "y": 114, "evt": {} },
{ "type": "move", "time": 1401786837375, "x": 72, "y": 113, "evt": {} },
{ "type": "move", "time": 1401786837393, "x": 74, "y": 112, "evt": {} },
{ "type": "move", "time": 1401786837410, "x": 78, "y": 109, "evt": {} },
{ "type": "move", "time": 1401786837426, "x": 80, "y": 107, "evt": {} },
{ "type": "move", "time": 1401786837443, "x": 83, "y": 103, "evt": {} },
{ "type": "move", "time": 1401786837460, "x": 85, "y": 101, "evt": {} },
{ "type": "move", "time": 1401786837478, "x": 87, "y": 100, "evt": {} },
{ "type": "move", "time": 1401786837495, "x": 88, "y": 99, "evt": {} },
{ "type": "move", "time": 1401786837513, "x": 89, "y": 98, "evt": {} },
{ "type": "move", "time": 1401786837530, "x": 90, "y": 97, "evt": {} },
{ "type": "move", "time": 1401786837546, "x": 91, "y": 96, "evt": {} },
{ "type": "move", "time": 1401786837563, "x": 93, "y": 95, "evt": {} },
{ "type": "move", "time": 1401786837581, "x": 94, "y": 94, "evt": {} },
{ "type": "move", "time": 1401786837599, "x": 96, "y": 93, "evt": {} },
{ "type": "move", "time": 1401786837616, "x": 97, "y": 93, "evt": {} },
{ "type": "move", "time": 1401786837632, "x": 98, "y": 92, "evt": {} },
{ "type": "move", "time": 1401786837649, "x": 99, "y": 92, "evt": {} },
{ "type": "move", "time": 1401786837667, "x": 100, "y": 92, "evt": {} },
{ "type": "move", "time": 1401786837690, "x": 101, "y": 92, "evt": {} },
{ "type": "move", "time": 1401786837721, "x": 101, "y": 92, "evt": {} },
{ "type": "move", "time": 1401786837738, "x": 101, "y": 91, "evt": {} },
{ "type": "move", "time": 1401786837755, "x": 102, "y": 91, "evt": {} },
{ "type": "move", "time": 1401786837771, "x": 104, "y": 91, "evt": {} },
{ "type": "move", "time": 1401786837788, "x": 105, "y": 91, "evt": {} },
{ "type": "move", "time": 1401786837806, "x": 106, "y": 91, "evt": {} },
{ "type": "move", "time": 1401786837824, "x": 107, "y": 91, "evt": {} },
{ "type": "move", "time": 1401786837841, "x": 108, "y": 91, "evt": {} },
{ "type": "move", "time": 1401786837857, "x": 108, "y": 90, "evt": {} },
{ "type": "move", "time": 1401786837875, "x": 109, "y": 90, "evt": {} },
{ "type": "move", "time": 1401786838991, "x": 109, "y": 91, "evt": {} },
{ "type": "move", "time": 1401786839008, "x": 110, "y": 91, "evt": {} },
{ "type": "move", "time": 1401786839029, "x": 112, "y": 91, "evt": {} },
{ "type": "move", "time": 1401786839043, "x": 112, "y": 92, "evt": {} },
{ "type": "down", "time": 1401786839048, "x": 112, "y": 92, "evt": {} },
{ "type": "move", "time": 1401786839435, "x": 113, "y": 92, "evt": {} },
{ "type": "move", "time": 1401786839452, "x": 117, "y": 92, "evt": {} },
{ "type": "move", "time": 1401786839469, "x": 122, "y": 92, "evt": {} },
{ "type": "move", "time": 1401786839488, "x": 128, "y": 92, "evt": {} },
{ "type": "move", "time": 1401786839505, "x": 137, "y": 92, "evt": {} },
{ "type": "move", "time": 1401786839523, "x": 144, "y": 92, "evt": {} },
{ "type": "move", "time": 1401786839539, "x": 154, "y": 95, "evt": {} },
{ "type": "move", "time": 1401786839557, "x": 166, "y": 100, "evt": {} },
{ "type": "move", "time": 1401786839584, "x": 175, "y": 105, "evt": {} },
{ "type": "move", "time": 1401786839589, "x": 187, "y": 112, "evt": {} },
{ "type": "move", "time": 1401786839607, "x": 202, "y": 121, "evt": {} },
{ "type": "move", "time": 1401786839623, "x": 210, "y": 127, "evt": {} },
{ "type": "move", "time": 1401786839641, "x": 221, "y": 136, "evt": {} },
{ "type": "move", "time": 1401786839658, "x": 232, "y": 147, "evt": {} },
{ "type": "move", "time": 1401786839675, "x": 238, "y": 155, "evt": {} },
{ "type": "move", "time": 1401786839693, "x": 246, "y": 165, "evt": {} },
{ "type": "move", "time": 1401786839709, "x": 252, "y": 174, "evt": {} },
{ "type": "move", "time": 1401786839727, "x": 256, "y": 185, "evt": {} },
{ "type": "move", "time": 1401786839743, "x": 257, "y": 187, "evt": {} },
{ "type": "move", "time": 1401786839956, "x": 257, "y": 191, "evt": {} },
{ "type": "move", "time": 1401786839974, "x": 257, "y": 200, "evt": {} },
{ "type": "move", "time": 1401786839990, "x": 257, "y": 212, "evt": {} },
{ "type": "move", "time": 1401786840007, "x": 257, "y": 235, "evt": {} },
{ "type": "move", "time": 1401786840024, "x": 257, "y": 264, "evt": {} },
{ "type": "move", "time": 1401786840041, "x": 260, "y": 283, "evt": {} },
{ "type": "move", "time": 1401786840058, "x": 262, "y": 306, "evt": {} },
{ "type": "move", "time": 1401786840075, "x": 263, "y": 324, "evt": {} },
{ "type": "move", "time": 1401786840092, "x": 265, "y": 340, "evt": {} },
{ "type": "move", "time": 1401786840109, "x": 265, "y": 353, "evt": {} },
{ "type": "move", "time": 1401786840126, "x": 265, "y": 366, "evt": {} },
{ "type": "move", "time": 1401786840144, "x": 264, "y": 373, "evt": {} },
{ "type": "move", "time": 1401786840161, "x": 259, "y": 378, "evt": {} },
{ "type": "move", "time": 1401786840331, "x": 259, "y": 379, "evt": {} },
{ "type": "move", "time": 1401786840348, "x": 257, "y": 385, "evt": {} },
{ "type": "move", "time": 1401786840365, "x": 254, "y": 389, "evt": {} },
{ "type": "move", "time": 1401786840382, "x": 250, "y": 392, "evt": {} },
{ "type": "move", "time": 1401786840400, "x": 246, "y": 395, "evt": {} },
{ "type": "move", "time": 1401786840417, "x": 240, "y": 398, "evt": {} },
{ "type": "move", "time": 1401786840434, "x": 233, "y": 402, "evt": {} },
{ "type": "move", "time": 1401786840451, "x": 227, "y": 404, "evt": {} },
{ "type": "move", "time": 1401786840468, "x": 222, "y": 407, "evt": {} },
{ "type": "move", "time": 1401786840485, "x": 215, "y": 409, "evt": {} },
{ "type": "move", "time": 1401786840502, "x": 210, "y": 410, "evt": {} },
{ "type": "move", "time": 1401786840519, "x": 205, "y": 412, "evt": {} },
{ "type": "move", "time": 1401786840536, "x": 202, "y": 413, "evt": {} },
{ "type": "move", "time": 1401786840553, "x": 196, "y": 415, "evt": {} },
{ "type": "move", "time": 1401786840571, "x": 191, "y": 417, "evt": {} },
{ "type": "move", "time": 1401786840587, "x": 185, "y": 419, "evt": {} },
{ "type": "move", "time": 1401786840604, "x": 179, "y": 420, "evt": {} },
{ "type": "move", "time": 1401786840621, "x": 173, "y": 421, "evt": {} },
{ "type": "move", "time": 1401786840639, "x": 169, "y": 423, "evt": {} },
{ "type": "move", "time": 1401786840656, "x": 164, "y": 424, "evt": {} },
{ "type": "move", "time": 1401786840673, "x": 160, "y": 425, "evt": {} },
{ "type": "move", "time": 1401786840690, "x": 154, "y": 426, "evt": {} },
{ "type": "move", "time": 1401786840707, "x": 150, "y": 427, "evt": {} },
{ "type": "move", "time": 1401786840726, "x": 147, "y": 428, "evt": {} },
{ "type": "move", "time": 1401786840743, "x": 144, "y": 429, "evt": {} },
{ "type": "move", "time": 1401786840760, "x": 140, "y": 430, "evt": {} },
{ "type": "move", "time": 1401786840777, "x": 138, "y": 430, "evt": {} },
{ "type": "move", "time": 1401786840794, "x": 134, "y": 430, "evt": {} },
{ "type": "move", "time": 1401786840812, "x": 131, "y": 430, "evt": {} },
{ "type": "move", "time": 1401786840828, "x": 129, "y": 430, "evt": {} },
{ "type": "move", "time": 1401786840846, "x": 127, "y": 430, "evt": {} },
{ "type": "move", "time": 1401786840863, "x": 125, "y": 430, "evt": {} },
{ "type": "move", "time": 1401786840880, "x": 124, "y": 430, "evt": {} },
{ "type": "move", "time": 1401786840898, "x": 123, "y": 430, "evt": {} },
{ "type": "move", "time": 1401786840915, "x": 122, "y": 430, "evt": {} },
{ "type": "move", "time": 1401786841315, "x": 121, "y": 430, "evt": {} },
{ "type": "move", "time": 1401786841332, "x": 120, "y": 429, "evt": {} },
{ "type": "move", "time": 1401786841350, "x": 119, "y": 428, "evt": {} },
{ "type": "move", "time": 1401786841366, "x": 118, "y": 428, "evt": {} },
{ "type": "move", "time": 1401786841384, "x": 118, "y": 427, "evt": {} },
{ "type": "move", "time": 1401786841408, "x": 117, "y": 427, "evt": {} },
{ "type": "move", "time": 1401786841418, "x": 116, "y": 427, "evt": {} },
{ "type": "move", "time": 1401786841436, "x": 115, "y": 427, "evt": {} },
{ "type": "move", "time": 1401786841453, "x": 114, "y": 427, "evt": {} },
{ "type": "move", "time": 1401786841504, "x": 113, "y": 427, "evt": {} },
{ "type": "move", "time": 1401786841509, "x": 112, "y": 426, "evt": {} },
{ "type": "move", "time": 1401786841533, "x": 111, "y": 426, "evt": {} },
{ "type": "move", "time": 1401786841653, "x": 110, "y": 426, "evt": {} },
{ "type": "move", "time": 1401786841670, "x": 110, "y": 425, "evt": {} },
{ "type": "up", "time": 1401786842595, "x": 110, "y": 425, "evt": {} },
{ "type": "move", "time": 1401786844368, "x": 110, "y": 424, "evt": {} },
{ "type": "move", "time": 1401786844385, "x": 111, "y": 419, "evt": {} },
{ "type": "move", "time": 1401786844402, "x": 121, "y": 410, "evt": {} },
{ "type": "move", "time": 1401786844419, "x": 145, "y": 392, "evt": {} },
{ "type": "move", "time": 1401786844437, "x": 175, "y": 375, "evt": {} },
{ "type": "move", "time": 1401786844453, "x": 214, "y": 359, "evt": {} },
{ "type": "move", "time": 1401786844470, "x": 261, "y": 343, "evt": {} },
{ "type": "move", "time": 1401786844487, "x": 309, "y": 328, "evt": {} },
{ "type": "move", "time": 1401786844509, "x": 332, "y": 322, "evt": {} },
{ "type": "move", "time": 1401786844522, "x": 353, "y": 317, "evt": {} },
{ "type": "move", "time": 1401786845065, "x": 353, "y": 316, "evt": {} },
{ "type": "move", "time": 1401786845082, "x": 360, "y": 311, "evt": {} },
{ "type": "move", "time": 1401786845099, "x": 371, "y": 300, "evt": {} },
{ "type": "move", "time": 1401786845116, "x": 399, "y": 282, "evt": {} }
]

View File

@@ -0,0 +1,158 @@
[
{ "type": "move", "time": 1401787196416, "x": 1, "y": 75, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196433, "x": 5, "y": 76, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196450, "x": 8, "y": 79, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196466, "x": 12, "y": 82, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196484, "x": 15, "y": 84, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196501, "x": 19, "y": 84, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196518, "x": 22, "y": 85, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196535, "x": 23, "y": 86, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196553, "x": 24, "y": 86, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196570, "x": 25, "y": 86, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196595, "x": 26, "y": 86, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196618, "x": 27, "y": 86, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196636, "x": 27, "y": 87, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196653, "x": 28, "y": 87, "evt": { "button": 0 } },
{ "type": "down", "time": 1401787196834, "x": 28, "y": 87, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787196996, "x": 29, "y": 87, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197013, "x": 32, "y": 88, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197030, "x": 36, "y": 90, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197047, "x": 46, "y": 98, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197064, "x": 66, "y": 113, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197095, "x": 81, "y": 124, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197098, "x": 102, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197116, "x": 120, "y": 146, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197133, "x": 133, "y": 152, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197150, "x": 145, "y": 156, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197167, "x": 151, "y": 159, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197184, "x": 158, "y": 162, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197201, "x": 162, "y": 164, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197219, "x": 165, "y": 165, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197236, "x": 168, "y": 166, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197252, "x": 171, "y": 166, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197269, "x": 173, "y": 166, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197286, "x": 176, "y": 167, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197304, "x": 179, "y": 168, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197320, "x": 182, "y": 170, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197338, "x": 185, "y": 171, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197356, "x": 189, "y": 172, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197372, "x": 191, "y": 174, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197389, "x": 193, "y": 175, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197406, "x": 195, "y": 176, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197423, "x": 196, "y": 177, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197441, "x": 198, "y": 178, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197458, "x": 199, "y": 179, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197475, "x": 200, "y": 179, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197492, "x": 201, "y": 180, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197508, "x": 202, "y": 180, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197525, "x": 203, "y": 180, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197543, "x": 204, "y": 180, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197560, "x": 205, "y": 180, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197577, "x": 206, "y": 180, "evt": { "button": 0 } },
{ "type": "up", "time": 1401787197642, "x": 206, "y": 180, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197790, "x": 183, "y": 177, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197807, "x": 175, "y": 176, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197824, "x": 167, "y": 173, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197841, "x": 161, "y": 168, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197858, "x": 153, "y": 162, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197875, "x": 145, "y": 154, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197893, "x": 138, "y": 146, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197909, "x": 132, "y": 139, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197927, "x": 128, "y": 132, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197943, "x": 125, "y": 129, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197960, "x": 123, "y": 125, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197977, "x": 121, "y": 122, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787197995, "x": 119, "y": 119, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198012, "x": 118, "y": 116, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198030, "x": 117, "y": 113, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198049, "x": 116, "y": 111, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198066, "x": 115, "y": 108, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198082, "x": 114, "y": 104, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198099, "x": 113, "y": 103, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198119, "x": 112, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198134, "x": 112, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198150, "x": 111, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198174, "x": 111, "y": 101, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198200, "x": 111, "y": 100, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198217, "x": 109, "y": 99, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198234, "x": 109, "y": 98, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198250, "x": 109, "y": 97, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198290, "x": 109, "y": 96, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198370, "x": 109, "y": 95, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198387, "x": 109, "y": 94, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198404, "x": 109, "y": 93, "evt": { "button": 0 } },
{ "type": "down", "time": 1401787198435, "x": 109, "y": 93, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198586, "x": 108, "y": 93, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198603, "x": 112, "y": 93, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198620, "x": 120, "y": 93, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198637, "x": 135, "y": 93, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198654, "x": 157, "y": 101, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198672, "x": 188, "y": 117, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198692, "x": 218, "y": 141, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198706, "x": 244, "y": 169, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198723, "x": 262, "y": 194, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198740, "x": 285, "y": 238, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198757, "x": 294, "y": 263, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198773, "x": 302, "y": 293, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198791, "x": 307, "y": 309, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198824, "x": 308, "y": 321, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198978, "x": 308, "y": 322, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787198995, "x": 301, "y": 329, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199012, "x": 294, "y": 338, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199029, "x": 287, "y": 345, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199046, "x": 279, "y": 352, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199063, "x": 269, "y": 362, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199081, "x": 258, "y": 367, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199098, "x": 248, "y": 371, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199114, "x": 238, "y": 375, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199131, "x": 228, "y": 377, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199148, "x": 218, "y": 379, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199165, "x": 209, "y": 381, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199183, "x": 200, "y": 383, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199200, "x": 195, "y": 384, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199220, "x": 190, "y": 385, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199237, "x": 185, "y": 385, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199253, "x": 180, "y": 385, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199271, "x": 175, "y": 385, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199289, "x": 170, "y": 386, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199304, "x": 166, "y": 386, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199322, "x": 162, "y": 386, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199339, "x": 159, "y": 387, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199355, "x": 156, "y": 387, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199373, "x": 153, "y": 388, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199390, "x": 150, "y": 388, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199407, "x": 147, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199425, "x": 143, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199442, "x": 139, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199459, "x": 137, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199475, "x": 134, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199510, "x": 131, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199515, "x": 130, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199527, "x": 128, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199545, "x": 127, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199562, "x": 126, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199580, "x": 124, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199596, "x": 123, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199613, "x": 122, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199630, "x": 121, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199648, "x": 120, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199665, "x": 119, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199724, "x": 118, "y": 390, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199740, "x": 118, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199758, "x": 117, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199788, "x": 116, "y": 389, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199805, "x": 116, "y": 388, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199821, "x": 115, "y": 388, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199844, "x": 115, "y": 387, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199860, "x": 114, "y": 387, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199877, "x": 114, "y": 386, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199895, "x": 113, "y": 386, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199912, "x": 112, "y": 385, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199930, "x": 111, "y": 384, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199949, "x": 111, "y": 384, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199973, "x": 110, "y": 384, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787199989, "x": 110, "y": 383, "evt": { "button": 0 } },
{ "type": "up", "time": 1401787200242, "x": 110, "y": 383, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787200554, "x": 46, "y": 387, "evt": { "button": 0 } },
{ "type": "move", "time": 1401787200571, "x": 20, "y": 392, "evt": { "button": 0 } }
]

View File

@@ -0,0 +1,342 @@
[
{ "type": "over", "time": 1439215637976, "x": 0, "y": 88, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215637977, "x": 0, "y": 88, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215637992, "x": 2, "y": 88, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638087, "x": 4, "y": 88, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638104, "x": 5, "y": 88, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638116, "x": 8, "y": 88, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638132, "x": 13, "y": 88, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638150, "x": 20, "y": 88, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638166, "x": 26, "y": 88, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638187, "x": 27, "y": 88, "evt": { "button": 0 } },
{ "type": "down", "time": 1439215638371, "x": 27, "y": 88, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638434, "x": 27, "y": 89, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638451, "x": 28, "y": 89, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638468, "x": 31, "y": 95, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638526, "x": 60, "y": 130, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638535, "x": 72, "y": 143, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638552, "x": 91, "y": 157, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638570, "x": 103, "y": 171, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638587, "x": 119, "y": 185, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638604, "x": 134, "y": 195, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638621, "x": 145, "y": 205, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638638, "x": 156, "y": 213, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638655, "x": 164, "y": 220, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638672, "x": 171, "y": 226, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638690, "x": 176, "y": 232, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638706, "x": 181, "y": 236, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638726, "x": 185, "y": 241, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638742, "x": 187, "y": 242, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638762, "x": 190, "y": 244, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638776, "x": 193, "y": 247, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638793, "x": 194, "y": 248, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638810, "x": 196, "y": 250, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638827, "x": 198, "y": 252, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638844, "x": 201, "y": 254, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638861, "x": 204, "y": 257, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638877, "x": 207, "y": 259, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638894, "x": 211, "y": 263, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638910, "x": 216, "y": 266, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638927, "x": 221, "y": 271, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638945, "x": 226, "y": 274, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638962, "x": 230, "y": 279, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638979, "x": 235, "y": 284, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215638996, "x": 239, "y": 287, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639013, "x": 242, "y": 290, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639030, "x": 245, "y": 293, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639048, "x": 248, "y": 295, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639065, "x": 250, "y": 297, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639082, "x": 251, "y": 298, "evt": { "button": 0 } },
{ "type": "up", "time": 1439215639323, "x": 251, "y": 298, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639370, "x": 251, "y": 297, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639388, "x": 249, "y": 291, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639404, "x": 238, "y": 275, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639421, "x": 220, "y": 251, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639438, "x": 203, "y": 228, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639468, "x": 184, "y": 201, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639472, "x": 166, "y": 175, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639489, "x": 144, "y": 142, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639506, "x": 133, "y": 127, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639524, "x": 132, "y": 124, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639543, "x": 132, "y": 122, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639560, "x": 131, "y": 121, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639576, "x": 131, "y": 120, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639594, "x": 131, "y": 117, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639611, "x": 130, "y": 115, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639627, "x": 128, "y": 112, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639643, "x": 127, "y": 110, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639661, "x": 126, "y": 109, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639677, "x": 125, "y": 107, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639694, "x": 125, "y": 106, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639712, "x": 124, "y": 106, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639728, "x": 124, "y": 105, "evt": { "button": 0 } },
{ "type": "down", "time": 1439215639802, "x": 124, "y": 105, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639922, "x": 125, "y": 105, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639939, "x": 127, "y": 107, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639956, "x": 132, "y": 112, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639972, "x": 142, "y": 119, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215639989, "x": 153, "y": 129, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640006, "x": 164, "y": 142, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640024, "x": 179, "y": 159, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640040, "x": 194, "y": 174, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640060, "x": 206, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640077, "x": 216, "y": 205, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640094, "x": 226, "y": 222, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640110, "x": 237, "y": 239, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640126, "x": 245, "y": 255, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640143, "x": 252, "y": 271, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640160, "x": 259, "y": 289, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640177, "x": 263, "y": 306, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640194, "x": 266, "y": 323, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640211, "x": 268, "y": 338, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640228, "x": 269, "y": 352, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640245, "x": 271, "y": 370, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640262, "x": 271, "y": 383, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640279, "x": 271, "y": 400, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640296, "x": 271, "y": 415, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640314, "x": 269, "y": 427, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640330, "x": 268, "y": 433, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640348, "x": 266, "y": 436, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640365, "x": 264, "y": 439, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640383, "x": 261, "y": 443, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640401, "x": 259, "y": 446, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640418, "x": 253, "y": 449, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640436, "x": 248, "y": 451, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640452, "x": 243, "y": 452, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640468, "x": 238, "y": 454, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640486, "x": 231, "y": 456, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640501, "x": 228, "y": 457, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640518, "x": 224, "y": 457, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640535, "x": 219, "y": 457, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640552, "x": 217, "y": 455, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640569, "x": 216, "y": 454, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640587, "x": 211, "y": 452, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640604, "x": 208, "y": 448, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640621, "x": 206, "y": 444, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640638, "x": 204, "y": 442, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640655, "x": 204, "y": 441, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640673, "x": 204, "y": 440, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640690, "x": 203, "y": 439, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640710, "x": 202, "y": 438, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640727, "x": 201, "y": 437, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640745, "x": 201, "y": 436, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640763, "x": 201, "y": 435, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640781, "x": 200, "y": 435, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215640818, "x": 200, "y": 434, "evt": { "button": 0 } },
{ "type": "up", "time": 1439215641095, "x": 200, "y": 434, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641258, "x": 201, "y": 434, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641347, "x": 202, "y": 434, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641380, "x": 202, "y": 435, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641396, "x": 202, "y": 436, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641414, "x": 202, "y": 438, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641431, "x": 202, "y": 441, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641447, "x": 204, "y": 447, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641467, "x": 210, "y": 456, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641482, "x": 213, "y": 465, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641499, "x": 213, "y": 473, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641516, "x": 215, "y": 479, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641533, "x": 217, "y": 484, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641550, "x": 218, "y": 486, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641567, "x": 220, "y": 486, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641587, "x": 220, "y": 487, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641605, "x": 221, "y": 487, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641621, "x": 225, "y": 487, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641639, "x": 236, "y": 488, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641656, "x": 256, "y": 488, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641676, "x": 278, "y": 486, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641693, "x": 296, "y": 483, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641709, "x": 312, "y": 480, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641727, "x": 318, "y": 477, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641765, "x": 322, "y": 474, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641802, "x": 321, "y": 474, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641820, "x": 317, "y": 473, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641837, "x": 313, "y": 469, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641854, "x": 308, "y": 467, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641871, "x": 303, "y": 467, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641888, "x": 300, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641906, "x": 296, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215641924, "x": 295, "y": 466, "evt": { "button": 0 } },
{ "type": "down", "time": 1439215642074, "x": 295, "y": 466, "evt": { "button": 0 } },
{ "type": "up", "time": 1439215642210, "x": 295, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642258, "x": 294, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642278, "x": 293, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642306, "x": 290, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642309, "x": 286, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642326, "x": 278, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642343, "x": 269, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642359, "x": 261, "y": 465, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642377, "x": 254, "y": 465, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642394, "x": 252, "y": 465, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642411, "x": 251, "y": 465, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642474, "x": 252, "y": 465, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642492, "x": 253, "y": 465, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642510, "x": 256, "y": 465, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642526, "x": 264, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642543, "x": 270, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642561, "x": 278, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642578, "x": 283, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642594, "x": 285, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642611, "x": 286, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642659, "x": 287, "y": 466, "evt": { "button": 0 } },
{ "type": "down", "time": 1439215642682, "x": 287, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642722, "x": 286, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642740, "x": 281, "y": 466, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642756, "x": 267, "y": 465, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642775, "x": 244, "y": 463, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642794, "x": 217, "y": 460, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642812, "x": 192, "y": 460, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642826, "x": 176, "y": 460, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642844, "x": 160, "y": 461, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642861, "x": 148, "y": 461, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642878, "x": 137, "y": 461, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642894, "x": 128, "y": 459, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642912, "x": 119, "y": 458, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642928, "x": 110, "y": 458, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642945, "x": 103, "y": 456, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642962, "x": 97, "y": 455, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642980, "x": 87, "y": 452, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215642997, "x": 82, "y": 452, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643014, "x": 77, "y": 452, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643031, "x": 72, "y": 452, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643049, "x": 69, "y": 450, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643066, "x": 66, "y": 449, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643083, "x": 63, "y": 448, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643101, "x": 58, "y": 446, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643118, "x": 53, "y": 445, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643135, "x": 52, "y": 444, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643152, "x": 51, "y": 444, "evt": { "button": 0 } },
{ "type": "up", "time": 1439215643196, "x": 51, "y": 444, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643260, "x": 52, "y": 444, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643279, "x": 53, "y": 444, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643294, "x": 59, "y": 446, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643309, "x": 69, "y": 448, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643350, "x": 98, "y": 453, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643360, "x": 108, "y": 455, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643378, "x": 118, "y": 457, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643395, "x": 124, "y": 457, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643412, "x": 127, "y": 457, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643498, "x": 128, "y": 457, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643515, "x": 129, "y": 457, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643562, "x": 129, "y": 457, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643587, "x": 129, "y": 456, "evt": { "button": 0 } },
{ "type": "down", "time": 1439215643637, "x": 129, "y": 456, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643660, "x": 130, "y": 456, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643678, "x": 131, "y": 456, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643694, "x": 134, "y": 455, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643711, "x": 139, "y": 452, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643729, "x": 145, "y": 449, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643744, "x": 152, "y": 446, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643761, "x": 161, "y": 442, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643778, "x": 167, "y": 436, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643795, "x": 174, "y": 432, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643811, "x": 180, "y": 425, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643828, "x": 185, "y": 420, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643845, "x": 190, "y": 414, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643862, "x": 194, "y": 407, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643880, "x": 198, "y": 397, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643897, "x": 203, "y": 387, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643914, "x": 203, "y": 378, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643932, "x": 204, "y": 371, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643949, "x": 204, "y": 366, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643965, "x": 204, "y": 361, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215643983, "x": 204, "y": 357, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644002, "x": 204, "y": 352, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644019, "x": 204, "y": 345, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644036, "x": 204, "y": 336, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644051, "x": 204, "y": 321, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644070, "x": 202, "y": 308, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644085, "x": 201, "y": 291, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644103, "x": 199, "y": 273, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644119, "x": 196, "y": 257, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644137, "x": 191, "y": 243, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644153, "x": 190, "y": 232, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644169, "x": 186, "y": 222, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644187, "x": 181, "y": 214, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644203, "x": 178, "y": 209, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644220, "x": 176, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644237, "x": 173, "y": 199, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644254, "x": 172, "y": 196, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644272, "x": 171, "y": 195, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644290, "x": 170, "y": 193, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644306, "x": 169, "y": 192, "evt": { "button": 0 } },
{ "type": "up", "time": 1439215644499, "x": 169, "y": 192, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644602, "x": 169, "y": 193, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644620, "x": 169, "y": 196, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644636, "x": 169, "y": 207, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644652, "x": 169, "y": 225, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644669, "x": 174, "y": 250, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644687, "x": 180, "y": 277, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644704, "x": 187, "y": 313, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644721, "x": 194, "y": 345, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644738, "x": 197, "y": 377, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644755, "x": 197, "y": 409, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644772, "x": 197, "y": 432, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644789, "x": 197, "y": 452, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644806, "x": 197, "y": 463, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644823, "x": 197, "y": 469, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644843, "x": 197, "y": 473, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644859, "x": 197, "y": 476, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644877, "x": 196, "y": 480, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644893, "x": 190, "y": 484, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644910, "x": 184, "y": 490, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644927, "x": 179, "y": 494, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644944, "x": 175, "y": 498, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644963, "x": 173, "y": 500, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644977, "x": 172, "y": 502, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215644994, "x": 170, "y": 504, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645011, "x": 169, "y": 505, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645035, "x": 168, "y": 505, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645067, "x": 168, "y": 506, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645095, "x": 168, "y": 507, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645115, "x": 168, "y": 508, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645170, "x": 168, "y": 509, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645212, "x": 168, "y": 510, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645227, "x": 168, "y": 511, "evt": { "button": 0 } },
{ "type": "down", "time": 1439215645282, "x": 168, "y": 511, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645354, "x": 167, "y": 511, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645387, "x": 169, "y": 511, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645403, "x": 172, "y": 511, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645420, "x": 181, "y": 510, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645437, "x": 188, "y": 509, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645454, "x": 199, "y": 504, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645471, "x": 209, "y": 497, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645489, "x": 219, "y": 490, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645506, "x": 231, "y": 477, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645524, "x": 241, "y": 463, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645541, "x": 246, "y": 450, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645561, "x": 252, "y": 434, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645577, "x": 260, "y": 413, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645594, "x": 264, "y": 395, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645611, "x": 266, "y": 374, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645628, "x": 270, "y": 353, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645644, "x": 271, "y": 333, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645661, "x": 274, "y": 318, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645677, "x": 274, "y": 303, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645694, "x": 275, "y": 287, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645713, "x": 275, "y": 271, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645728, "x": 272, "y": 260, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645745, "x": 272, "y": 247, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645762, "x": 269, "y": 238, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645780, "x": 267, "y": 230, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645796, "x": 266, "y": 221, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645814, "x": 265, "y": 217, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645831, "x": 264, "y": 213, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645848, "x": 264, "y": 210, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645865, "x": 263, "y": 208, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645882, "x": 263, "y": 206, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645900, "x": 262, "y": 204, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645918, "x": 261, "y": 204, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215645935, "x": 261, "y": 203, "evt": { "button": 0 } },
{ "type": "up", "time": 1439215646074, "x": 261, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215646258, "x": 260, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215646279, "x": 258, "y": 207, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215646295, "x": 254, "y": 213, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215646311, "x": 247, "y": 221, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215646327, "x": 232, "y": 231, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215646345, "x": 212, "y": 243, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215646361, "x": 187, "y": 259, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215646378, "x": 147, "y": 276, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215646395, "x": 80, "y": 293, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215646412, "x": 9, "y": 305, "evt": { "button": 0 } },
{ "type": "out", "time": 1439215646429, "x": -70, "y": 309, "evt": { "button": 0 } }
]

View File

@@ -0,0 +1,167 @@
[
{ "type": "over", "time": 1431673500632, "x": 3, "y": 472, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500633, "x": 3, "y": 472, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500649, "x": 28, "y": 472, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500665, "x": 61, "y": 472, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500683, "x": 97, "y": 472, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500701, "x": 130, "y": 474, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500718, "x": 152, "y": 472, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500734, "x": 164, "y": 463, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500751, "x": 172, "y": 452, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500769, "x": 179, "y": 441, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500787, "x": 185, "y": 434, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500804, "x": 188, "y": 427, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500821, "x": 191, "y": 414, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500838, "x": 193, "y": 397, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500855, "x": 196, "y": 386, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500872, "x": 200, "y": 380, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500889, "x": 205, "y": 376, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500907, "x": 210, "y": 369, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500922, "x": 214, "y": 362, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500938, "x": 216, "y": 357, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500955, "x": 218, "y": 352, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500973, "x": 219, "y": 349, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673500991, "x": 220, "y": 345, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673501006, "x": 220, "y": 340, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673501024, "x": 221, "y": 334, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673501041, "x": 221, "y": 329, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673501058, "x": 221, "y": 325, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673501076, "x": 221, "y": 323, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673501093, "x": 221, "y": 322, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673501110, "x": 222, "y": 321, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673501127, "x": 222, "y": 319, "evt": { "button": 0 } },
{ "type": "down", "time": 1431673501148, "x": 222, "y": 319, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501164, "x": 222, "y": 319, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501219, "x": 222, "y": 320, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501240, "x": 222, "y": 322, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501258, "x": 222, "y": 326, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501270, "x": 222, "y": 333, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501322, "x": 226, "y": 364, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501347, "x": 228, "y": 373, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501356, "x": 229, "y": 383, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501373, "x": 231, "y": 394, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501389, "x": 231, "y": 403, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501407, "x": 232, "y": 408, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501424, "x": 232, "y": 414, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501441, "x": 233, "y": 419, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501458, "x": 233, "y": 424, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501475, "x": 234, "y": 427, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501493, "x": 234, "y": 429, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501510, "x": 234, "y": 430, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501605, "x": 234, "y": 429, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501623, "x": 234, "y": 420, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501639, "x": 234, "y": 405, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501654, "x": 235, "y": 388, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501673, "x": 237, "y": 372, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501690, "x": 237, "y": 356, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501710, "x": 241, "y": 336, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501724, "x": 241, "y": 316, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501739, "x": 243, "y": 290, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501757, "x": 243, "y": 271, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501774, "x": 244, "y": 257, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501790, "x": 244, "y": 248, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501807, "x": 244, "y": 242, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501824, "x": 244, "y": 240, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501891, "x": 244, "y": 241, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501909, "x": 244, "y": 245, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501926, "x": 244, "y": 255, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501943, "x": 244, "y": 267, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501960, "x": 244, "y": 283, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501977, "x": 244, "y": 300, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673501994, "x": 246, "y": 315, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502011, "x": 248, "y": 328, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502028, "x": 248, "y": 337, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502045, "x": 248, "y": 344, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502062, "x": 248, "y": 349, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502078, "x": 248, "y": 351, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502096, "x": 248, "y": 352, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502179, "x": 248, "y": 351, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502197, "x": 247, "y": 347, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502214, "x": 246, "y": 340, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502231, "x": 246, "y": 332, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502248, "x": 246, "y": 326, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502265, "x": 246, "y": 320, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502282, "x": 246, "y": 316, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502299, "x": 246, "y": 311, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502317, "x": 246, "y": 310, "evt": { "button": 2 } },
{ "type": "up", "time": 1431673502475, "x": 246, "y": 310, "evt": { "button": 2 } },
{ "type": "move", "time": 1431673502516, "x": 245, "y": 310, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673502589, "x": 245, "y": 311, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673502605, "x": 245, "y": 314, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673502622, "x": 246, "y": 320, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673502639, "x": 248, "y": 330, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673502656, "x": 253, "y": 338, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673502674, "x": 257, "y": 349, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673502691, "x": 261, "y": 357, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673502708, "x": 263, "y": 366, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502725, "x": 266, "y": 375, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502741, "x": 266, "y": 382, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502759, "x": 266, "y": 387, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502776, "x": 264, "y": 392, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502793, "x": 264, "y": 394, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502817, "x": 264, "y": 395, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502829, "x": 263, "y": 396, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502899, "x": 262, "y": 396, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502924, "x": 262, "y": 395, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502941, "x": 261, "y": 394, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502958, "x": 261, "y": 393, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502981, "x": 261, "y": 392, "evt": { "button": 0, "spaceKey": true } },
{ "type": "down", "time": 1431673502989, "x": 261, "y": 392, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673502998, "x": 261, "y": 391, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503014, "x": 261, "y": 389, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503031, "x": 261, "y": 386, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503048, "x": 261, "y": 380, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503065, "x": 261, "y": 375, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503081, "x": 261, "y": 366, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503098, "x": 261, "y": 355, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503115, "x": 261, "y": 344, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503132, "x": 261, "y": 337, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503149, "x": 260, "y": 334, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503174, "x": 260, "y": 333, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503219, "x": 260, "y": 333, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503239, "x": 260, "y": 334, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503256, "x": 260, "y": 342, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503275, "x": 260, "y": 351, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503291, "x": 260, "y": 362, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503306, "x": 260, "y": 378, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503323, "x": 260, "y": 394, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503339, "x": 260, "y": 414, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503357, "x": 260, "y": 433, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503374, "x": 260, "y": 448, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503391, "x": 260, "y": 459, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503407, "x": 260, "y": 466, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503425, "x": 260, "y": 469, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503441, "x": 260, "y": 472, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503531, "x": 260, "y": 470, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503548, "x": 261, "y": 455, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503565, "x": 261, "y": 441, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503582, "x": 261, "y": 425, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503600, "x": 261, "y": 410, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503616, "x": 261, "y": 397, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503634, "x": 261, "y": 387, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503650, "x": 261, "y": 379, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503667, "x": 261, "y": 372, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503685, "x": 261, "y": 367, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503702, "x": 261, "y": 366, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503719, "x": 261, "y": 365, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503779, "x": 261, "y": 366, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503797, "x": 262, "y": 371, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503814, "x": 262, "y": 380, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503831, "x": 262, "y": 388, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503848, "x": 262, "y": 397, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503865, "x": 262, "y": 409, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503883, "x": 262, "y": 417, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503900, "x": 262, "y": 423, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503917, "x": 262, "y": 428, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503934, "x": 262, "y": 432, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503950, "x": 263, "y": 434, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673503973, "x": 263, "y": 435, "evt": { "button": 0, "spaceKey": true } },
{ "type": "up", "time": 1431673504179, "x": 263, "y": 435, "evt": { "button": 0, "spaceKey": true } },
{ "type": "move", "time": 1431673504307, "x": 261, "y": 436, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673504325, "x": 251, "y": 436, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673504342, "x": 235, "y": 436, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673504359, "x": 198, "y": 437, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673504376, "x": 147, "y": 434, "evt": { "button": 0 } },
{ "type": "move", "time": 1431673504393, "x": 75, "y": 430, "evt": { "button": 0 } },
{ "type": "out", "time": 1431673504411, "x": -12, "y": 421, "evt": { "button": 0 } }
]

View File

@@ -0,0 +1,182 @@
[
{ "type": "over", "time": 1439215782915, "x": 114, "y": 184, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215782916, "x": 114, "y": 184, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215782926, "x": 118, "y": 186, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215782943, "x": 120, "y": 187, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783049, "x": 121, "y": 188, "evt": { "button": 0 } },
{ "type": "down", "time": 1439215783320, "x": 121, "y": 188, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783384, "x": 121, "y": 187, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783401, "x": 121, "y": 186, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783418, "x": 122, "y": 183, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783485, "x": 125, "y": 167, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783485, "x": 127, "y": 162, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783503, "x": 129, "y": 158, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783520, "x": 131, "y": 154, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783537, "x": 132, "y": 149, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783554, "x": 135, "y": 146, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783572, "x": 137, "y": 144, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783589, "x": 139, "y": 141, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783607, "x": 141, "y": 140, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783623, "x": 143, "y": 138, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783641, "x": 145, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783658, "x": 147, "y": 134, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783675, "x": 148, "y": 132, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783692, "x": 150, "y": 130, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783713, "x": 151, "y": 130, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783730, "x": 151, "y": 129, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783764, "x": 151, "y": 128, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783808, "x": 152, "y": 128, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215783824, "x": 152, "y": 127, "evt": { "button": 0 } },
{ "type": "up", "time": 1439215783993, "x": 152, "y": 127, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784080, "x": 152, "y": 129, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784099, "x": 152, "y": 133, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784114, "x": 152, "y": 141, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784131, "x": 150, "y": 159, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784148, "x": 146, "y": 178, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784166, "x": 144, "y": 194, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784183, "x": 144, "y": 213, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784208, "x": 144, "y": 225, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784217, "x": 144, "y": 231, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784234, "x": 144, "y": 239, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784252, "x": 143, "y": 247, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784268, "x": 143, "y": 252, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784286, "x": 140, "y": 256, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784303, "x": 139, "y": 259, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784321, "x": 139, "y": 262, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784338, "x": 138, "y": 264, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784355, "x": 137, "y": 266, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784373, "x": 137, "y": 267, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784390, "x": 137, "y": 269, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784417, "x": 137, "y": 270, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784434, "x": 137, "y": 271, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784451, "x": 137, "y": 274, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784469, "x": 136, "y": 276, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784485, "x": 135, "y": 278, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784503, "x": 135, "y": 281, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784519, "x": 134, "y": 283, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784537, "x": 134, "y": 285, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784554, "x": 134, "y": 286, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784580, "x": 134, "y": 287, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784609, "x": 134, "y": 288, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784656, "x": 133, "y": 288, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784673, "x": 133, "y": 289, "evt": { "button": 0 } },
{ "type": "down", "time": 1439215784704, "x": 133, "y": 289, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784784, "x": 133, "y": 288, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784800, "x": 133, "y": 287, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784817, "x": 134, "y": 283, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784852, "x": 147, "y": 261, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784868, "x": 158, "y": 248, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784889, "x": 165, "y": 235, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784917, "x": 172, "y": 222, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784919, "x": 180, "y": 208, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784937, "x": 184, "y": 194, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784953, "x": 184, "y": 185, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784970, "x": 185, "y": 177, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215784987, "x": 185, "y": 171, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785004, "x": 185, "y": 167, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785022, "x": 185, "y": 164, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785038, "x": 185, "y": 160, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785056, "x": 185, "y": 158, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785073, "x": 185, "y": 156, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785089, "x": 185, "y": 155, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785107, "x": 185, "y": 154, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785124, "x": 185, "y": 153, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785141, "x": 185, "y": 151, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785159, "x": 184, "y": 148, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785176, "x": 184, "y": 145, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785193, "x": 184, "y": 143, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785210, "x": 184, "y": 139, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785229, "x": 184, "y": 137, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785247, "x": 184, "y": 135, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785263, "x": 183, "y": 134, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785280, "x": 183, "y": 132, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785299, "x": 183, "y": 131, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785314, "x": 183, "y": 129, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785337, "x": 183, "y": 128, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785361, "x": 182, "y": 128, "evt": { "button": 0 } },
{ "type": "up", "time": 1439215785672, "x": 182, "y": 128, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785856, "x": 182, "y": 129, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785874, "x": 182, "y": 131, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785890, "x": 182, "y": 132, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785908, "x": 182, "y": 135, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785925, "x": 182, "y": 138, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785942, "x": 182, "y": 143, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785959, "x": 182, "y": 149, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785977, "x": 182, "y": 155, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215785994, "x": 182, "y": 159, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786010, "x": 182, "y": 161, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786030, "x": 182, "y": 163, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786047, "x": 182, "y": 165, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786063, "x": 182, "y": 166, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786081, "x": 182, "y": 170, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786100, "x": 180, "y": 177, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786114, "x": 177, "y": 184, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786132, "x": 174, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786148, "x": 172, "y": 194, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786165, "x": 170, "y": 196, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786182, "x": 168, "y": 198, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786199, "x": 165, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786216, "x": 163, "y": 206, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786234, "x": 159, "y": 210, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786251, "x": 153, "y": 213, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786268, "x": 150, "y": 217, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786284, "x": 148, "y": 219, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786301, "x": 145, "y": 221, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786325, "x": 143, "y": 225, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786336, "x": 141, "y": 227, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786407, "x": 141, "y": 228, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786425, "x": 141, "y": 229, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786442, "x": 140, "y": 230, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786459, "x": 140, "y": 231, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786476, "x": 140, "y": 232, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786494, "x": 140, "y": 233, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786543, "x": 140, "y": 234, "evt": { "button": 0 } },
{ "type": "down", "time": 1439215786735, "x": 140, "y": 234, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786816, "x": 140, "y": 233, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786833, "x": 140, "y": 232, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786850, "x": 140, "y": 231, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786867, "x": 140, "y": 229, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786884, "x": 140, "y": 226, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786901, "x": 140, "y": 223, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786918, "x": 140, "y": 220, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786935, "x": 140, "y": 216, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786953, "x": 140, "y": 212, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786970, "x": 143, "y": 208, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215786987, "x": 143, "y": 204, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787003, "x": 144, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787020, "x": 145, "y": 196, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787037, "x": 146, "y": 192, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787055, "x": 147, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787071, "x": 148, "y": 186, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787088, "x": 148, "y": 183, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787104, "x": 150, "y": 180, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787122, "x": 151, "y": 178, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787139, "x": 151, "y": 176, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787156, "x": 151, "y": 172, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787172, "x": 151, "y": 170, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787190, "x": 151, "y": 168, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787207, "x": 151, "y": 166, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787224, "x": 151, "y": 164, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787241, "x": 151, "y": 162, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787258, "x": 151, "y": 160, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787275, "x": 151, "y": 158, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787301, "x": 151, "y": 156, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787314, "x": 151, "y": 155, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787332, "x": 150, "y": 154, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787348, "x": 150, "y": 153, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787366, "x": 150, "y": 152, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787393, "x": 150, "y": 151, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787464, "x": 150, "y": 151, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787508, "x": 150, "y": 149, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787515, "x": 150, "y": 148, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215787671, "x": 150, "y": 147, "evt": { "button": 0 } },
{ "type": "up", "time": 1439215788056, "x": 150, "y": 147, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215788272, "x": 150, "y": 147, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215788289, "x": 150, "y": 148, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215788305, "x": 149, "y": 150, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215788322, "x": 145, "y": 155, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215788339, "x": 136, "y": 162, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215788356, "x": 113, "y": 173, "evt": { "button": 0 } },
{ "type": "move", "time": 1439215788401, "x": 31, "y": 198, "evt": { "button": 0 } },
{ "type": "out", "time": 1439215788409, "x": -14, "y": 210, "evt": { "button": 0 } }
]

View File

@@ -0,0 +1,155 @@
[
{ "type": "over", "time": 1439216575369, "x": 14, "y": 413, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575370, "x": 14, "y": 413, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575386, "x": 65, "y": 424, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575403, "x": 103, "y": 433, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575420, "x": 138, "y": 442, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575437, "x": 170, "y": 447, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575455, "x": 195, "y": 447, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575472, "x": 216, "y": 448, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575489, "x": 233, "y": 448, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575510, "x": 246, "y": 446, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575528, "x": 254, "y": 442, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575542, "x": 262, "y": 440, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575559, "x": 268, "y": 435, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575576, "x": 273, "y": 432, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575594, "x": 276, "y": 429, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575610, "x": 278, "y": 424, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575626, "x": 279, "y": 414, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575643, "x": 279, "y": 402, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575661, "x": 277, "y": 388, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575676, "x": 270, "y": 374, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575694, "x": 263, "y": 361, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575710, "x": 256, "y": 352, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575742, "x": 251, "y": 345, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575744, "x": 251, "y": 342, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216575762, "x": 250, "y": 341, "evt": { "button": 0 } },
{ "type": "down", "time": 1439216575915, "x": 250, "y": 341, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576055, "x": 209, "y": 293, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576057, "x": 190, "y": 279, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576074, "x": 177, "y": 266, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576092, "x": 168, "y": 258, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576108, "x": 160, "y": 250, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576125, "x": 156, "y": 243, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576142, "x": 152, "y": 237, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576159, "x": 148, "y": 232, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576175, "x": 142, "y": 227, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576192, "x": 138, "y": 222, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576212, "x": 135, "y": 216, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576227, "x": 132, "y": 212, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576244, "x": 128, "y": 209, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576261, "x": 125, "y": 207, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576278, "x": 124, "y": 206, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576295, "x": 123, "y": 205, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576313, "x": 122, "y": 204, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576330, "x": 122, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576347, "x": 121, "y": 202, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576364, "x": 119, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576381, "x": 118, "y": 199, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576398, "x": 117, "y": 198, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576416, "x": 115, "y": 196, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576434, "x": 113, "y": 195, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576449, "x": 111, "y": 192, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576467, "x": 110, "y": 190, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576483, "x": 110, "y": 189, "evt": { "button": 0 } },
{ "type": "up", "time": 1439216576651, "x": 110, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576842, "x": 111, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576861, "x": 112, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576883, "x": 113, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576922, "x": 114, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576939, "x": 115, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576959, "x": 116, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216576988, "x": 120, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577004, "x": 125, "y": 190, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577011, "x": 131, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577026, "x": 134, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577043, "x": 137, "y": 191, "evt": { "button": 0 } },
{ "type": "down", "time": 1439216577243, "x": 137, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577315, "x": 138, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577339, "x": 139, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577356, "x": 141, "y": 188, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577373, "x": 146, "y": 185, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577394, "x": 150, "y": 180, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577411, "x": 155, "y": 176, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577426, "x": 160, "y": 172, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577456, "x": 162, "y": 169, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577460, "x": 168, "y": 165, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577475, "x": 170, "y": 161, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577493, "x": 172, "y": 158, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577510, "x": 173, "y": 155, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577527, "x": 175, "y": 153, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577543, "x": 177, "y": 150, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577562, "x": 179, "y": 148, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577578, "x": 179, "y": 146, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577596, "x": 180, "y": 143, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577613, "x": 181, "y": 141, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577630, "x": 182, "y": 139, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577647, "x": 182, "y": 135, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577664, "x": 183, "y": 134, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577683, "x": 183, "y": 132, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577700, "x": 183, "y": 131, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577718, "x": 183, "y": 130, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577734, "x": 183, "y": 129, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577751, "x": 183, "y": 128, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216577768, "x": 183, "y": 127, "evt": { "button": 0 } },
{ "type": "up", "time": 1439216578034, "x": 183, "y": 127, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578227, "x": 183, "y": 128, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578244, "x": 183, "y": 140, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578260, "x": 181, "y": 157, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578277, "x": 179, "y": 180, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578294, "x": 179, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578311, "x": 177, "y": 228, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578328, "x": 177, "y": 257, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578355, "x": 177, "y": 286, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578362, "x": 174, "y": 309, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578379, "x": 170, "y": 325, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578395, "x": 170, "y": 329, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578412, "x": 170, "y": 332, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578430, "x": 170, "y": 334, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578466, "x": 170, "y": 335, "evt": { "button": 0 } },
{ "type": "down", "time": 1439216578562, "x": 170, "y": 335, "evt": { "button": 0 } },
{ "type": "up", "time": 1439216578722, "x": 170, "y": 335, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578858, "x": 170, "y": 334, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578876, "x": 170, "y": 329, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578893, "x": 169, "y": 323, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578910, "x": 167, "y": 310, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578928, "x": 161, "y": 292, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578943, "x": 155, "y": 275, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578959, "x": 148, "y": 261, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578977, "x": 141, "y": 249, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216578994, "x": 139, "y": 243, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579012, "x": 138, "y": 240, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579028, "x": 137, "y": 237, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579045, "x": 137, "y": 234, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579063, "x": 137, "y": 231, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579080, "x": 136, "y": 229, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579098, "x": 135, "y": 226, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579114, "x": 134, "y": 222, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579132, "x": 133, "y": 220, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579150, "x": 132, "y": 220, "evt": { "button": 0 } },
{ "type": "down", "time": 1439216579418, "x": 132, "y": 220, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579522, "x": 132, "y": 221, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579540, "x": 133, "y": 226, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579557, "x": 135, "y": 231, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579575, "x": 139, "y": 238, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579593, "x": 142, "y": 244, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579610, "x": 144, "y": 249, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579628, "x": 145, "y": 255, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579644, "x": 148, "y": 261, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579659, "x": 151, "y": 264, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579676, "x": 154, "y": 267, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579695, "x": 155, "y": 269, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579710, "x": 156, "y": 272, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579728, "x": 157, "y": 274, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579745, "x": 158, "y": 277, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579761, "x": 161, "y": 280, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579779, "x": 162, "y": 280, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216579797, "x": 162, "y": 281, "evt": { "button": 0 } },
{ "type": "up", "time": 1439216579938, "x": 162, "y": 281, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216580104, "x": 157, "y": 291, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216580117, "x": 140, "y": 309, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216580134, "x": 107, "y": 340, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216580151, "x": 65, "y": 373, "evt": { "button": 0 } },
{ "type": "move", "time": 1439216580169, "x": 15, "y": 402, "evt": { "button": 0 } },
{ "type": "out", "time": 1439216580186, "x": -34, "y": 422, "evt": { "button": 0 } }
]

View File

@@ -0,0 +1,212 @@
[
{ "type": "over", "time": 1405373847970, "x": 43, "y": 208, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373847970, "x": 43, "y": 208, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373847986, "x": 94, "y": 208, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848003, "x": 125, "y": 206, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848020, "x": 146, "y": 206, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848037, "x": 157, "y": 204, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848054, "x": 160, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848072, "x": 162, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848110, "x": 162, "y": 202, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848112, "x": 162, "y": 201, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848129, "x": 162, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848146, "x": 163, "y": 199, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848168, "x": 163, "y": 198, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848192, "x": 163, "y": 197, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848240, "x": 163, "y": 196, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848256, "x": 163, "y": 195, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848273, "x": 162, "y": 194, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848290, "x": 162, "y": 192, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848308, "x": 161, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848325, "x": 160, "y": 189, "evt": { "button": 0 } },
{ "type": "down", "time": 1405373848400, "x": 160, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848456, "x": 160, "y": 188, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848473, "x": 160, "y": 186, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848489, "x": 161, "y": 182, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848507, "x": 163, "y": 178, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848523, "x": 165, "y": 172, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848540, "x": 170, "y": 162, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848557, "x": 172, "y": 156, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848575, "x": 179, "y": 147, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848591, "x": 180, "y": 141, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848609, "x": 180, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848626, "x": 181, "y": 134, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848643, "x": 181, "y": 133, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848660, "x": 181, "y": 132, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848676, "x": 181, "y": 131, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848696, "x": 181, "y": 130, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848713, "x": 182, "y": 130, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848730, "x": 182, "y": 128, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848747, "x": 183, "y": 127, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848764, "x": 183, "y": 125, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848782, "x": 184, "y": 125, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848799, "x": 184, "y": 122, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848816, "x": 185, "y": 121, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848832, "x": 186, "y": 120, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373848849, "x": 186, "y": 119, "evt": { "button": 0 } },
{ "type": "up", "time": 1405373849056, "x": 186, "y": 119, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849064, "x": 186, "y": 118, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849128, "x": 186, "y": 119, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849146, "x": 185, "y": 126, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849163, "x": 181, "y": 137, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849180, "x": 174, "y": 158, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849197, "x": 166, "y": 187, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849214, "x": 159, "y": 217, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849231, "x": 152, "y": 241, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849248, "x": 148, "y": 262, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849265, "x": 148, "y": 276, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849281, "x": 147, "y": 280, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849298, "x": 144, "y": 285, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849315, "x": 144, "y": 288, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849332, "x": 144, "y": 290, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849350, "x": 143, "y": 291, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849367, "x": 141, "y": 292, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849384, "x": 141, "y": 295, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849401, "x": 140, "y": 295, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849528, "x": 140, "y": 294, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849559, "x": 140, "y": 293, "evt": { "button": 0 } },
{ "type": "down", "time": 1405373849568, "x": 140, "y": 293, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849616, "x": 140, "y": 292, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849632, "x": 141, "y": 290, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849649, "x": 143, "y": 286, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849666, "x": 149, "y": 279, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849683, "x": 156, "y": 268, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849700, "x": 167, "y": 254, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849717, "x": 176, "y": 239, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849734, "x": 187, "y": 224, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849752, "x": 194, "y": 212, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849769, "x": 197, "y": 204, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849786, "x": 198, "y": 198, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849803, "x": 199, "y": 192, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849821, "x": 201, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849837, "x": 201, "y": 187, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849854, "x": 201, "y": 184, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849871, "x": 203, "y": 183, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849888, "x": 203, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849905, "x": 203, "y": 180, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849922, "x": 203, "y": 179, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849944, "x": 203, "y": 178, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849961, "x": 204, "y": 177, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849978, "x": 204, "y": 176, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373849995, "x": 204, "y": 175, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850016, "x": 204, "y": 174, "evt": { "button": 0 } },
{ "type": "up", "time": 1405373850168, "x": 204, "y": 174, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850248, "x": 204, "y": 175, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850265, "x": 201, "y": 182, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850281, "x": 196, "y": 194, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850298, "x": 188, "y": 214, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850315, "x": 174, "y": 245, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850332, "x": 159, "y": 281, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850350, "x": 146, "y": 317, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850367, "x": 136, "y": 346, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850384, "x": 130, "y": 361, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850402, "x": 129, "y": 367, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850419, "x": 129, "y": 372, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850436, "x": 128, "y": 375, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850453, "x": 127, "y": 378, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850469, "x": 125, "y": 380, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850487, "x": 124, "y": 381, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850504, "x": 124, "y": 383, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850522, "x": 123, "y": 384, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850539, "x": 122, "y": 385, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850560, "x": 122, "y": 386, "evt": { "button": 0 } },
{ "type": "down", "time": 1405373850656, "x": 122, "y": 386, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850720, "x": 122, "y": 386, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850737, "x": 122, "y": 382, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850754, "x": 126, "y": 372, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850771, "x": 132, "y": 353, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850788, "x": 140, "y": 326, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850804, "x": 151, "y": 289, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850821, "x": 157, "y": 259, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850838, "x": 161, "y": 232, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850855, "x": 167, "y": 207, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850872, "x": 167, "y": 186, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850889, "x": 169, "y": 167, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850906, "x": 169, "y": 155, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850923, "x": 169, "y": 144, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850940, "x": 169, "y": 139, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850958, "x": 169, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850976, "x": 169, "y": 133, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373850992, "x": 169, "y": 130, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851009, "x": 169, "y": 128, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851026, "x": 169, "y": 123, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851043, "x": 169, "y": 118, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851059, "x": 167, "y": 113, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851077, "x": 167, "y": 107, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851093, "x": 167, "y": 103, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851111, "x": 166, "y": 99, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851128, "x": 165, "y": 97, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851145, "x": 165, "y": 96, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851168, "x": 165, "y": 95, "evt": { "button": 0 } },
{ "type": "up", "time": 1405373851544, "x": 165, "y": 95, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851672, "x": 164, "y": 95, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851690, "x": 164, "y": 100, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851705, "x": 164, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851722, "x": 163, "y": 105, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851740, "x": 163, "y": 107, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851757, "x": 162, "y": 111, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851774, "x": 162, "y": 113, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851796, "x": 161, "y": 115, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851809, "x": 160, "y": 119, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851826, "x": 160, "y": 120, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851872, "x": 160, "y": 121, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851889, "x": 164, "y": 129, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851905, "x": 177, "y": 141, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851922, "x": 201, "y": 151, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851940, "x": 226, "y": 159, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851957, "x": 249, "y": 163, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851974, "x": 268, "y": 163, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373851991, "x": 283, "y": 165, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852008, "x": 293, "y": 167, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852025, "x": 297, "y": 168, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852042, "x": 299, "y": 168, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852104, "x": 299, "y": 169, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852128, "x": 299, "y": 170, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852145, "x": 299, "y": 171, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852168, "x": 299, "y": 172, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852185, "x": 298, "y": 173, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852201, "x": 296, "y": 175, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852219, "x": 292, "y": 178, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852236, "x": 288, "y": 180, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852253, "x": 281, "y": 183, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852270, "x": 276, "y": 185, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852287, "x": 272, "y": 188, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852305, "x": 271, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852322, "x": 270, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852384, "x": 270, "y": 190, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852401, "x": 270, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852432, "x": 270, "y": 192, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852449, "x": 270, "y": 193, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852466, "x": 270, "y": 194, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852484, "x": 269, "y": 194, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852501, "x": 269, "y": 195, "evt": { "button": 0 } },
{ "type": "down", "time": 1405373852528, "x": 269, "y": 195, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852561, "x": 268, "y": 195, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852586, "x": 267, "y": 195, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852601, "x": 261, "y": 194, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852617, "x": 252, "y": 193, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852634, "x": 234, "y": 188, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852651, "x": 205, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852668, "x": 171, "y": 173, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852685, "x": 139, "y": 165, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852702, "x": 107, "y": 158, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852719, "x": 78, "y": 153, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852736, "x": 61, "y": 150, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852753, "x": 50, "y": 147, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852770, "x": 44, "y": 146, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852787, "x": 40, "y": 145, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852804, "x": 35, "y": 145, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852821, "x": 31, "y": 143, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852838, "x": 26, "y": 142, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852855, "x": 21, "y": 141, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852872, "x": 17, "y": 140, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852889, "x": 14, "y": 139, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852906, "x": 12, "y": 137, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852924, "x": 10, "y": 135, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373852942, "x": 9, "y": 135, "evt": { "button": 0 } },
{ "type": "up", "time": 1405373853176, "x": 9, "y": 135, "evt": { "button": 0 } },
{ "type": "cut", "time": 1405373853518 },
{ "type": "move", "time": 1405373853800, "x": 8, "y": 135, "evt": { "button": 0 } },
{ "type": "move", "time": 1405373853817, "x": 4, "y": 135, "evt": { "button": 0 } },
{ "type": "out", "time": 1405373853835, "x": -10, "y": 137, "evt": { "button": 0 } }
]

View File

@@ -0,0 +1,114 @@
[
{ "type": "over", "time": 1412929236006, "x": 13, "y": 197, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236008, "x": 13, "y": 197, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236023, "x": 20, "y": 197, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236040, "x": 24, "y": 198, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236057, "x": 27, "y": 199, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236075, "x": 29, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236091, "x": 33, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236108, "x": 41, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236126, "x": 57, "y": 202, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236144, "x": 74, "y": 202, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236162, "x": 89, "y": 204, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236178, "x": 99, "y": 204, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236195, "x": 102, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236212, "x": 103, "y": 203, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236230, "x": 103, "y": 202, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236278, "x": 105, "y": 201, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236291, "x": 106, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236309, "x": 107, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236345, "x": 108, "y": 200, "evt": { "button": 0 } },
{ "type": "down", "time": 1412929236505, "x": 108, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236561, "x": 109, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236586, "x": 111, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236603, "x": 113, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236620, "x": 119, "y": 200, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236637, "x": 126, "y": 201, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236654, "x": 135, "y": 201, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236672, "x": 145, "y": 201, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236689, "x": 152, "y": 201, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236707, "x": 159, "y": 199, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236725, "x": 161, "y": 198, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236750, "x": 163, "y": 195, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236758, "x": 165, "y": 193, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236776, "x": 166, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236792, "x": 167, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236809, "x": 167, "y": 187, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236826, "x": 167, "y": 185, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236844, "x": 168, "y": 181, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236860, "x": 168, "y": 178, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236878, "x": 168, "y": 173, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236894, "x": 168, "y": 166, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236911, "x": 166, "y": 161, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236929, "x": 165, "y": 155, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236947, "x": 161, "y": 150, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236964, "x": 160, "y": 147, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236980, "x": 158, "y": 143, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929236997, "x": 157, "y": 142, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237014, "x": 156, "y": 141, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237041, "x": 156, "y": 141, "evt": { "button": 0 } },
{ "type": "up", "time": 1412929237153, "x": 156, "y": 141, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237289, "x": 160, "y": 142, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237307, "x": 174, "y": 146, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237324, "x": 192, "y": 152, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237341, "x": 217, "y": 157, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237358, "x": 238, "y": 164, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237376, "x": 253, "y": 169, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237393, "x": 264, "y": 173, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237411, "x": 268, "y": 176, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237428, "x": 271, "y": 180, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237445, "x": 273, "y": 185, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237462, "x": 275, "y": 187, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237479, "x": 276, "y": 189, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237497, "x": 277, "y": 190, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237515, "x": 277, "y": 191, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237532, "x": 277, "y": 192, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237548, "x": 278, "y": 193, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237569, "x": 278, "y": 194, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237601, "x": 278, "y": 195, "evt": { "button": 0 } },
{ "type": "down", "time": 1412929237634, "x": 278, "y": 195, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237641, "x": 278, "y": 196, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237673, "x": 277, "y": 196, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237690, "x": 276, "y": 196, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237707, "x": 263, "y": 193, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237726, "x": 244, "y": 185, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237741, "x": 213, "y": 173, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237758, "x": 184, "y": 163, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237776, "x": 165, "y": 155, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237793, "x": 149, "y": 149, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237811, "x": 142, "y": 145, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237829, "x": 140, "y": 143, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237846, "x": 138, "y": 143, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237863, "x": 138, "y": 142, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237880, "x": 138, "y": 141, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237897, "x": 137, "y": 140, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237914, "x": 133, "y": 138, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237931, "x": 131, "y": 137, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237948, "x": 130, "y": 135, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237965, "x": 129, "y": 135, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929237983, "x": 129, "y": 134, "evt": { "button": 0 } },
{ "type": "up", "time": 1412929238226, "x": 129, "y": 134, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238234, "x": 128, "y": 134, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238265, "x": 128, "y": 133, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238284, "x": 130, "y": 133, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238301, "x": 134, "y": 131, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238318, "x": 144, "y": 127, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238334, "x": 157, "y": 120, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238352, "x": 180, "y": 115, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238369, "x": 207, "y": 106, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238386, "x": 236, "y": 99, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238403, "x": 259, "y": 97, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238420, "x": 271, "y": 95, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238438, "x": 274, "y": 94, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929238455, "x": 275, "y": 94, "evt": { "button": 0 } },
{ "type": "cut", "time": 1412929238856 },
{ "type": "move", "time": 1412929239729, "x": 274, "y": 94, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929239747, "x": 271, "y": 95, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929239764, "x": 262, "y": 96, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929239781, "x": 239, "y": 102, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929239798, "x": 204, "y": 105, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929239816, "x": 150, "y": 105, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929239833, "x": 86, "y": 109, "evt": { "button": 0 } },
{ "type": "move", "time": 1412929239851, "x": 30, "y": 117, "evt": { "button": 0 } },
{ "type": "out", "time": 1412929239867, "x": -31, "y": 126, "evt": { "button": 0 } }
]

View File

@@ -0,0 +1,56 @@
[
{ "type": "over", "time": 1404236456261, "x": 4, "y": 146, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456262, "x": 4, "y": 146, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456278, "x": 9, "y": 146, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456295, "x": 11, "y": 145, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456395, "x": 11, "y": 144, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456411, "x": 11, "y": 143, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456429, "x": 11, "y": 141, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456447, "x": 12, "y": 139, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456464, "x": 13, "y": 137, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456481, "x": 14, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456499, "x": 15, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456515, "x": 17, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456533, "x": 20, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456550, "x": 23, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456567, "x": 27, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456584, "x": 28, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456601, "x": 31, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456619, "x": 32, "y": 135, "evt": { "button": 0 } },
{ "type": "down", "time": 1404236456681, "x": 32, "y": 135, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456843, "x": 33, "y": 136, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456860, "x": 39, "y": 144, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456877, "x": 49, "y": 157, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456894, "x": 68, "y": 179, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456911, "x": 90, "y": 204, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456928, "x": 116, "y": 232, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456946, "x": 143, "y": 259, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456970, "x": 161, "y": 278, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456980, "x": 172, "y": 289, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236456997, "x": 184, "y": 299, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457014, "x": 191, "y": 306, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457031, "x": 194, "y": 310, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457049, "x": 198, "y": 312, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457066, "x": 203, "y": 315, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457083, "x": 206, "y": 317, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457100, "x": 209, "y": 319, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457117, "x": 211, "y": 321, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457134, "x": 213, "y": 322, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457151, "x": 215, "y": 324, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457168, "x": 216, "y": 325, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457186, "x": 216, "y": 326, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457205, "x": 216, "y": 326, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457230, "x": 217, "y": 327, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236457253, "x": 217, "y": 328, "evt": { "button": 0 } },
{ "type": "up", "time": 1404236457445, "x": 217, "y": 328, "evt": { "button": 0 } },
{ "type": "cut", "time": 1404236457973 },
{ "type": "paste", "time": 1404236458813 },
{ "type": "move", "time": 1404236459383, "x": 214, "y": 328, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236459400, "x": 201, "y": 331, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236459417, "x": 185, "y": 332, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236459434, "x": 150, "y": 332, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236459451, "x": 100, "y": 332, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236459469, "x": 48, "y": 327, "evt": { "button": 0 } },
{ "type": "move", "time": 1404236459486, "x": 7, "y": 321, "evt": { "button": 0 } },
{ "type": "out", "time": 1404236459503, "x": -52, "y": 317, "evt": { "button": 0 } }
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 MiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="51px" height="19px" viewBox="0 0 51 19" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>badge/1 HR icon Copy 5</title>
<desc>Created with Sketch.</desc>
<g id="Prototype-Flow-2-|-Prioritized" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="FLOW-3---Screen-1" transform="translate(-16.000000, -298.000000)">
<g id="Group-12" transform="translate(16.000000, 294.000000)">
<g id="badge/1-HR-icon" transform="translate(0.000000, 4.000000)">
<rect id="background" fill="#FFFFFF" x="0" y="0" width="51" height="19" rx="3"></rect>
<rect id="background" fill="#0DBA7F" opacity="0.149999991" x="0" y="0" width="51" height="19" rx="3"></rect>
<g id="Group-10" stroke-width="1" transform="translate(-2.000000, 3.000000)" fill="#047850" font-size="11" font-weight="normal" line-spacing="12">
<text id="text---title" font-family="Helvetica" letter-spacing="-0.2">
<tspan x="20" y="10">1 hour</tspan>
</text>
<text id="text---title-copy" font-family="LastResort, .LastResort" letter-spacing="-0.3">
<tspan x="6" y="9"></tspan>
</text>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>18-clock-black</title>
<g id="18-clock-black" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="🎨Edit-color-here!-2">
<rect id="boundry" x="0" y="0" width="18" height="18"></rect>
<path d="M9,17 C7.89816299,17 6.86081073,16.7890131 5.88791209,16.367033 C4.91501345,15.9450528 4.06813547,15.37363 3.34725275,14.6527473 C2.62637002,13.9318645 2.05494716,13.0849865 1.63296703,12.1120879 C1.2109869,11.1391893 1,10.101837 1,9 C1,7.89816299 1.2109869,6.86081073 1.63296703,5.88791209 C2.05494716,4.91501345 2.62637002,4.06813547 3.34725275,3.34725275 C4.06813547,2.62637002 4.91501345,2.05494716 5.88791209,1.63296703 C6.86081073,1.2109869 7.89816299,1 9,1 C10.101837,1 11.1391893,1.2109869 12.1120879,1.63296703 C13.0849865,2.05494716 13.9318645,2.62637002 14.6527473,3.34725275 C15.37363,4.06813547 15.9450528,4.91501345 16.367033,5.88791209 C16.7890131,6.86081073 17,7.89816299 17,9 C17,10.101837 16.7890131,11.1391893 16.367033,12.1120879 C15.9450528,13.0849865 15.37363,13.9318645 14.6527473,14.6527473 C13.9318645,15.37363 13.0849865,15.9450528 12.1120879,16.367033 C11.1391893,16.7890131 10.101837,17 9,17 Z M9,2.0021978 C8.03882303,2.0021978 7.13040721,2.18388097 6.27472527,2.54725275 C5.43076501,2.91062453 4.68938048,3.41171842 4.05054945,4.05054945 C3.41171842,4.68938048 2.91062453,5.43076501 2.54725275,6.27472527 C2.18388097,7.13040721 2.0021978,8.03882303 2.0021978,9 C2.0021978,9.96117697 2.18388097,10.8695928 2.54725275,11.7252747 C2.91062453,12.569235 3.41171842,13.3106195 4.05054945,13.9494505 C4.68938048,14.5882816 5.43076501,15.0893755 6.27472527,15.4527473 C7.13040721,15.816119 8.03882303,15.9978022 9,15.9978022 C9.96117697,15.9978022 10.8695928,15.816119 11.7252747,15.4527473 C12.569235,15.0893755 13.3106195,14.5882816 13.9494505,13.9494505 C14.5882816,13.3106195 15.0893755,12.569235 15.4527473,11.7252747 C15.816119,10.8695928 15.9978022,9.96117697 15.9978022,9 C15.9978022,8.03882303 15.816119,7.13040721 15.4527473,6.27472527 C15.0893755,5.43076501 14.5882816,4.68938048 13.9494505,4.05054945 C13.3106195,3.41171842 12.569235,2.91062453 11.7252747,2.54725275 C10.8695928,2.18388097 9.96117697,2.0021978 9,2.0021978 Z M9,5.5010989 C9,5.36043886 9.04981635,5.24029354 9.14945055,5.14065934 C9.24908475,5.04102514 9.36336932,4.99120879 9.49230769,4.99120879 C9.63296774,4.99120879 9.75311305,5.04102514 9.85274725,5.14065934 C9.95238145,5.24029354 10.0021978,5.36043886 10.0021978,5.5010989 L10.0021978,10.0021978 L6.5032967,10.0021978 C6.36263666,10.0021978 6.24249134,9.95238145 6.14285714,9.85274725 C6.04322295,9.75311305 5.99340659,9.63296774 5.99340659,9.49230769 C5.99340659,9.36336932 6.04322295,9.24908475 6.14285714,9.14945055 C6.24249134,9.04981635 6.36263666,9 6.5032967,9 L9,9 L9,5.5010989 Z" id="🎨Edit-color-here!" fill="#000000"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

Some files were not shown because too many files have changed in this diff Show More