mirror of
https://github.com/xodio/xod.git
synced 2026-03-13 20:26:55 +01:00
refactor(infra): kill xod-core package
This commit is contained in:
@@ -51,12 +51,12 @@ Maintenance Scripts
|
||||
|
||||
* `yarn run build` builds all packages
|
||||
* `yarn run build -- <name> --only` builds a package with specified `<name>`,
|
||||
e.g. `yarn run build -- xod-core --only`
|
||||
e.g. `yarn run build -- xod-cli --only`
|
||||
* `yarn run build -- <name>` builds a package with specified `<name>`
|
||||
and all its dependencies, e.g. `yarn run build -- xod-client-electron`
|
||||
* `yarn run dev -- <name> --only` builds a package with specified `<name>` and
|
||||
stays in watch mode with auto-rebuild when its files change,
|
||||
e.g. `yarn run dev -- xod-core --only`
|
||||
e.g. `yarn run dev -- xod-cli --only`
|
||||
* `yarn run dev -- <name>` builds a package with specified `<name>` and all
|
||||
its dependencies, then stay in watch mode looking for changes in that
|
||||
package or any of its dependencies;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"presets": ["es2015"]
|
||||
}
|
||||
4
packages/xod-core/.gitignore
vendored
4
packages/xod-core/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log
|
||||
fs-temp/
|
||||
@@ -1,3 +0,0 @@
|
||||
# xod-core
|
||||
|
||||
FIXME: write a README
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
label: 'and',
|
||||
inputs: [{
|
||||
key: 'a',
|
||||
type: 'bool',
|
||||
description: 'First input'
|
||||
},
|
||||
{
|
||||
key: 'b',
|
||||
type: 'bool',
|
||||
description: 'Second input'
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'out',
|
||||
type: 'bool',
|
||||
description: '"true" if both inputs are "true"',
|
||||
}],
|
||||
impl: {
|
||||
js: "\nmodule.exports.evaluate = function(e) {\n return { out: e.inputs.a && e.inputs.b };\n};",
|
||||
},
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
category: 'hardware',
|
||||
outputs: [{
|
||||
key: 'state',
|
||||
type: 'bool',
|
||||
description: 'Emits `true` when pressed and `false` when released',
|
||||
}],
|
||||
properties: [{
|
||||
key: 'pin',
|
||||
label: 'Pin',
|
||||
type: 'string',
|
||||
value: 'BTN1',
|
||||
}],
|
||||
impl: {
|
||||
espruino: "\nmodule.exports.setup = function(e) {\n var pin = new Pin(e.props.pin);\n\n setWatch(function(evt) {\n e.fire({ state: !evt.state });\n }, pin, {\n edge: 'both',\n repeat: true,\n debounce: 30\n });\n};\n",
|
||||
},
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
label: 'Buzzer',
|
||||
category: 'hardware',
|
||||
inputs: [{
|
||||
key: 'freq',
|
||||
type: 'number',
|
||||
pinLabel: 'FREQ',
|
||||
label: 'Frequency',
|
||||
description: 'Frequency (Hz)'
|
||||
}],
|
||||
properties: [{
|
||||
key: 'pin',
|
||||
label: 'Pin',
|
||||
type: 'string',
|
||||
value: 'A3',
|
||||
}],
|
||||
impl: {
|
||||
espruino: "\nmodule.exports.setup = function(e) {\n e.context.pin = new Pin(e.props.pin);\n};\n\nmodule.exports.evaluate = function(e) {\n var f = e.inputs.freq;\n\n if (f === 0) {\n digitalWrite(e.context.pin, false);\n } else {\n analogWrite(e.context.pin, 0.5, { freq: f });\n }\n};\n",
|
||||
},
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
inputs: [{
|
||||
key: 'interval',
|
||||
pinLabel: 'INT',
|
||||
label: 'Interval (sec)',
|
||||
type: 'number',
|
||||
value: 1,
|
||||
}],
|
||||
pure: false,
|
||||
outputs: [{
|
||||
key: 'tick',
|
||||
type: 'pulse',
|
||||
description: 'Emits pulse periodically',
|
||||
}],
|
||||
impl: {
|
||||
js: "\nmodule.exports.setup = function(e) {\n e.context.intId = null;\n};\n\nmodule.exports.evaluate = function(e) {\n if (e.context.intId) {\n clearInterval(e.context.intId);\n }\n e.context.intId = setInterval(function() {\n e.fire({ tick: PULSE });\n }, e.inputs.interval * 1000);\n};",
|
||||
},
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
inputs: [{
|
||||
key: 'a',
|
||||
pinLabel: 'IN1',
|
||||
label: 'First value',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
key: 'b',
|
||||
pinLabel: 'IN2',
|
||||
label: 'Second value',
|
||||
type: 'number',
|
||||
}
|
||||
],
|
||||
outputs: [{
|
||||
key: 'equal',
|
||||
type: 'pulse',
|
||||
pinLabel: '==',
|
||||
description: 'Pulses if a = b',
|
||||
},
|
||||
{
|
||||
key: 'less',
|
||||
type: 'pulse',
|
||||
pinLabel: '<',
|
||||
description: 'Pulses if a < b',
|
||||
},
|
||||
{
|
||||
key: 'greater',
|
||||
type: 'pulse',
|
||||
pinLabel: '>',
|
||||
description: 'Pulses if a > b',
|
||||
}],
|
||||
impls: {
|
||||
js: "\nmodule.exports.evaluate = function(e) {\n var a = e.inputs.a;\n var b = e.inputs.b;\n if (a < b) {\n return { less: PULSE };\n } else if (a > b) {\n return { greater: PULSE };\n } else {\n return { equal: PULSE };\n }\n};",
|
||||
},
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
label: '<Bool>',
|
||||
category: 'configuration',
|
||||
inputs: [{
|
||||
key: 'inValue',
|
||||
type: 'bool',
|
||||
injected: true,
|
||||
pinLabel: 'VAL',
|
||||
label: 'Value',
|
||||
value: false
|
||||
}, {
|
||||
key: 'pulse',
|
||||
type: 'pulse',
|
||||
injected: true,
|
||||
pinLabel: 'PLS',
|
||||
label: 'Pulse'
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'value',
|
||||
type: 'bool',
|
||||
}],
|
||||
impl: {
|
||||
js: "\nmodule.exports.evaluate = function(e) {\n e.fire({ value: e.inputs.value });\n};",
|
||||
},
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
label: '<Number>',
|
||||
category: 'configuration',
|
||||
inputs: [{
|
||||
key: 'inValue',
|
||||
type: 'number',
|
||||
injected: true,
|
||||
pinLabel: 'VAL',
|
||||
label: 'Value',
|
||||
value: 0
|
||||
}, {
|
||||
key: 'pulse',
|
||||
type: 'pulse',
|
||||
injected: true,
|
||||
pinLabel: 'PLS',
|
||||
label: 'Pulse'
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'value',
|
||||
type: 'number',
|
||||
}],
|
||||
impl: {
|
||||
js: "\nmodule.exports.evaluate = function(e) {\n e.fire({ value: e.inputs.value });\n};",
|
||||
},
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
label: '<String>',
|
||||
category: 'configuration',
|
||||
inputs: [{
|
||||
key: 'inValue',
|
||||
type: 'string',
|
||||
injected: true,
|
||||
pinLabel: 'VAL',
|
||||
label: 'Value',
|
||||
value: ''
|
||||
}, {
|
||||
key: 'pulse',
|
||||
type: 'pulse',
|
||||
injected: true,
|
||||
pinLabel: 'PLS',
|
||||
label: 'Pulse'
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'value',
|
||||
type: 'string',
|
||||
}],
|
||||
impl: {
|
||||
js: "\nmodule.exports.evaluate = function(e) {\n e.fire({ value: e.inputs.value });\n};",
|
||||
},
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
inputs: [{
|
||||
key: 'inp',
|
||||
type: 'bool',
|
||||
pinLabel: 'IN',
|
||||
label: 'Value',
|
||||
description: 'Selector value'
|
||||
}, {
|
||||
key: 'trueValue',
|
||||
type: 'number',
|
||||
label: 'Value if true',
|
||||
pinLabel: 'T',
|
||||
label: 'True output',
|
||||
value: 1,
|
||||
description: 'Output if selector is `true`'
|
||||
}, {
|
||||
key: 'falseValue',
|
||||
type: 'number',
|
||||
label: 'Value if false',
|
||||
pinLabel: 'F',
|
||||
label: 'False output',
|
||||
value: 0,
|
||||
description: 'Output if selector is `false`'
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'out',
|
||||
type: 'number',
|
||||
description: 'Selected output value',
|
||||
}],
|
||||
impl: {
|
||||
js: "\nmodule.exports.evaluate = function(e) {\n var out = e.inputs.inp ? e.inputs.trueValue : e.inputs.falseValue;\n return { out: out };\n};\n",
|
||||
},
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
label: '<InputBool>',
|
||||
category: 'io',
|
||||
outputs: [{
|
||||
key: 'PIN',
|
||||
type: 'bool',
|
||||
}],
|
||||
properties: [{
|
||||
key: 'pinLabel',
|
||||
type: 'string',
|
||||
widget: 'IOLabel',
|
||||
label: 'Pin label',
|
||||
value: '',
|
||||
}]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
label: '<InputNumber>',
|
||||
category: 'io',
|
||||
outputs: [{
|
||||
key: 'PIN',
|
||||
type: 'number',
|
||||
}],
|
||||
properties: [{
|
||||
key: 'pinLabel',
|
||||
type: 'string',
|
||||
widget: 'IOLabel',
|
||||
label: 'Pin label',
|
||||
value: '',
|
||||
}]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
label: '<InputPulse>',
|
||||
category: 'io',
|
||||
outputs: [{
|
||||
key: 'PIN',
|
||||
type: 'pulse',
|
||||
}],
|
||||
properties: [{
|
||||
key: 'label',
|
||||
label: 'Pin label',
|
||||
type: 'io_label',
|
||||
defaultValue: 'InP',
|
||||
}]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
label: '<InputString>',
|
||||
category: 'io',
|
||||
outputs: [{
|
||||
key: 'PIN',
|
||||
type: 'string',
|
||||
}],
|
||||
properties: [{
|
||||
key: 'pinLabel',
|
||||
type: 'string',
|
||||
widget: 'IOLabel',
|
||||
label: 'Pin label',
|
||||
value: '',
|
||||
}]
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
inputs: [{
|
||||
key: 'toggle',
|
||||
type: 'pulse',
|
||||
pinLabel: 'TGL',
|
||||
label: 'Toggle pulse',
|
||||
description: 'Flips current state to opposite'
|
||||
}, {
|
||||
key: 'set',
|
||||
type: 'pulse',
|
||||
pinLabel: 'SET',
|
||||
label: 'Set pulse',
|
||||
description: 'Sets current state to `true`'
|
||||
}, {
|
||||
key: 'reset',
|
||||
type: 'pulse',
|
||||
modes: 'pin',
|
||||
pinLabel: 'RST',
|
||||
label: 'Reset pulse',
|
||||
description: 'Sets current state to `false`'
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'state',
|
||||
type: 'bool',
|
||||
description: 'Emits current latch state when changes',
|
||||
}],
|
||||
impl: {
|
||||
js: "\nmodule.exports.evaluate = function(e) {\n var inputs = e.inputs;\n var newState;\n\n if (inputs.toggle) {\n newState = !e.context.state;\n } else if (inputs.set) {\n newState = true;\n } else /* if (inputs.reset) */ {\n newState = false;\n }\n\n e.context.state = newState;\n return { state: newState };\n};",
|
||||
},
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
label: 'LED',
|
||||
category: 'hardware',
|
||||
inputs: [{
|
||||
key: 'brightness',
|
||||
type: 'number',
|
||||
label: 'Brightness',
|
||||
value: 0,
|
||||
description: 'Shine brightness'
|
||||
}],
|
||||
properties: [{
|
||||
key: 'pin',
|
||||
label: 'Pin',
|
||||
type: 'string',
|
||||
value: 'LED1',
|
||||
}],
|
||||
impl: {
|
||||
espruino: "\nmodule.exports.setup = function(e) {\n e.context.pin = new Pin(e.props.pin);\n};\n\nmodule.exports.evaluate = function(e) {\n var b = e.inputs.brightness;\n\n // Adjust duty cycle as a power function to align brightness\n // perception by human eye\n var duty = b * b * b;\n\n analogWrite(e.context.pin, duty);\n};\n",
|
||||
},
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
{
|
||||
inputs: [{
|
||||
key: 'inp',
|
||||
type: 'number',
|
||||
pinLabel: 'IN',
|
||||
label: 'Input',
|
||||
value: 0,
|
||||
description: 'Input value to map'
|
||||
}, {
|
||||
key: 'inA',
|
||||
type: 'number',
|
||||
injected: true,
|
||||
pinLabel: 'As',
|
||||
label: 'Input range start',
|
||||
value: 0,
|
||||
description: 'Input range start'
|
||||
}, {
|
||||
key: 'inB',
|
||||
type: 'number',
|
||||
injected: true,
|
||||
pinLabel: 'Bs',
|
||||
label: 'Input range end',
|
||||
value: 0,
|
||||
description: 'Input range end'
|
||||
}, {
|
||||
key: 'outA',
|
||||
type: 'number',
|
||||
injected: true,
|
||||
pinLabel: 'At',
|
||||
label: 'Output range start',
|
||||
value: 0,
|
||||
description: 'Output range start'
|
||||
}, {
|
||||
key: 'outB',
|
||||
type: 'number',
|
||||
injected: true,
|
||||
pinLabel: 'Bt',
|
||||
label: 'Output range end',
|
||||
value: 0,
|
||||
description: 'Output range end'
|
||||
}, {
|
||||
key: 'clip',
|
||||
type: 'bool',
|
||||
injected: true,
|
||||
pinLabel: 'CL',
|
||||
label: 'Clip',
|
||||
value: false,
|
||||
description: 'Clip result to output range'
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'out',
|
||||
type: 'number',
|
||||
description: 'Mapped value',
|
||||
}],
|
||||
impl: {
|
||||
js: "\nmodule.exports.evaluate = function(e) {\n var inputs = e.inputs;\n var k = (inputs.inp - inputs.inA) / (inputs.inB - inputs.inA);\n var out = inputs.outA + k * (inputs.outB - inputs.outA);\n\n if (inputs.clip) {\n if (inputs.outB > inputs.outA) {\n out = Math.max(inputs.outA, out);\n out = Math.min(inputs.outB, out);\n } else {\n out = Math.max(inputs.outB, out);\n out = Math.min(inputs.outA, out);\n }\n }\n\n return { out: out };\n};\n",
|
||||
},
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
label: 'not',
|
||||
inputs: [{
|
||||
key: 'in',
|
||||
type: 'bool',
|
||||
label: 'Input',
|
||||
description: 'Value to be inverted'
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'out',
|
||||
type: 'bool',
|
||||
description: 'Resulting inverted value',
|
||||
}],
|
||||
impl: {
|
||||
js: "\nmodule.exports.evaluate = function(e) {\n return { out: !e.inputs.in };\n};",
|
||||
},
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
label: 'or',
|
||||
inputs: [{
|
||||
key: 'a',
|
||||
type: 'bool',
|
||||
description: 'First input'
|
||||
},
|
||||
{
|
||||
key: 'b',
|
||||
type: 'bool',
|
||||
description: 'Second input'
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'out',
|
||||
type: 'bool',
|
||||
description: '"true" if a least one of inputs is "true"',
|
||||
}],
|
||||
impl: {
|
||||
js: "\nmodule.exports.evaluate = function(e) {\n return { out: e.inputs.a || e.inputs.b };\n};\n",
|
||||
},
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
label: '<OutputBool>',
|
||||
category: 'io',
|
||||
inputs: [{
|
||||
key: 'OUT',
|
||||
type: 'bool',
|
||||
label: 'Value',
|
||||
}],
|
||||
properties: [{
|
||||
key: 'pinLabel',
|
||||
type: 'string',
|
||||
widget: 'IOLabel',
|
||||
label: 'Pin label',
|
||||
value: '',
|
||||
}]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
label: '<OutputNumber>',
|
||||
category: 'io',
|
||||
inputs: [{
|
||||
key: 'OUT',
|
||||
type: 'number',
|
||||
label: 'Value',
|
||||
}],
|
||||
properties: [{
|
||||
key: 'pinLabel',
|
||||
type: 'string',
|
||||
widget: 'IOLabel',
|
||||
label: 'Pin label',
|
||||
value: '',
|
||||
}]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
label: '<OutputPulse>',
|
||||
category: 'io',
|
||||
inputs: [{
|
||||
key: 'PIN',
|
||||
type: 'pulse',
|
||||
}],
|
||||
properties: [{
|
||||
key: 'label',
|
||||
label: 'Pin label',
|
||||
type: 'io_label',
|
||||
defaultValue: 'OutB',
|
||||
}]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
label: '<OutputString>',
|
||||
category: 'io',
|
||||
inputs: [{
|
||||
key: 'OUT',
|
||||
type: 'string',
|
||||
label: 'Value',
|
||||
}],
|
||||
properties: [{
|
||||
key: 'pinLabel',
|
||||
type: 'string',
|
||||
widget: 'IOLabel',
|
||||
label: 'Pin label',
|
||||
value: '',
|
||||
}]
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
category: 'hardware',
|
||||
inputs: [{
|
||||
key: 'sample',
|
||||
type: 'pulse',
|
||||
pinLabel: 'PLS',
|
||||
label: 'Sample pulse',
|
||||
description: 'Sample current value',
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'value',
|
||||
type: 'number',
|
||||
description: 'Sampled potentiometer value',
|
||||
}],
|
||||
properties: [{
|
||||
key: 'pin',
|
||||
label: 'Pin',
|
||||
type: 'string',
|
||||
value: 'A6',
|
||||
}],
|
||||
impl: {
|
||||
espruino: "\nmodule.exports.setup = function(e) {\n e.context.pin = new Pin(e.props.pin);\n};\n\nmodule.exports.evaluate = function(e) {\n return { value: analogRead(e.context.pin) };\n};\n",
|
||||
},
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
category: 'hardware',
|
||||
inputs: [{
|
||||
key: 'minPulse',
|
||||
type: 'number',
|
||||
injected: true,
|
||||
pinLabel: 'MIN',
|
||||
label: 'Min Pulse (μs)',
|
||||
value: 700
|
||||
}, {
|
||||
key: 'maxPulse',
|
||||
type: 'number',
|
||||
injected: true,
|
||||
pinLabel: 'MAX',
|
||||
label: 'Max Pulse (μs)',
|
||||
value: 2300
|
||||
}, {
|
||||
key: 'value',
|
||||
type: 'number',
|
||||
pinLabel: 'VAL',
|
||||
label: 'Value',
|
||||
value: 1,
|
||||
description: 'Rotation angle/value'
|
||||
}],
|
||||
properties: [{
|
||||
key: 'pin',
|
||||
label: 'Pin',
|
||||
type: 'string',
|
||||
value: 'A10',
|
||||
}],
|
||||
impl: {
|
||||
espruino: "\nmodule.exports.setup = function(e) {\n e.context.pin = new Pin(e.props.pin);\n};\n\nmodule.exports.evaluate = function(e) {\n var minPulse = +e.inputs.minPulse;\n var maxPulse = +e.inputs.maxPulse;\n var us = minPulse + (maxPulse - minPulse) * e.inputs.value;\n analogWrite(e.context.pin, us / 20000, { freq: 50 });\n};\n",
|
||||
},
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
label: 'triggerableNumber',
|
||||
inputs: [{
|
||||
key: 'trigOn',
|
||||
type: 'pulse',
|
||||
pinLabel: 'PLS',
|
||||
label: 'Pulse',
|
||||
description: 'Pulse to trig on'
|
||||
}, {
|
||||
key: 'value',
|
||||
pinLabel: 'VAL',
|
||||
label: 'Value',
|
||||
type: 'number',
|
||||
value: 0,
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'out',
|
||||
type: 'number',
|
||||
description: 'Output value',
|
||||
}],
|
||||
impl: {
|
||||
js: "\nmodule.exports.evaluate = function(e) {\n return { out: e.inputs.value };\n};\n",
|
||||
},
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
label: "HC-SR04",
|
||||
category: 'hardware',
|
||||
inputs: [{
|
||||
key: 'sample',
|
||||
type: 'pulse',
|
||||
pinLabel: 'PLS',
|
||||
label: 'Pulse',
|
||||
description: 'Sample current value',
|
||||
},
|
||||
{
|
||||
key: 'units',
|
||||
pinLabel: 'UNT',
|
||||
label: 'Units',
|
||||
type: 'string',
|
||||
value: 'mm',
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'value',
|
||||
type: 'number',
|
||||
pinLabel: 'DST',
|
||||
description: 'Sampled distance (mm)',
|
||||
},
|
||||
{
|
||||
key: 'error',
|
||||
type: 'string',
|
||||
pinLabel: 'ERR',
|
||||
description: 'Operation error',
|
||||
}],
|
||||
properties: [{
|
||||
key: 'pinTrig',
|
||||
label: 'Pin trigger',
|
||||
type: 'string',
|
||||
value: 'C9',
|
||||
},
|
||||
{
|
||||
key: 'pinEcho',
|
||||
label: 'Pin echo',
|
||||
type: 'string',
|
||||
value: 'A8',
|
||||
}],
|
||||
impl: {
|
||||
espruino: "\nvar sonic = require('@amperka/ultrasonic');\n\nmodule.exports.setup = function(e) {\n var pinTrig = new Pin(e.props.pinTrig);\n var pinEcho = new Pin(e.props.pinEcho);\n e.context.device = sonic.connect({\n trigPin: pinTrig,\n echoPin: pinEcho\n });\n e.context.units = e.props.units; // FIXME! remove prop in the future\n e.context.isBusy = false;\n};\n\nmodule.exports.evaluate = function(e) {\n if (e.context.isBusy) {\n e.fire({ error: \"busy\" });\n } else {\n e.context.isBusy = true;\n e.context.device.ping(function(err, value) {\n e.context.isBusy = false;\n if (err) {\n e.fire({ error: err.msg });\n } else {\n e.fire({ value: value });\n }\n }, e.context.units);\n }\n};\n",
|
||||
},
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
label: 'valveNumber',
|
||||
inputs: [{
|
||||
key: 'cond',
|
||||
type: 'bool',
|
||||
description: 'Condition'
|
||||
},
|
||||
{
|
||||
key: 'in',
|
||||
type: 'number',
|
||||
description: 'Input'
|
||||
}],
|
||||
outputs: [{
|
||||
key: 'out',
|
||||
type: 'number',
|
||||
description: 'Output (receives messages from the input if condition is "true")',
|
||||
}],
|
||||
impl: {
|
||||
js: "\nmodule.exports.evaluate = function(e) {\n if (e.inputs.cond) {\n e.fire({ out: e.inputs.in });\n }\n};\n",
|
||||
},
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "xod-core",
|
||||
"version": "0.0.1",
|
||||
"description": "XOD project: Core",
|
||||
"scripts": {
|
||||
"build": "babel src/ -d dist/",
|
||||
"dev": "yarn run build -- --watch",
|
||||
"test": "mocha test/**/*.spec.js"
|
||||
},
|
||||
"repository": {},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"dependencies": {
|
||||
"co": "^4.6.0",
|
||||
"ramda": "^0.23.0",
|
||||
"shortid": "^2.2.6",
|
||||
"uuid": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^3.5.0"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import * as Project from './project';
|
||||
import * as Utils from './utils';
|
||||
|
||||
export * from './project';
|
||||
export * from './utils';
|
||||
|
||||
export default Object.assign({},
|
||||
Project,
|
||||
Utils
|
||||
);
|
||||
@@ -1,94 +0,0 @@
|
||||
export const LAYER = {
|
||||
BACKGROUND: 'background',
|
||||
LINKS: 'links',
|
||||
NODES: 'nodes',
|
||||
GHOSTS: 'ghosts',
|
||||
};
|
||||
|
||||
export const ENTITY = {
|
||||
NODE: 'Node',
|
||||
LINK: 'Link',
|
||||
};
|
||||
|
||||
export const NODE_CATEGORY = {
|
||||
FUNCTIONAL: 'functional',
|
||||
HARDWARE: 'hardware',
|
||||
CONFIGURATION: 'configuration',
|
||||
WATCH: 'watch',
|
||||
IO: 'io',
|
||||
PATCHES: 'patch',
|
||||
};
|
||||
|
||||
export const PIN_DIRECTION = {
|
||||
INPUT: 'input',
|
||||
OUTPUT: 'output',
|
||||
};
|
||||
|
||||
export const PIN_TYPE = {
|
||||
PULSE: 'pulse',
|
||||
BOOL: 'boolean',
|
||||
NUMBER: 'number',
|
||||
STRING: 'string',
|
||||
};
|
||||
|
||||
export const PIN_VALIDITY = {
|
||||
NONE: null,
|
||||
INVALID: 0,
|
||||
ALMOST: 1,
|
||||
VALID: 2,
|
||||
};
|
||||
|
||||
export const PROPERTY_TYPE = {
|
||||
BOOL: 'boolean',
|
||||
NUMBER: 'number',
|
||||
STRING: 'string',
|
||||
PULSE: 'pulse',
|
||||
};
|
||||
|
||||
export const PROPERTY_DEFAULT_VALUE = {
|
||||
BOOL: false,
|
||||
NUMBER: 0,
|
||||
STRING: '',
|
||||
PULSE: false,
|
||||
};
|
||||
|
||||
export const SIZE = {
|
||||
NODE: {
|
||||
minWidth: 80,
|
||||
minHeight: 40,
|
||||
padding: {
|
||||
x: 2,
|
||||
y: 25,
|
||||
},
|
||||
},
|
||||
PIN: {
|
||||
radius: 5,
|
||||
margin: 15,
|
||||
},
|
||||
NODE_TEXT: {
|
||||
margin: {
|
||||
x: 15,
|
||||
y: 5,
|
||||
},
|
||||
},
|
||||
LINK_HOTSPOT: {
|
||||
width: 8,
|
||||
},
|
||||
};
|
||||
|
||||
export const NODETYPE_ERRORS = {
|
||||
CANT_DELETE_USED_PIN_OF_PATCHNODE: 'CANT_DELETE_USED_PIN_OF_PATCHNODE',
|
||||
CANT_DELETE_USED_PATCHNODE: 'CANT_DELETE_USED_PATCHNODE',
|
||||
};
|
||||
|
||||
export const LINK_ERRORS = {
|
||||
SAME_DIRECTION: 'SAME_DIRECTION',
|
||||
SAME_NODE: 'SAME_NODE',
|
||||
ONE_LINK_FOR_INPUT_PIN: 'ONE_LINK_FOR_INPUT_PIN',
|
||||
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
|
||||
PROP_CANT_HAVE_LINKS: 'PROP_CANT_HAVE_LINKS',
|
||||
};
|
||||
|
||||
export const PROPERTY_ERRORS = {
|
||||
PIN_HAS_LINK: 'PIN_HAS_LINK',
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
export * from './constants';
|
||||
export * from './selectors';
|
||||
export * from './state';
|
||||
@@ -1,828 +0,0 @@
|
||||
import R from 'ramda';
|
||||
|
||||
import {
|
||||
PIN_DIRECTION,
|
||||
PROPERTY_TYPE,
|
||||
SIZE,
|
||||
NODE_CATEGORY,
|
||||
NODETYPE_ERRORS,
|
||||
LINK_ERRORS,
|
||||
} from './constants';
|
||||
|
||||
import { deepMerge, localID, isLocalID } from '../utils';
|
||||
|
||||
export const getUserName = R.always('Bob');
|
||||
|
||||
/*
|
||||
Common utils
|
||||
*/
|
||||
export const indexById = R.indexBy(R.prop('id'));
|
||||
const findByProp = (propName, propVal, from) => R.pipe(
|
||||
R.values,
|
||||
R.find(R.propEq(propName, propVal))
|
||||
)(from);
|
||||
|
||||
const findById = (id, from) => findByProp('id', id, from);
|
||||
const findByNodeTypeId = (id, from) => findByProp('typeId', id, from);
|
||||
|
||||
const getProjectState = (state, path) => {
|
||||
if (path.length > 0 && R.has(path[0], state)) {
|
||||
return getProjectState(
|
||||
R.prop(path.shift(), state),
|
||||
path
|
||||
);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export const getProject = state => R.propOr(state, 'project', state);
|
||||
|
||||
const getPatchStatic = patch => R.propOr(patch, 'static', patch);
|
||||
const getPatchPresent = patch => R.propOr(patch, 'present', patch);
|
||||
|
||||
export const getPatches = R.pipe(
|
||||
getProject,
|
||||
R.prop('patches'),
|
||||
R.mapObjIndexed(patch => R.merge(getPatchStatic(patch), getPatchPresent(patch)))
|
||||
);
|
||||
|
||||
export const getPatchById = R.curry((id, projectState) =>
|
||||
R.pipe(
|
||||
getPatches,
|
||||
R.prop(id)
|
||||
)(projectState)
|
||||
);
|
||||
|
||||
// :: patch -> id
|
||||
export const getPatchId = R.compose(
|
||||
R.prop('id'),
|
||||
getPatchStatic
|
||||
);
|
||||
|
||||
// :: id -> projectState -> patchStatic
|
||||
export const getPatchStaticById = id => R.compose(
|
||||
getPatchStatic,
|
||||
getPatchById(id)
|
||||
);
|
||||
|
||||
// :: id -> projectState -> patchPresent
|
||||
export const getPatchPresentById = id => R.compose(
|
||||
getPatchPresent,
|
||||
getPatchById(id)
|
||||
);
|
||||
|
||||
export const getPatchesByFolderId = (state, folderId) => R.pipe(
|
||||
getPatches,
|
||||
R.values,
|
||||
R.filter(R.propEq('folderId', folderId))
|
||||
)(state);
|
||||
|
||||
const getPatchByEntityId = (projectState, id, entityBranch) => R.pipe(
|
||||
R.prop('patches'),
|
||||
R.values,
|
||||
R.find(
|
||||
R.pipe(
|
||||
getPatchPresent,
|
||||
R.prop(entityBranch),
|
||||
R.has(id)
|
||||
)
|
||||
)
|
||||
)(projectState);
|
||||
|
||||
export const getPatchByNodeId = (projectState, nodeId) =>
|
||||
getPatchByEntityId(projectState, nodeId, 'nodes');
|
||||
|
||||
export const getPatchByLinkId = (projectState, linkId) =>
|
||||
getPatchByEntityId(projectState, linkId, 'links');
|
||||
|
||||
export const getPatchName = (projectState, patchId) => R.compose(
|
||||
R.prop('label'),
|
||||
getPatchStaticById(patchId)
|
||||
)(projectState);
|
||||
|
||||
export const doesPinHaveLinks = (pin, links) => R.pipe(
|
||||
R.values,
|
||||
R.filter(link => (
|
||||
(link.pins[0].pinKey === pin.key && link.pins[0].nodeId === pin.nodeId) ||
|
||||
(link.pins[1].pinKey === pin.key && link.pins[1].nodeId === pin.nodeId)
|
||||
)),
|
||||
R.length,
|
||||
R.flip(R.gt)(0)
|
||||
)(links);
|
||||
|
||||
export const canPinHaveMoreLinks = (pin, links) => (
|
||||
(
|
||||
pin.direction === PIN_DIRECTION.INPUT &&
|
||||
!doesPinHaveLinks(pin, links)
|
||||
) ||
|
||||
pin.direction === PIN_DIRECTION.OUTPUT
|
||||
);
|
||||
|
||||
export const getAllPinsFromNodes = R.pipe(
|
||||
R.values,
|
||||
R.reduce(
|
||||
(p, cur) =>
|
||||
R.pipe(
|
||||
R.prop('pins'),
|
||||
R.values,
|
||||
R.map(R.assoc('nodeId', cur.id)),
|
||||
R.concat(p)
|
||||
)(cur),
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
export const validatePatches = () => R.pipe(
|
||||
R.values,
|
||||
R.all(
|
||||
R.allPass([
|
||||
R.has('id'),
|
||||
R.has('label'),
|
||||
R.has('nodes'),
|
||||
R.has('links'),
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
export const isPatchesUpdated = (newPatches, oldPatches) => (
|
||||
!R.equals(R.keys(newPatches), R.keys(oldPatches))
|
||||
);
|
||||
|
||||
export const validateProject = project => (
|
||||
typeof project === 'object' &&
|
||||
R.allPass([
|
||||
R.has('patches'),
|
||||
R.has('nodeTypes'),
|
||||
R.has('meta'),
|
||||
], project) &&
|
||||
(
|
||||
R.keys(project.patches).length === 0 ||
|
||||
validatePatches(project.patches)
|
||||
)
|
||||
);
|
||||
|
||||
export const parseProjectJSON = (json) => {
|
||||
const project = JSON.parse(json);
|
||||
const patches = R.pipe(
|
||||
getPatches,
|
||||
R.values,
|
||||
R.reduce((p, patch) => R.assoc(getPatchId(patch), patch, p), {})
|
||||
)(project);
|
||||
const projectToLoad = R.assoc('patches', patches, project);
|
||||
return projectToLoad;
|
||||
};
|
||||
|
||||
export const getMeta = R.pipe(
|
||||
getProject,
|
||||
R.prop('meta')
|
||||
);
|
||||
|
||||
export const getName = R.prop('name');
|
||||
export const getId = R.prop('id');
|
||||
|
||||
/*
|
||||
NodeType selectors
|
||||
*/
|
||||
|
||||
export const getNodeTypes = R.pipe(
|
||||
getProject,
|
||||
R.prop('nodeTypes')
|
||||
);
|
||||
|
||||
export const getNodeTypeById = R.curry((state, id) => R.pipe(
|
||||
getNodeTypes,
|
||||
R.prop(id)
|
||||
)(state));
|
||||
|
||||
/*
|
||||
Node selectors
|
||||
*/
|
||||
|
||||
export const getNodes = R.curry((patchId, state) => R.compose(
|
||||
R.propOr({}, 'nodes'),
|
||||
getPatchById(patchId)
|
||||
)(state));
|
||||
|
||||
/*
|
||||
Link selectors
|
||||
*/
|
||||
|
||||
export const getLinks = (state, patchId) => R.compose(
|
||||
R.prop('links'),
|
||||
getPatchById(patchId)
|
||||
)(state);
|
||||
|
||||
export const getLinkById = (state, props) => R.pipe(
|
||||
getLinks,
|
||||
R.filter(link => link.id === props.id),
|
||||
R.values,
|
||||
R.head
|
||||
)(state, props);
|
||||
|
||||
export const getLinksByPinIdInPatch = (state, props) => {
|
||||
const patchId = R.prop('patchId', props);
|
||||
if (!patchId) { return {}; }
|
||||
|
||||
return R.pipe(
|
||||
R.view(R.lensPath(['patches', patchId, 'present', 'links'])),
|
||||
R.filter(
|
||||
link => (
|
||||
props.pinIds.indexOf(link.pins[0]) !== -1 ||
|
||||
props.pinIds.indexOf(link.pins[1]) !== -1
|
||||
)
|
||||
)
|
||||
)(state);
|
||||
};
|
||||
|
||||
/*
|
||||
Folders
|
||||
*/
|
||||
export const getFolders = R.pipe(
|
||||
getProject,
|
||||
R.prop('folders')
|
||||
);
|
||||
|
||||
// :: id -> folders -> folder
|
||||
export const getFolderById = R.pick;
|
||||
|
||||
export const getFoldersByFolderId = (state, folderId) => R.pipe(
|
||||
getFolders,
|
||||
R.values,
|
||||
R.filter(R.propEq('parentId', folderId))
|
||||
)(state);
|
||||
|
||||
/*
|
||||
Tree view (get / parse)
|
||||
*/
|
||||
export const getFoldersPath = (folders, folderId) => {
|
||||
if (!folderId) { return []; }
|
||||
const folder = folders[folderId];
|
||||
const parentPath = getFoldersPath(folders, folder.parentId);
|
||||
return R.concat([folderId], parentPath);
|
||||
};
|
||||
|
||||
// :: foldersPath -> folders -> 'folder/childFolder/'
|
||||
export const getPath = (foldersPath, folders) => {
|
||||
const folderIds = R.reverse(foldersPath);
|
||||
const folderNames = R.map(
|
||||
R.pipe(
|
||||
R.prop(R.__, folders),
|
||||
R.prop('name')
|
||||
),
|
||||
folderIds
|
||||
);
|
||||
|
||||
return R.join('/', folderNames);
|
||||
};
|
||||
|
||||
export const getTreeView = (state, patchId) => {
|
||||
const makeTree = (folders, patches, parentId, curPatchPath) => {
|
||||
const path = curPatchPath || [];
|
||||
const foldersAtLevel = R.pipe(
|
||||
R.values,
|
||||
R.filter(R.propEq('parentId', parentId))
|
||||
)(folders);
|
||||
const patchesAtLevel = R.pipe(
|
||||
R.values,
|
||||
R.filter(R.propEq('folderId', parentId))
|
||||
)(patches);
|
||||
|
||||
return R.concat(
|
||||
R.map(
|
||||
folder => ({
|
||||
id: folder.id,
|
||||
module: folder.name,
|
||||
collapsed: (path.indexOf(folder.id) === -1),
|
||||
children: makeTree(folders, patches, folder.id),
|
||||
}),
|
||||
foldersAtLevel
|
||||
),
|
||||
R.map(
|
||||
patch => ({
|
||||
id: getPatchId(patch),
|
||||
module: patch.label,
|
||||
leaf: true,
|
||||
}),
|
||||
patchesAtLevel
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const folders = getFolders(state);
|
||||
const patches = getPatches(state);
|
||||
const curPatchStatic = getPatchStaticById(patchId, state);
|
||||
const curPatchPath = getFoldersPath(folders, curPatchStatic.folderId);
|
||||
const projectChildren = makeTree(folders, patches, null, curPatchPath);
|
||||
const projectName = R.pipe(
|
||||
getMeta,
|
||||
getName
|
||||
)(state);
|
||||
|
||||
return {
|
||||
id: 0,
|
||||
module: projectName,
|
||||
collapsed: false,
|
||||
children: projectChildren,
|
||||
};
|
||||
};
|
||||
|
||||
export const parseTreeView = (tree) => {
|
||||
const resultShape = {
|
||||
folders: [],
|
||||
patches: [],
|
||||
};
|
||||
const parseTree = (treePart, parentId) => {
|
||||
const partResult = R.clone(resultShape);
|
||||
if (treePart.leaf) {
|
||||
return R.assoc('patches', R.append({
|
||||
id: treePart.id,
|
||||
folderId: parentId,
|
||||
}, partResult.patches), partResult);
|
||||
}
|
||||
|
||||
if (treePart.id) {
|
||||
partResult.folders = R.append({
|
||||
id: treePart.id,
|
||||
parentId,
|
||||
}, partResult.folders);
|
||||
}
|
||||
|
||||
if (treePart.children && treePart.children.length > 0) {
|
||||
R.pipe(
|
||||
R.values,
|
||||
R.forEach((child) => {
|
||||
const chilParentId = treePart.id || null;
|
||||
const childResult = parseTree(child, chilParentId);
|
||||
partResult.folders = R.concat(partResult.folders, childResult.folders);
|
||||
partResult.patches = R.concat(partResult.patches, childResult.patches);
|
||||
})
|
||||
)(treePart.children);
|
||||
}
|
||||
|
||||
return partResult;
|
||||
};
|
||||
|
||||
return parseTree(tree, null);
|
||||
};
|
||||
|
||||
export const getTreeChanges = (oldTree, newTree) => {
|
||||
const oldTreeParsed = parseTreeView(oldTree);
|
||||
const newTreeParsed = parseTreeView(newTree);
|
||||
const result = {
|
||||
folders: [],
|
||||
patches: [],
|
||||
changed: false,
|
||||
};
|
||||
|
||||
const sortById = R.sortBy(R.prop('id'));
|
||||
|
||||
oldTreeParsed.folders = sortById(oldTreeParsed.folders);
|
||||
newTreeParsed.folders = sortById(newTreeParsed.folders);
|
||||
newTreeParsed.folders.forEach(
|
||||
(newFolder, i) => {
|
||||
if (
|
||||
newFolder.id !== oldTreeParsed.folders[i].id ||
|
||||
newFolder.parentId !== oldTreeParsed.folders[i].parentId
|
||||
) {
|
||||
result.folders.push(newFolder);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
oldTreeParsed.patches = sortById(oldTreeParsed.patches);
|
||||
newTreeParsed.patches = sortById(newTreeParsed.patches);
|
||||
newTreeParsed.patches.forEach(
|
||||
(newPatch, i) => {
|
||||
if (
|
||||
newPatch.id !== oldTreeParsed.patches[i].id ||
|
||||
newPatch.folderId !== oldTreeParsed.patches[i].folderId
|
||||
) {
|
||||
result.patches.push(newPatch);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (result.folders.length > 0 || result.patches.length > 0) {
|
||||
result.changed = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const filterIOAkaTerminalNodes = R.filter(R.propEq('category', NODE_CATEGORY.IO));
|
||||
|
||||
export const getPatchIOPin = (node, i) => {
|
||||
const pin = R.values(node.pins)[0];
|
||||
const invertDirection = R.ifElse(
|
||||
R.equals(PIN_DIRECTION.INPUT),
|
||||
R.always(PIN_DIRECTION.OUTPUT),
|
||||
R.always(PIN_DIRECTION.INPUT)
|
||||
);
|
||||
const dir = invertDirection(pin.direction);
|
||||
|
||||
return {
|
||||
key: node.id,
|
||||
nodeId: node.id,
|
||||
pinLabel: node.properties.pinLabel,
|
||||
label: node.properties.label,
|
||||
direction: dir,
|
||||
type: pin.type,
|
||||
index: i,
|
||||
};
|
||||
};
|
||||
|
||||
export const getPatchIO = R.pipe(
|
||||
R.prop('nodes'),
|
||||
R.values,
|
||||
filterIOAkaTerminalNodes,
|
||||
R.groupBy(R.compose(R.prop('direction'), R.prop(0), R.values, R.prop('pins'))),
|
||||
R.map(R.pipe(
|
||||
R.sortBy(R.path(['position', 'x'])),
|
||||
R.addIndex(R.map)(getPatchIOPin)
|
||||
)),
|
||||
R.values,
|
||||
R.merge([[], []]),
|
||||
R.values,
|
||||
R.apply(R.concat),
|
||||
R.values
|
||||
);
|
||||
|
||||
export const getPatchNode = R.curry((state, patch) => {
|
||||
const extendNodes = R.map(
|
||||
node => R.compose(
|
||||
R.flip(deepMerge)(node),
|
||||
R.omit(['id']),
|
||||
getNodeTypeById(state),
|
||||
R.prop('typeId')
|
||||
)(node)
|
||||
);
|
||||
|
||||
const isItPatchNode = R.pipe(
|
||||
R.prop('nodes'),
|
||||
R.values,
|
||||
filterIOAkaTerminalNodes,
|
||||
R.length,
|
||||
R.flip(R.gt)(0)
|
||||
);
|
||||
|
||||
const assocNodes = p => R.assoc('nodes', extendNodes(R.prop('nodes', p)), p);
|
||||
const assocFlag = p => R.assoc('isPatchNode', isItPatchNode(p), p);
|
||||
const assocIO = p => R.assoc('io', getPatchIO(p), p);
|
||||
|
||||
return R.pipe(
|
||||
assocNodes,
|
||||
assocFlag,
|
||||
assocIO
|
||||
)(patch);
|
||||
});
|
||||
|
||||
export const getPatchNodes = state => R.pipe(
|
||||
getPatches,
|
||||
R.map(getPatchNode(state)),
|
||||
R.pickBy(R.propEq('isPatchNode', true))
|
||||
)(state);
|
||||
|
||||
const getPatchNodePath = R.curry(
|
||||
(patch, project) => {
|
||||
const folders = getFolders(project);
|
||||
const patchFolders = getFoldersPath(folders, patch.folderId);
|
||||
const folderPath = getPath(patchFolders, folders);
|
||||
const patchLabel = R.prop('label', patch);
|
||||
|
||||
return localID(`${folderPath}${folderPath ? '/' : ''}${patchLabel}`);
|
||||
}
|
||||
);
|
||||
|
||||
const isLocalPatch = R.compose(
|
||||
isLocalID,
|
||||
getPatchId
|
||||
);
|
||||
|
||||
export const dereferencedNodeTypes = (state) => {
|
||||
const patchNodes = getPatchNodes(state);
|
||||
const patchNodeTypes = R.pipe(
|
||||
R.values,
|
||||
R.filter(isLocalPatch),
|
||||
R.map(
|
||||
patch => ({
|
||||
id: getPatchId(patch),
|
||||
patchNode: true,
|
||||
label: R.prop('label', patch),
|
||||
path: getPatchNodePath(patch, state),
|
||||
category: NODE_CATEGORY.PATCHES,
|
||||
properties: [],
|
||||
pins: R.pipe(
|
||||
R.values,
|
||||
R.indexBy(R.prop('key'))
|
||||
)(patch.io),
|
||||
})
|
||||
),
|
||||
R.indexBy(R.prop('id'))
|
||||
)(patchNodes);
|
||||
|
||||
return R.pipe(
|
||||
getNodeTypes,
|
||||
R.flip(R.merge)(patchNodeTypes)
|
||||
)(state);
|
||||
};
|
||||
|
||||
export const getPreparedNodeTypeById = (state, typeId) => R.pipe(
|
||||
dereferencedNodeTypes,
|
||||
R.prop(typeId)
|
||||
)(state);
|
||||
|
||||
export const addPinRadius = position => ({
|
||||
x: position.x + SIZE.PIN.radius,
|
||||
y: position.y + SIZE.PIN.radius,
|
||||
});
|
||||
|
||||
export const getNodeLabel = (state, node) => {
|
||||
const nodeType = getPreparedNodeTypeById(state, node.typeId);
|
||||
let nodeLabel = node.label ||
|
||||
nodeType.label ||
|
||||
nodeType.id;
|
||||
|
||||
const nodeValue = R.view(R.lensPath(['properties', 'value']), node);
|
||||
if (nodeValue !== undefined) {
|
||||
const nodeValueType = nodeType.properties.value.type;
|
||||
nodeLabel = nodeValue;
|
||||
if (nodeValue === '' && nodeValueType === PROPERTY_TYPE.STRING) {
|
||||
nodeLabel = '<EmptyString>';
|
||||
}
|
||||
}
|
||||
|
||||
let nodeCustomLabel = R.path(['properties', 'label'], node);
|
||||
if (nodeCustomLabel === '') { nodeCustomLabel = null; }
|
||||
|
||||
nodeLabel = nodeCustomLabel || nodeLabel;
|
||||
|
||||
return String(nodeLabel);
|
||||
};
|
||||
const getNodePins = (state, typeId) => R.pipe(
|
||||
dereferencedNodeTypes,
|
||||
R.pickBy(R.propEq('id', typeId)),
|
||||
R.values,
|
||||
R.map(R.prop('pins')),
|
||||
R.head
|
||||
)(state);
|
||||
|
||||
export const getLinksConnectedWithPin = R.curry(
|
||||
(projectState, nodeId, pinKey, patchId) => R.pipe(
|
||||
R.values,
|
||||
R.filter(
|
||||
R.pipe(
|
||||
R.prop('pins'),
|
||||
R.find(
|
||||
R.allPass([
|
||||
R.propEq('nodeId', nodeId),
|
||||
R.propEq('pinKey', pinKey),
|
||||
])
|
||||
)
|
||||
)
|
||||
),
|
||||
R.map(
|
||||
R.pipe(
|
||||
R.prop('id'),
|
||||
R.toString
|
||||
)
|
||||
)
|
||||
)(getLinks(projectState, patchId))
|
||||
);
|
||||
|
||||
const isLinkConnected = R.curry(R.compose(
|
||||
R.gt(R.__, 0),
|
||||
R.length,
|
||||
getLinksConnectedWithPin
|
||||
));
|
||||
|
||||
export const preparePins = (projectState, node, getIsLinkConnected) => {
|
||||
const pins = getNodePins(projectState, node.typeId);
|
||||
|
||||
return R.map((pin) => {
|
||||
const originalPin = R.pathOr({}, ['pins', pin.key], node); // TODO: explain it
|
||||
const isSelected = { isSelected: false };
|
||||
const isConnected = { isConnected: getIsLinkConnected(pin.key) };
|
||||
const defaultPin = { value: null, injected: false };
|
||||
return R.mergeAll([defaultPin, pin, originalPin, isConnected, isSelected]);
|
||||
})(pins);
|
||||
};
|
||||
|
||||
export const dereferencedNodes = (projectState, patchId) =>
|
||||
R.pipe(
|
||||
getNodes(patchId),
|
||||
R.map((node) => {
|
||||
const label = getNodeLabel(projectState, node);
|
||||
const nodePins = preparePins(
|
||||
projectState,
|
||||
node,
|
||||
isLinkConnected(projectState, node.id, R.__, patchId)
|
||||
);
|
||||
|
||||
return R.merge(node, {
|
||||
label,
|
||||
pins: nodePins,
|
||||
});
|
||||
})
|
||||
)(projectState);
|
||||
|
||||
export const dereferencedLinks = (projectState, patchId) => {
|
||||
const nodes = dereferencedNodes(projectState, patchId);
|
||||
const links = getLinks(projectState, patchId);
|
||||
|
||||
return R.map((link) => {
|
||||
const pins = R.map(data => R.merge(data, nodes[data.nodeId].pins[data.pinKey]), link.pins);
|
||||
return R.merge(
|
||||
link,
|
||||
{
|
||||
type: pins[0].type,
|
||||
}
|
||||
);
|
||||
})(links);
|
||||
};
|
||||
|
||||
export const getLinksConnectedWithNode = (projectState, nodeId, patchId) => R.pipe(
|
||||
R.values,
|
||||
R.filter(
|
||||
R.pipe(
|
||||
R.prop('pins'),
|
||||
R.find(R.propEq('nodeId', nodeId))
|
||||
)
|
||||
),
|
||||
R.map(R.prop('id'))
|
||||
)(getLinks(projectState, patchId));
|
||||
|
||||
const getPinKeyByNodeId = (nodeId, patch) => R.pipe(
|
||||
R.prop('io'),
|
||||
R.find(
|
||||
R.propEq('nodeId', nodeId)
|
||||
),
|
||||
R.propOr(null, 'key')
|
||||
)(patch);
|
||||
|
||||
export const getNodeTypeToDeleteWithNode = (projectState, nodeId, patchId) => {
|
||||
const nodes = getNodes(patchId, projectState);
|
||||
const node = findById(nodeId, nodes);
|
||||
const nodeTypes = dereferencedNodeTypes(projectState);
|
||||
const nodeType = findById(node.typeId, nodeTypes);
|
||||
const isIO = (nodeType.category === NODE_CATEGORY.IO);
|
||||
|
||||
let nodeTypeToDelete = null;
|
||||
let nodeTypeToDeleteError = false;
|
||||
|
||||
if (isIO) {
|
||||
const patchNodes = getPatchNodes(projectState);
|
||||
const patch = patchNodes[patchId];
|
||||
const ioNodes = patch.io.length;
|
||||
const patchNodeType = findById(patchId, nodeTypes);
|
||||
const patchNode = findByNodeTypeId(patchNodeType.id, nodes);
|
||||
|
||||
if (ioNodes === 1) {
|
||||
// This is last IO node! It will remove whole PatchNode.
|
||||
nodeTypeToDelete = patchNodeType.id;
|
||||
}
|
||||
|
||||
if (patchNode) {
|
||||
// Get links and check for pins usage
|
||||
const pinKey = getPinKeyByNodeId(node.id, patch);
|
||||
const links = getLinks(projectState, patchId);
|
||||
const patchNodeLinks = R.pipe(
|
||||
R.values,
|
||||
R.filter(
|
||||
link => (
|
||||
(link.pins[0].nodeId === patchNode.id && link.pins[0].pinKey === pinKey) ||
|
||||
(link.pins[1].nodeId === patchNode.id && link.pins[1].pinKey === pinKey)
|
||||
)
|
||||
),
|
||||
R.length
|
||||
)(links);
|
||||
|
||||
if (patchNodeLinks > 0) {
|
||||
// This pin have links
|
||||
nodeTypeToDeleteError = NODETYPE_ERRORS.CANT_DELETE_USED_PIN_OF_PATCHNODE;
|
||||
} else if (ioNodes === 1) {
|
||||
// This patch node is used somewhere!
|
||||
nodeTypeToDeleteError = NODETYPE_ERRORS.CANT_DELETE_USED_PATCHNODE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: nodeTypeToDelete,
|
||||
error: nodeTypeToDeleteError,
|
||||
};
|
||||
};
|
||||
|
||||
const pinComparator = data => R.both(
|
||||
R.propEq('nodeId', data.nodeId),
|
||||
R.propEq('key', data.pinKey)
|
||||
);
|
||||
const findPin = pins => R.compose(
|
||||
R.flip(R.find)(pins),
|
||||
pinComparator
|
||||
);
|
||||
|
||||
const pinIsInjected = pin => !!pin.injected;
|
||||
|
||||
export const validateLink = (state, linkData) => {
|
||||
const project = getProject(state);
|
||||
const patch = getPatchByNodeId(project, linkData[0].nodeId);
|
||||
const patchId = getPatchStatic(patch).id;
|
||||
|
||||
const nodes = dereferencedNodes(project, patchId);
|
||||
const pins = getAllPinsFromNodes(nodes);
|
||||
const linksState = getLinks(project, patchId);
|
||||
|
||||
const getPin = findPin(pins);
|
||||
|
||||
const pin1 = getPin(linkData[0]);
|
||||
const pin2 = getPin(linkData[1]);
|
||||
|
||||
const sameDirection = pin1.direction === pin2.direction;
|
||||
const sameNode = pin1.nodeId === pin2.nodeId;
|
||||
const allPinsEjected = !pinIsInjected(pin1) && !pinIsInjected(pin2);
|
||||
const pin1CanHaveMoreLinks = canPinHaveMoreLinks(pin1, linksState);
|
||||
const pin2CanHaveMoreLinks = canPinHaveMoreLinks(pin2, linksState);
|
||||
|
||||
const check = (
|
||||
!sameDirection &&
|
||||
!sameNode &&
|
||||
allPinsEjected &&
|
||||
pin1CanHaveMoreLinks &&
|
||||
pin2CanHaveMoreLinks
|
||||
);
|
||||
|
||||
let error = null;
|
||||
|
||||
if (!check) {
|
||||
if (sameDirection) {
|
||||
error = LINK_ERRORS.SAME_DIRECTION;
|
||||
} else if (sameNode) {
|
||||
error = LINK_ERRORS.SAME_NODE;
|
||||
} else if (!pin1CanHaveMoreLinks || !pin2CanHaveMoreLinks) {
|
||||
error = LINK_ERRORS.ONE_LINK_FOR_INPUT_PIN;
|
||||
} else if (!allPinsEjected) {
|
||||
error = LINK_ERRORS.PROP_CANT_HAVE_LINKS;
|
||||
} else {
|
||||
error = LINK_ERRORS.UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
};
|
||||
|
||||
export const validatePin = (state, pin) => {
|
||||
const project = getProject(state);
|
||||
const patch = getPatchByNodeId(project, pin.nodeId);
|
||||
const patchId = getPatchId(patch);
|
||||
const nodes = dereferencedNodes(project, patchId);
|
||||
const linksState = getLinks(project, patchId);
|
||||
const pins = getAllPinsFromNodes(nodes);
|
||||
|
||||
const getPin = findPin(pins);
|
||||
const pinData = getPin(pin);
|
||||
|
||||
const pinCanHaveMoreLinks = canPinHaveMoreLinks(pinData, linksState);
|
||||
const pinEjected = !pinIsInjected(pinData);
|
||||
|
||||
const check = (
|
||||
pinCanHaveMoreLinks &&
|
||||
pinEjected
|
||||
);
|
||||
|
||||
let error = null;
|
||||
|
||||
if (!check) {
|
||||
if (!pinCanHaveMoreLinks) {
|
||||
error = LINK_ERRORS.ONE_LINK_FOR_INPUT_PIN;
|
||||
} else
|
||||
if (!pinEjected) {
|
||||
error = LINK_ERRORS.PROP_CANT_HAVE_LINKS;
|
||||
} else {
|
||||
error = LINK_ERRORS.UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
};
|
||||
|
||||
export const getProjectPojo = (state) => {
|
||||
const project = getProject(state);
|
||||
const patches = R.pipe(
|
||||
getPatches,
|
||||
R.values,
|
||||
indexById
|
||||
)(project);
|
||||
|
||||
return R.pipe(
|
||||
R.assoc('patches', patches),
|
||||
R.assoc('nodeTypes', dereferencedNodeTypes(state)),
|
||||
R.omit(['counter'])
|
||||
)(project);
|
||||
};
|
||||
|
||||
const prettyJSON = data => JSON.stringify(data, undefined, 2);
|
||||
|
||||
export const getProjectJSON = R.compose(
|
||||
prettyJSON,
|
||||
getProjectPojo
|
||||
);
|
||||
@@ -1,49 +0,0 @@
|
||||
import R from 'ramda';
|
||||
import { PIN_DIRECTION } from './constants';
|
||||
|
||||
const mapDirectedNodeTypePins = (direction, collectionKey) => R.compose(
|
||||
R.indexBy(R.prop('key')),
|
||||
R.addIndex(R.map)((io, index) => R.merge({ index, direction }, io)),
|
||||
R.propOr([], collectionKey)
|
||||
);
|
||||
|
||||
const mapNodeTypePins = meta => R.merge(
|
||||
mapDirectedNodeTypePins(PIN_DIRECTION.INPUT, 'inputs')(meta),
|
||||
mapDirectedNodeTypePins(PIN_DIRECTION.OUTPUT, 'outputs')(meta)
|
||||
);
|
||||
|
||||
// :: (String -> String -> String) -> Object -> Object
|
||||
export const genNodeTypes = R.compose(
|
||||
R.indexBy(R.prop('id')),
|
||||
R.values,
|
||||
R.mapObjIndexed((meta, id) => R.merge(
|
||||
R.omit(['inputs', 'outputs'], meta),
|
||||
{
|
||||
id,
|
||||
pins: mapNodeTypePins(meta),
|
||||
}
|
||||
))
|
||||
);
|
||||
|
||||
export const getInitialState = nodeTypes => ({
|
||||
meta: {
|
||||
name: 'Awesome project',
|
||||
author: 'Amperka team',
|
||||
},
|
||||
patches: {
|
||||
'@/1': {
|
||||
id: '@/1',
|
||||
label: 'Main',
|
||||
nodes: {},
|
||||
links: {},
|
||||
},
|
||||
'@/2': {
|
||||
id: '@/2',
|
||||
label: 'QUX',
|
||||
nodes: {},
|
||||
links: {},
|
||||
},
|
||||
},
|
||||
nodeTypes,
|
||||
folders: {},
|
||||
});
|
||||
@@ -1,59 +0,0 @@
|
||||
|
||||
import R from 'ramda';
|
||||
|
||||
export function findVertexesWithNoIncomingEdges(vertexes, edges) {
|
||||
return R.difference(
|
||||
vertexes,
|
||||
R.map(R.nth(1), edges)
|
||||
);
|
||||
}
|
||||
|
||||
export function hasIncomingEdges(vertex, edges) {
|
||||
const edgeIncoming = R.compose(R.equals(vertex), R.nth(1));
|
||||
return R.any(edgeIncoming, edges);
|
||||
}
|
||||
|
||||
export function hasEdgeFrom(n, edges) {
|
||||
return m => R.contains([n, m], edges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts graph vertexes topologically.
|
||||
*
|
||||
* @param {Array.<number>} vertexes
|
||||
* List of graph vertexes with an arbitrary number (ID, for example) as payload.
|
||||
* @param {Array.<Array.<number, number>>} edges
|
||||
* List of pairs in the form of `[sourceVertex, destinationVertex] that defines
|
||||
* graph edges along with their direction.
|
||||
*
|
||||
* This is an implementation of Kahn’s algorithm.
|
||||
* @see https://en.wikipedia.org/wiki/Topological_sorting
|
||||
*/
|
||||
export function sortGraph(vertexes, edges) {
|
||||
let l = []; // Empty list that will contain the sorted elements
|
||||
let s = findVertexesWithNoIncomingEdges(vertexes, edges);
|
||||
let edgesLeft = edges;
|
||||
|
||||
const excludeEdgesFrom = n => (m) => {
|
||||
edgesLeft = R.without([[n, m]], edgesLeft);
|
||||
if (!hasIncomingEdges(m, edgesLeft)) {
|
||||
s = R.append(m, s);
|
||||
}
|
||||
};
|
||||
|
||||
while (s.length) {
|
||||
const n = R.head(s);
|
||||
s = R.drop(1, s);
|
||||
l = R.append(n, l);
|
||||
R.forEach(
|
||||
excludeEdgesFrom(n),
|
||||
R.filter(hasEdgeFrom(n, edgesLeft), vertexes)
|
||||
);
|
||||
}
|
||||
|
||||
if (edgesLeft.length) {
|
||||
throw new Error('Graph has at least one cycle');
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { generate } from 'shortid';
|
||||
|
||||
export * from './gmath';
|
||||
export * from './ramda';
|
||||
|
||||
const removeTrailingSlash = text => text.replace(/\/$/, '');
|
||||
|
||||
export const localID = sid => `@/${removeTrailingSlash(sid)}`;
|
||||
|
||||
export const generateId = () => generate();
|
||||
export const generatePatchSID = () => localID(generateId());
|
||||
|
||||
export const isLocalID = id => (typeof id === 'string' && id[0] === '@');
|
||||
@@ -1,15 +0,0 @@
|
||||
import R from 'ramda';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const deepMerge = R.mergeWith(
|
||||
(o1, o2) =>
|
||||
R.ifElse(
|
||||
R.is(Object),
|
||||
R.flip(R.mergeWith(deepMerge))(o2),
|
||||
R.flip(R.merge)(o2)
|
||||
)(o1)
|
||||
);
|
||||
|
||||
export const notNil = R.complement(R.isNil);
|
||||
export const hasNot = R.complement(R.has);
|
||||
export const mapIndexed = R.addIndex(R.map);
|
||||
@@ -1,47 +0,0 @@
|
||||
|
||||
import { expect } from 'chai';
|
||||
import { sortGraph } from '../src/utils/gmath';
|
||||
|
||||
describe('Graph math', () => {
|
||||
describe('Topological sorting', () => {
|
||||
it('should return [] for empty graph', () => {
|
||||
const sorted = sortGraph([], []);
|
||||
expect(sorted).to.be.eql([]);
|
||||
});
|
||||
|
||||
it('should return single vertex for single-vertex graph', () => {
|
||||
const sorted = sortGraph([42], []);
|
||||
expect(sorted).to.be.eql([42]);
|
||||
});
|
||||
|
||||
it('should return vertexes as is if there are no edges', () => {
|
||||
const sorted = sortGraph([42, 43, 44], []);
|
||||
expect(sorted).to.be.eql([42, 43, 44]);
|
||||
});
|
||||
|
||||
it('should return vertexes as is if already sorted', () => {
|
||||
const sorted = sortGraph([42, 43, 44], [[42, 43], [43, 44]]);
|
||||
expect(sorted).to.be.eql([42, 43, 44]);
|
||||
});
|
||||
|
||||
it('should return sorted vertexes if given vertexes are inversed', () => {
|
||||
const sorted = sortGraph([44, 43, 42], [[42, 43], [43, 44]]);
|
||||
expect(sorted).to.be.eql([42, 43, 44]);
|
||||
});
|
||||
|
||||
it('should throw error for cycled graph', () => {
|
||||
const sort = () => sortGraph([42, 43, 44], [[42, 43], [43, 42]]);
|
||||
expect(sort).to.throw(Error, /cycle/);
|
||||
});
|
||||
|
||||
it('should sort diamond graph', () => {
|
||||
const sorted = sortGraph([44, 43, 42, 45], [[42, 43], [42, 44], [43, 45], [44, 45]]);
|
||||
expect(sorted).to.be.eql([42, 44, 43, 45]);
|
||||
});
|
||||
|
||||
it('should sort clusters', () => {
|
||||
const sorted = sortGraph([44, 43, 42, 45], [[42, 43], [44, 45]]);
|
||||
expect(sorted).to.be.eql([44, 42, 45, 43]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
--require babel-register
|
||||
--colors
|
||||
@@ -1,45 +0,0 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
assertion-error@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c"
|
||||
|
||||
chai@^3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247"
|
||||
dependencies:
|
||||
assertion-error "^1.0.1"
|
||||
deep-eql "^0.1.3"
|
||||
type-detect "^1.0.0"
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
|
||||
deep-eql@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2"
|
||||
dependencies:
|
||||
type-detect "0.1.1"
|
||||
|
||||
ramda@^0.23.0:
|
||||
version "0.23.0"
|
||||
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.23.0.tgz#ccd13fff73497a93974e3e86327bfd87bd6e8e2b"
|
||||
|
||||
shortid@^2.2.6:
|
||||
version "2.2.8"
|
||||
resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131"
|
||||
|
||||
type-detect@0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822"
|
||||
|
||||
type-detect@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2"
|
||||
|
||||
uuid@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
|
||||
@@ -18,7 +18,6 @@
|
||||
"ramda": "^0.23.0",
|
||||
"recursive-readdir": "^2.1.0",
|
||||
"rimraf": "^2.5.4",
|
||||
"xod-core": "^0.0.1",
|
||||
"xod-project": "^0.0.1",
|
||||
"xod-func-tools": "^0.0.1"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import R from 'ramda';
|
||||
import path from 'path';
|
||||
import { hasNot } from 'xod-core';
|
||||
import { hasNo } from 'xod-func-tools';
|
||||
import { toV2, listLibraryPatches } from 'xod-project';
|
||||
import { readDir, readJSON, readFile } from './read';
|
||||
import { resolvePath } from './utils';
|
||||
@@ -63,7 +63,7 @@ const readLibFiles = (libfiles) => {
|
||||
.then((loaded) => {
|
||||
const data = R.assoc('id', `${name}/${getPatchName(patchPath)}`, loaded);
|
||||
|
||||
if (hasNot('nodes', data)) {
|
||||
if (hasNo('nodes', data)) {
|
||||
return R.assoc('impl', {}, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import path from 'path';
|
||||
import R from 'ramda';
|
||||
import { generateId, localID } from 'xod-core';
|
||||
import { generateId } from 'xod-project';
|
||||
import { localID } from './utils';
|
||||
|
||||
const indexById = R.indexBy(R.prop('id'));
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from 'path';
|
||||
import R from 'ramda';
|
||||
import XF from 'xod-func-tools';
|
||||
import { hasNot, isLocalID, localID, notNil } from 'xod-core';
|
||||
import { isLocalID, localID } from './utils';
|
||||
|
||||
// :: "./awesome-project/" -> "main" -> "patch.xodm" -> "./awesome-project/main/patch.xodm"
|
||||
const filePath = (projectPath, patchPath, fileName) => R.pipe(
|
||||
@@ -42,7 +42,7 @@ const foldersPaths = R.pipe(
|
||||
R.values,
|
||||
R.sort(
|
||||
R.allPass([
|
||||
notNil,
|
||||
XF.notNil,
|
||||
R.gte,
|
||||
])
|
||||
),
|
||||
@@ -78,7 +78,7 @@ const extractLibs = R.pipe(
|
||||
|
||||
// :: patchMeta -> xodball -> patchNodeMeta
|
||||
const margeWithNodeType = (obj, patchId, xodball) => {
|
||||
if (hasNot(patchId, xodball.nodeTypes)) { return obj; }
|
||||
if (XF.hasNo(patchId, xodball.nodeTypes)) { return obj; }
|
||||
|
||||
return R.pipe(
|
||||
R.path(['nodeTypes', patchId]),
|
||||
@@ -132,7 +132,7 @@ const resolvePatchIds = (patches) => {
|
||||
|
||||
const resolveNode = (node) => {
|
||||
const typeId = node.typeId;
|
||||
if (hasNot(typeId, pathMapping)) { return node; }
|
||||
if (XF.hasNo(typeId, pathMapping)) { return node; }
|
||||
return R.assoc('typeId', pathMapping[typeId], node);
|
||||
};
|
||||
|
||||
|
||||
@@ -28,3 +28,9 @@ export const isFileExists = R.tryCatch(
|
||||
),
|
||||
R.F
|
||||
);
|
||||
|
||||
|
||||
// TODO: remove rudimental utilities
|
||||
const removeTrailingSlash = text => text.replace(/\/$/, '');
|
||||
export const localID = sid => `@/${removeTrailingSlash(sid)}`;
|
||||
export const isLocalID = id => (typeof id === 'string' && id[0] === '@');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import R from 'ramda';
|
||||
import { expect } from 'chai';
|
||||
import { isLocalID } from 'xod-core';
|
||||
import { isLocalID } from '../src/utils';
|
||||
import * as Unpack from '../src/unpack';
|
||||
import xodball from './fixtures/xodball.json';
|
||||
import unpacked from './fixtures/unpacked.json';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import R from 'ramda';
|
||||
import { mapIndexed } from 'xod-core';
|
||||
|
||||
export const numerateFolders = (initialFolders) => {
|
||||
const accordance = {};
|
||||
@@ -7,7 +6,7 @@ export const numerateFolders = (initialFolders) => {
|
||||
return R.pipe(
|
||||
R.values,
|
||||
R.sortBy(R.prop('name')),
|
||||
mapIndexed(
|
||||
R.mapObjIndexed(
|
||||
(folder, idx) => {
|
||||
accordance[folder.id] = idx;
|
||||
return R.assoc('id', idx, folder);
|
||||
|
||||
@@ -103,6 +103,7 @@ export const optionalObjOf = def(
|
||||
|
||||
export const notNil = R.complement(R.isNil);
|
||||
export const notEmpty = R.complement(R.isEmpty);
|
||||
export const hasNo = R.complement(R.has);
|
||||
|
||||
/**
|
||||
* Like `R.tap` but works with Promises.
|
||||
@@ -116,6 +117,7 @@ export default Object.assign(
|
||||
explode,
|
||||
explodeMaybe,
|
||||
foldEither,
|
||||
hasNo,
|
||||
omitNilValues,
|
||||
omitEmptyValues,
|
||||
isAmong,
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"hm-def": "^0.1.2",
|
||||
"ramda": "^0.23.0",
|
||||
"ramda-fantasy": "^0.7.0",
|
||||
"xod-core": "^0.0.1",
|
||||
"xod-project": "^0.0.1",
|
||||
"xod-func-tools": "^0.0.1"
|
||||
},
|
||||
|
||||
@@ -203,7 +203,7 @@ function transpileImpl(impl) {
|
||||
const itemRef = `impl['${implId}']`;
|
||||
let lines = [];
|
||||
|
||||
// TODO: move such predicates to xod-core
|
||||
// TODO: use functions from xod-project, unhardcode regexes
|
||||
if (/^xod\/core\/input/.test(implId) || /^xod\/core\/output/.test(implId)) {
|
||||
lines = [`${itemRef} = identityNode();`];
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,6 @@ module.exports = {
|
||||
umdNamedDefine: true
|
||||
},
|
||||
externals: [
|
||||
'xod-core',
|
||||
'fs',
|
||||
],
|
||||
module: {
|
||||
|
||||
Reference in New Issue
Block a user