refactor(infra): kill xod-core package

This commit is contained in:
Victor Nakoryakov
2017-05-10 16:16:58 +03:00
parent 996dbf85f2
commit 15c9727dc1
54 changed files with 21 additions and 1854 deletions

View File

@@ -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;

View File

@@ -1,3 +0,0 @@
{
"presets": ["es2015"]
}

View File

@@ -1,4 +0,0 @@
node_modules/
dist/
npm-debug.log
fs-temp/

View File

@@ -1,3 +0,0 @@
# xod-core
FIXME: write a README

View File

@@ -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};",
},
}

View File

@@ -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",
},
}

View File

@@ -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",
},
}

View File

@@ -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};",
},
}

View File

@@ -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};",
},
}

View File

@@ -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};",
},
}

View File

@@ -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};",
},
}

View File

@@ -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};",
},
}

View File

@@ -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",
},
}

View File

@@ -1,15 +0,0 @@
{
label: '<InputBool>',
category: 'io',
outputs: [{
key: 'PIN',
type: 'bool',
}],
properties: [{
key: 'pinLabel',
type: 'string',
widget: 'IOLabel',
label: 'Pin label',
value: '',
}]
}

View File

@@ -1,15 +0,0 @@
{
label: '<InputNumber>',
category: 'io',
outputs: [{
key: 'PIN',
type: 'number',
}],
properties: [{
key: 'pinLabel',
type: 'string',
widget: 'IOLabel',
label: 'Pin label',
value: '',
}]
}

View File

@@ -1,14 +0,0 @@
{
label: '<InputPulse>',
category: 'io',
outputs: [{
key: 'PIN',
type: 'pulse',
}],
properties: [{
key: 'label',
label: 'Pin label',
type: 'io_label',
defaultValue: 'InP',
}]
}

View File

@@ -1,15 +0,0 @@
{
label: '<InputString>',
category: 'io',
outputs: [{
key: 'PIN',
type: 'string',
}],
properties: [{
key: 'pinLabel',
type: 'string',
widget: 'IOLabel',
label: 'Pin label',
value: '',
}]
}

View File

@@ -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};",
},
}

View File

@@ -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",
},
}

View File

@@ -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",
},
}

View File

@@ -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};",
},
}

View File

@@ -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",
},
}

View File

@@ -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: '',
}]
}

View File

@@ -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: '',
}]
}

View File

@@ -1,14 +0,0 @@
{
label: '<OutputPulse>',
category: 'io',
inputs: [{
key: 'PIN',
type: 'pulse',
}],
properties: [{
key: 'label',
label: 'Pin label',
type: 'io_label',
defaultValue: 'OutB',
}]
}

View File

@@ -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: '',
}]
}

View File

@@ -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",
},
}

View File

@@ -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",
},
}

View File

@@ -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",
},
}

View File

@@ -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",
},
}

View File

@@ -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",
},
}

View File

@@ -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"
}
}

View File

@@ -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
);

View File

@@ -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',
};

View File

@@ -1,4 +0,0 @@
export * from './constants';
export * from './selectors';
export * from './state';

View File

@@ -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
);

View File

@@ -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: {},
});

View File

@@ -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 Kahns 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;
}

View File

@@ -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] === '@');

View File

@@ -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);

View File

@@ -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]);
});
});
});

View File

@@ -1,2 +0,0 @@
--require babel-register
--colors

View File

@@ -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"

View File

@@ -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"
},

View File

@@ -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);
}

View File

@@ -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'));

View File

@@ -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);
};

View File

@@ -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] === '@');

View File

@@ -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';

View File

@@ -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);

View File

@@ -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,

View File

@@ -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"
},

View File

@@ -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 {

View File

@@ -15,7 +15,6 @@ module.exports = {
umdNamedDefine: true
},
externals: [
'xod-core',
'fs',
],
module: {