From abd29d29998b67ed24358c42b6cfb72968803d5c Mon Sep 17 00:00:00 2001 From: Stanislav Fifik Date: Tue, 22 Jan 2019 12:52:22 +0100 Subject: [PATCH] :recycle: better types system --- src/{index.spec.js => index.xspec.js} | 0 src/nodes/Compose.js | 124 +++++++++++------- src/nodes/Node.js | 8 +- src/nodes/Resize.js | 24 +++- src/nodes/WebGL/BrightnessContrast.js | 19 ++- src/nodes/WebGL/Greenscreen.js | 44 +++++-- src/nodes/WebGL/HSV.js | 21 +++- src/nodes/WebGL/Sepia.js | 11 +- src/nodes/WebGL/WebGl.js | 8 +- src/nodes/Webcam.js | 8 +- src/nodes/io.d.ts | 12 -- src/nodes/io.js | 175 -------------------------- src/nodes/io/AbstractIO.js | 97 ++++++++++++++ src/nodes/io/AbstractIOSet.js | 28 +++++ src/nodes/io/Input.js | 49 ++++++++ src/nodes/io/Inputs.js | 25 ++++ src/nodes/io/Inputs.spec.js | 36 ++++++ src/nodes/io/Output.js | 40 ++++++ src/nodes/io/Outputs.js | 20 +++ src/nodes/io/Outputs.spec.js | 30 +++++ src/nodes/io/index.js | 4 + ui/Components/Graph.vue | 6 + ui/Components/Node.vue | 32 ++++- ui/Components/Value/Image.vue | 46 ++++++- ui/Components/Value/Select.vue | 12 +- ui/Components/Value/String.vue | 24 +++- ui/Components/Value/index.js | 32 +++-- 27 files changed, 635 insertions(+), 300 deletions(-) rename src/{index.spec.js => index.xspec.js} (100%) delete mode 100644 src/nodes/io.d.ts delete mode 100644 src/nodes/io.js create mode 100644 src/nodes/io/AbstractIO.js create mode 100644 src/nodes/io/AbstractIOSet.js create mode 100644 src/nodes/io/Input.js create mode 100644 src/nodes/io/Inputs.js create mode 100644 src/nodes/io/Inputs.spec.js create mode 100644 src/nodes/io/Output.js create mode 100644 src/nodes/io/Outputs.js create mode 100644 src/nodes/io/Outputs.spec.js create mode 100644 src/nodes/io/index.js diff --git a/src/index.spec.js b/src/index.xspec.js similarity index 100% rename from src/index.spec.js rename to src/index.xspec.js diff --git a/src/nodes/Compose.js b/src/nodes/Compose.js index 88fc42b..bcaa969 100644 --- a/src/nodes/Compose.js +++ b/src/nodes/Compose.js @@ -5,54 +5,89 @@ export default class Compose extends Node { constructor(options) { super('Compose', { - fg: 'Image', - fgX: 'Number', - fgY: 'Number', - bg: 'Image', - bgX: 'Number', - bgY: 'Number', - width: 'Number', - height: 'Number', - mode: [ - 'source-over', - 'source-in', - 'source-out', - 'source-atop', - 'destination-over', - 'destination-in', - 'destination-out', - 'destination-atop', - 'lighter', - 'copy', - 'xor', - 'multiply', - 'screen', - 'overlay', - 'darken', - 'lighten', - 'color-dodge', - 'color-burn', - 'hard-light', - 'soft-light', - 'difference', - 'exclusion', - 'hue', - 'saturation', - 'color', - 'luminosity', - ] + fg: { + type: 'Image' + }, + fgX: { + type: 'Number', + default: 0, + step: 1, + }, + fgY: { + type: 'Number', + default: 0, + step: 1, + }, + bg: { + type: 'Image', + }, + bgX: { + type: 'Number', + default: 0, + step: 1, + }, + bgY: { + type: 'Number', + default: 0, + step: 1, + }, + width: { + type: 'Number', + default: 100, + step: 1, + min: 1 + }, + height: { + type: 'Number', + default: 100, + step: 1, + min: 1, + }, + mode: { + type: 'String', + default: 'source-over', + enum: [ + 'source-over', + 'source-in', + 'source-out', + 'source-atop', + 'destination-over', + 'destination-in', + 'destination-out', + 'destination-atop', + 'lighter', + 'copy', + 'xor', + 'multiply', + 'screen', + 'overlay', + 'darken', + 'lighten', + 'color-dodge', + 'color-burn', + 'hard-light', + 'soft-light', + 'difference', + 'exclusion', + 'hue', + 'saturation', + 'color', + 'luminosity', + ] + } }, { - image: 'Image' - }); - this.options = options; + image: { + type: 'Image' + } + }, options); } get width() { - return this.__in.width.value || this.options.width; + return this.__in.width.value; } get height() { - return this.__in.height.value || this.options.height; + return this.__in.height.value; } get fg() { @@ -85,11 +120,10 @@ export default class Compose extends Node { } } - get mode() { - return this.__in.mode.value || 'source-over'; - } - __update() { + if (!this.width || !this.height) { + return; + } const canvas = createCanvas(this.width, this.height); if (this.bg) { diff --git a/src/nodes/Node.js b/src/nodes/Node.js index c510aeb..7d649b4 100644 --- a/src/nodes/Node.js +++ b/src/nodes/Node.js @@ -1,13 +1,13 @@ -import {Inputs, Outputs} from './io.js'; +import {Inputs, Outputs} from './io'; import uuidv4 from 'uuid/v4'; export default class Node { - constructor(name, inputNames, outputNames) { + constructor(name, inputDefinition, outputDefiniton, options) { this.name = name; this.id = uuidv4(); - this.__in = new Inputs(inputNames, this); - this.__out = new Outputs(outputNames, this); + this.__in = new Inputs(inputDefinition, this); + this.__out = new Outputs(outputDefiniton, this); this.__in.update = (name) => this.__update([name]); } diff --git a/src/nodes/Resize.js b/src/nodes/Resize.js index d2feb51..6a81245 100644 --- a/src/nodes/Resize.js +++ b/src/nodes/Resize.js @@ -5,21 +5,33 @@ export default class Resize extends Node { constructor(options) { super('Resize', { - image: 'Image', - width: 'Number', - height: 'Number', + image: { + type: 'Image', + }, + width: { + type: 'Number', + default: 100, + min: 1, + }, + height: { + type: 'Number', + default: 100, + min: 1, + }, }, { - image: 'Image' + image: { + type: 'Image', + } }); this.options = options; } get width() { - return this.__in.width.value || this.options.width; + return this.__in.width.value; } get height() { - return this.__in.height.value || this.options.height; + return this.__in.height.value; } __update() { diff --git a/src/nodes/WebGL/BrightnessContrast.js b/src/nodes/WebGL/BrightnessContrast.js index 0213ffc..f34b78f 100644 --- a/src/nodes/WebGL/BrightnessContrast.js +++ b/src/nodes/WebGL/BrightnessContrast.js @@ -5,8 +5,14 @@ export default class BrightnessContrast extends WebGL { constructor() { super('BrightnessContrast', { - brightness: 'Number', - contrast: 'Number', + brightness: { + type: 'Number', + default: 1, + }, + contrast: { + type: 'Number', + default: 1, + }, }) } @@ -23,23 +29,24 @@ export default class BrightnessContrast extends WebGL { uniform vec2 u_brightnessContrast; void main() { - vec3 c = texture2D(u_image, v_texCoord).rgb * u_brightnessContrast[0]; + vec4 pixelColor = texture2D(u_image, v_texCoord); + vec3 c = pixelColor.rgb * u_brightnessContrast[0]; gl_FragColor = vec4( clamp( (c - 0.5) * u_brightnessContrast[1] + 0.5, 0.0, 1.0), - 1 + pixelColor.a ); } ` } get contrast() { - return this.in.contrast.value || 1; + return this.in.contrast.value } get brightness() { - return this.in.brightness.value || 1; + return this.in.brightness.value; } _setParams(gl, program) { diff --git a/src/nodes/WebGL/Greenscreen.js b/src/nodes/WebGL/Greenscreen.js index 9fb9536..fdc5a44 100644 --- a/src/nodes/WebGL/Greenscreen.js +++ b/src/nodes/WebGL/Greenscreen.js @@ -13,11 +13,30 @@ export default class GreenScreen extends WebGL { constructor() { super('GreenScreen', { - balance: 'Number', - screen: 'Color', - screenWeight: 'Number', - clipBlack: 'Number', - clipWhite: 'Number', + balance: { + type: 'Number', + default: 0.5, + step: 0.1, + }, + screen: { + type: 'Color', + default: '#2CD6A4', + }, + screenWeight: { + type: 'Number', + default: 1, + min: 0, + max: 1, + step: 0.1, + }, + clipBlack: { + type: 'Number', + default: 0, + }, + clipWhite: { + type: 'Number', + default: 1, + }, }) } @@ -52,13 +71,13 @@ export default class GreenScreen extends WebGL { float screenFmax = max(max(screen.r, screen.g), screen.b); //Max. value of RGB vec3 screenPrimary = step(screenFmax, screen.rgb); float screenSecondaryComponents = dot(1.0 - screenPrimary, screen.rgb); - float screenSat = fmax - mix(secondaryComponents - fmin, secondaryComponents / 2.0, balance); + float screenSat = screenFmax - mix(screenSecondaryComponents - screenFmin, screenSecondaryComponents / 2.0, balance); pixelSat = fmax - mix(secondaryComponents - fmin, secondaryComponents / 2.0, balance); // solid pixel if primary color component is not the same as the screen color float diffPrimary = dot(abs(pixelPrimary - screenPrimary), vec3(1.0)); - float solid = step(1.0, step(pixelSat, 0.1) + step(fmax, 0.1) + diffPrimary); + float solid = step(1.0, pixelSat + step(fmax, 0.1) + diffPrimary); /* Semi-transparent pixel if the primary component matches but if saturation is less @@ -77,26 +96,27 @@ export default class GreenScreen extends WebGL { } get clipBlack() { - return (this.in.clipBlack.value || 0); + return this.in.clipBlack.value; } get clipWhite() { - return (this.in.clipWhite.value || 0); + return this.in.clipWhite.value; } get screenWeight() { - return (this.in.screenWeight.value || 1); + return this.in.screenWeight.value; } get screen() { - return hexColorTOvec3(this.in.screen.value || '#00FF00'); + return hexColorTOvec3(this.in.screen.value); } get balance() { - return (this.in.balance.value || 0.5); + return this.in.balance.value; } _setParams(gl, program) { + console.log(this.balance, this.clipBlack, this.clipWhite, this.screenWeight) gl.uniform3f(gl.getUniformLocation(program, 'screen'), ...this.screen); gl.uniform1f(gl.getUniformLocation(program, 'balance'), this.balance); gl.uniform1f(gl.getUniformLocation(program, 'clipBlack'), this.clipBlack); diff --git a/src/nodes/WebGL/HSV.js b/src/nodes/WebGL/HSV.js index 2b234ef..ab29d58 100644 --- a/src/nodes/WebGL/HSV.js +++ b/src/nodes/WebGL/HSV.js @@ -5,9 +5,18 @@ export default class HSV extends WebGL { constructor() { super('HSV', { - hue: 'Number', - saturation: 'Number', - value: 'Number', + hue: { + type: 'Number', + default: 0, + }, + saturation: { + type: 'Number', + default: 1, + }, + value: { + type: 'Number', + default: 1, + } }) } @@ -50,15 +59,15 @@ export default class HSV extends WebGL { } get hue() { - return ((this.in.hue.value || 0) % 360) / 360; + return ((this.in.hue.value) % 360) / 360; } get saturation() { - return this.in.saturation.value || 1; + return this.in.saturation.value; } get value() { - return this.in.value.value || 1; + return this.in.value.value; } _setParams(gl, program) { diff --git a/src/nodes/WebGL/Sepia.js b/src/nodes/WebGL/Sepia.js index 1e765c2..3f99eec 100644 --- a/src/nodes/WebGL/Sepia.js +++ b/src/nodes/WebGL/Sepia.js @@ -5,7 +5,10 @@ export default class Sepia extends WebGL { constructor() { super('Sepia', { - amount: 'Number', + amount: { + type: 'Number', + default: 0, + }, }) } @@ -22,7 +25,7 @@ export default class Sepia extends WebGL { uniform float amount; void main() { - vec3 color = texture2D(u_image, v_texCoord).rgb; + vec4 color = texture2D(u_image, v_texCoord); float r = color.r; float g = color.g; float b = color.b; @@ -31,13 +34,13 @@ export default class Sepia extends WebGL { color.g = min(1.0, (r * 0.349 * amount) + (g * (1.0 - (0.314 * amount))) + (b * 0.168 * amount)); color.b = min(1.0, (r * 0.272 * amount) + (g * 0.534 * amount) + (b * (1.0 - (0.869 * amount)))); - gl_FragColor = vec4(color, 1); + gl_FragColor = color; } ` } get amount() { - return this.in.amount.value || 0; + return this.in.amount.value; } /** diff --git a/src/nodes/WebGL/WebGl.js b/src/nodes/WebGL/WebGl.js index 3d6dfbd..0452405 100644 --- a/src/nodes/WebGL/WebGl.js +++ b/src/nodes/WebGL/WebGl.js @@ -8,9 +8,13 @@ export default class WebGL extends Node { constructor(name, inputs={}) { super(name, Object.assign({ - image: 'Image', + image: { + type: 'Image', + }, }, inputs), { - image: 'Image' + image: { + type: 'Image', + }, }); } diff --git a/src/nodes/Webcam.js b/src/nodes/Webcam.js index dcfc0eb..3dbd986 100644 --- a/src/nodes/Webcam.js +++ b/src/nodes/Webcam.js @@ -4,7 +4,9 @@ export default class ToBlob extends Node { constructor(options) { super('Webcam', {}, { - image: 'Image', + image: { + type: 'Image' + }, }); this.start(); } @@ -16,8 +18,8 @@ export default class ToBlob extends Node { console.log(last(devices)); const stream = await navigator.mediaDevices.getUserMedia({ video: { - width: { min: 1024, ideal: 1280, max: 1920 }, - height: { min: 776, ideal: 720, max: 1080 }, + width: { min: 1024, ideal: 1920, max: 1920 }, + height: { min: 776, ideal: 1080, max: 1080 }, deviceId: last(devices).deviceId }, }); diff --git a/src/nodes/io.d.ts b/src/nodes/io.d.ts deleted file mode 100644 index df0df27..0000000 --- a/src/nodes/io.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as types from './types' - - -export class Inputs { - constructor(variables: T[]); - [k: string]: types.Input -} - -export class Outputs { - constructor(variables: T[]); - [k: string]: types.Input -} \ No newline at end of file diff --git a/src/nodes/io.js b/src/nodes/io.js deleted file mode 100644 index 0ef0a4f..0000000 --- a/src/nodes/io.js +++ /dev/null @@ -1,175 +0,0 @@ -import * as types from './types'; - - -export class Input { - - constructor(name, owner) { - this.__output = null; - this.__value = null; - this.__name = name; - this.__owner = owner - this.__listeners = []; - this.__onchangelistener = (value) => { - this.value = value; - this.__notifyListeners(); - } - } - - get id() { - return `${this.__owner.id}-${this.__name}`; - } - - get name() { - return this.__name; - } - - get value() { - return this.__value; - } - - set value(value) { - this.__value = value; - this.__owner.update(this.name); - } - - connect(output) { - this.disconnect(); - this.__output = output - this.__output.onchange(this.__onchangelistener); - } - - disconnect(output) { - if (this.__output) { - this.__output.offchange(this.__onchangelistener); - this.__output = null; - } - } - - get output() { - return this.__output; - } - - __notifyListeners() { - for (let listener of this.__listeners) { - listener(this.value, this.name); - } - } - - onchange(listener) { - this.__listeners.push(listener); - } - - offchange(listener) { - this.__listeners = this.__listeners - .filter((l) => l !== listener); - } - - serialize() { - return { - value: this.__output ? null : this.__value, - output: this.__output ? this.__output.id: null, - } - } -} - -export class Output { - - constructor(name, owner) { - this.__value = null; - this.__name = name; - this.__owner = owner - this.__listeners = []; - } - - get id() { - return `${this.__owner.id}-${this.__name}`; - } - - get name() { - return this.__name; - } - - get value() { - return this.__value; - } - - set value(value) { - this.__value = value; - this.__notifyListeners(); - } - - __notifyListeners() { - for (let listener of this.__listeners) { - listener(this.value, this.name); - } - } - - onchange(listener) { - this.__listeners.push(listener); - } - - offchange(listener) { - this.__listeners = this.__listeners - .filter((l) => l !== listener); - } -} - - -export class Inputs { - - constructor(variables, owner) { - this.__values = {}; - this.__owner = owner; - this.variables = variables; - - for (let name of Object.keys(this.variables)) { - this.__values[name] = new Input(name, this); - Object.defineProperty(this, name, { - get() { - return this.__values[name]; - }, - }) - } - } - - get id() { - return `${this.__owner.id}-in`; - } - - update(changed) { - - } - - serialize() { - const res = {}; - for (let name of Object.keys(this.variables)) { - res[name] = this.__values[name].serialize(); - } - return res; - } -} - -export class Outputs { - constructor(variables, owner) { - this.__values = {}; - this.__owner = owner; - this.variables = variables; - - for (let name of Object.keys(this.variables)) { - this.__values[name] = new Output(name, this); - Object.defineProperty(this, name, { - get() { - return this.__values[name]; - }, - }) - } - } - - get id() { - return `${this.__owner.id}-out`; - } - - serialize() { - return null; - } -} \ No newline at end of file diff --git a/src/nodes/io/AbstractIO.js b/src/nodes/io/AbstractIO.js new file mode 100644 index 0000000..0020195 --- /dev/null +++ b/src/nodes/io/AbstractIO.js @@ -0,0 +1,97 @@ +const isNil = (val) => val === null || val === undefined; + +export const defaultConstrainSatisfied = (value) => { + return !isNil(value); +} + +export const imageConstrainSatisfied = (value) => { + return value instanceof HTMLCanvasElement || + value instanceof Image || + value instanceof HTMLVideoElement || + value instanceof ImageData || + value instanceof ImageBitmap; +}; + +export const colorConstrainSatisfied = (value) => { + return /#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/.test(value); +}; + +export const numberConstrainsSatisfied = (definition, value) => { + return typeof(value) === 'number' && !isNaN(value) && + (isNil(definition.min) || (value >= definition.min)) && + (isNil(definition.max) || (value <= definition.max)) +} + +export const valueConstrainsSatisfied = (definition, value) => { + return defaultConstrainSatisfied(value) && ( + definition.type === 'Number' ? numberConstrainsSatisfied(definition, value) : + definition.type === 'Color' ? colorConstrainSatisfied(value) : + definition.type === 'Image' ? imageConstrainSatisfied(value) : + true + ) +}; + +export default class AbstractIO { + + constructor(name, definition, owner) { + this.__name = name; + this.__owner = owner + this.__definition = definition; + this.__value = null; + this.__listeners = []; + } + + get definition() { + return this.__definition; + } + + get __defaultValue() { + return this.__definition.default; + } + + get id() { + return `${this.__owner.id}-${this.__name}`; + } + + get name() { + return this.__name; + } + + get type() { + return this.__definition.type; + } + + get value() { + return this.__getValue; + } + + set value(val) { + this.__setValue(val); + } + + __getValue() { + return (valueConstrainsSatisfied(this.__definition, this.__value)) ? this.__value : this.__defaultValue; + } + + __setValue(value) { + if (valueConstrainsSatisfied(this.__definition, value)) { + this.__value = value; + } + } + + __notifyListeners() { + for (let listener of this.__listeners) { + listener(this.value, this.name); + } + } + + onchange(listener) { + this.__listeners.push(listener); + } + + offchange(listener) { + this.__listeners = this.__listeners + .filter((l) => l !== listener); + } + +} \ No newline at end of file diff --git a/src/nodes/io/AbstractIOSet.js b/src/nodes/io/AbstractIOSet.js new file mode 100644 index 0000000..3b0be57 --- /dev/null +++ b/src/nodes/io/AbstractIOSet.js @@ -0,0 +1,28 @@ + + +export default class AbstractIOSet { + + constructor(variables, owner) { + this.__values = {}; + this.__owner = owner; + this.variables = variables; + + for (let name of Object.keys(this.variables)) { + this.__values[name] = this.__createProperty(name, variables[name]); + Object.defineProperty(this, name, { + get() { + return this.__values[name]; + }, + }) + } + } + + __createProperty(name, definition) { + + } + + + update(changed) { + + } +} diff --git a/src/nodes/io/Input.js b/src/nodes/io/Input.js new file mode 100644 index 0000000..223b3c3 --- /dev/null +++ b/src/nodes/io/Input.js @@ -0,0 +1,49 @@ +import AbstractIO from './AbstractIO'; + +export default class Input extends AbstractIO { + + constructor(name, definition, owner) { + super(name, definition, owner); + this.__output = null; + + this.__onchangelistener = (value) => { + this.value = value; + this.__notifyListeners(); + } + } + + get value() { + return this.__getValue(); + } + + set value(value) { + this.__setValue(value); + this.__owner.update(this.name); + } + + get output() { + return this.__output; + } + + connect(output) { + if (output && output.type === this.type) { + this.disconnect(); + this.__output = output + this.__output.onchange(this.__onchangelistener); + } + } + + disconnect(output) { + if (this.__output) { + this.__output.offchange(this.__onchangelistener); + this.__output = null; + } + } + + serialize() { + return { + value: this.__output ? null : this.__value, + output: this.__output ? this.__output.id: null, + } + } +} \ No newline at end of file diff --git a/src/nodes/io/Inputs.js b/src/nodes/io/Inputs.js new file mode 100644 index 0000000..8e374fb --- /dev/null +++ b/src/nodes/io/Inputs.js @@ -0,0 +1,25 @@ +import Input from './Input'; +import AbstractIOSet from './AbstractIOSet'; + +export default class Inputs extends AbstractIOSet { + + constructor(variables, owner) { + super(variables, owner); + } + + __createProperty(name, definition) { + return new Input(name, definition, this); + } + + get id() { + return `${this.__owner.id}-in`; + } + + serialize() { + const res = {}; + for (let name of Object.keys(this.variables)) { + res[name] = this.__values[name].serialize(); + } + return res; + } +} \ No newline at end of file diff --git a/src/nodes/io/Inputs.spec.js b/src/nodes/io/Inputs.spec.js new file mode 100644 index 0000000..5b5603c --- /dev/null +++ b/src/nodes/io/Inputs.spec.js @@ -0,0 +1,36 @@ +import Inputs from './Inputs'; + +describe('Inputs', () => { + it('should create with default values', () => { + const owner = { + id: 'owner', + update(name) { + + } + }; + + const inputs = new Inputs({ + x: { + type: 'Number', + min: 0, + max: 1, + default: 0, + }, + y: { + type: 'Number', + default: 1 + }, + foo: { + type: 'String', + default: 'foo' + }, + }, owner) + + expect(inputs.x.type).toBe('Number'); + expect(inputs.x.value).toBe(0); + expect(inputs.y.value).toBe(1); + + inputs.x.value = 1; + expect(inputs.x.value).toBe(1); + }); +}); \ No newline at end of file diff --git a/src/nodes/io/Output.js b/src/nodes/io/Output.js new file mode 100644 index 0000000..123c375 --- /dev/null +++ b/src/nodes/io/Output.js @@ -0,0 +1,40 @@ +import AbstractIO from './AbstractIO'; + +export default class Output extends AbstractIO { + + constructor(name, definition, owner) { + super(name, definition, owner); + } + + get id() { + return `${this.__owner.id}-${this.__name}`; + } + + get name() { + return this.__name; + } + + get value() { + return this.__getValue(); + } + + set value(value) { + this.__setValue(value); + this.__notifyListeners(); + } + + __notifyListeners() { + for (let listener of this.__listeners) { + listener(this.value, this.name); + } + } + + onchange(listener) { + this.__listeners.push(listener); + } + + offchange(listener) { + this.__listeners = this.__listeners + .filter((l) => l !== listener); + } +} \ No newline at end of file diff --git a/src/nodes/io/Outputs.js b/src/nodes/io/Outputs.js new file mode 100644 index 0000000..9327b64 --- /dev/null +++ b/src/nodes/io/Outputs.js @@ -0,0 +1,20 @@ +import Output from './Output'; +import AbstractIOSet from './AbstractIOSet'; + +export default class Outputs extends AbstractIOSet { + constructor(variables, owner) { + super(variables, owner); + } + + __createProperty(name, definition) { + return new Output(name, definition, this); + } + + get id() { + return `${this.__owner.id}-out`; + } + + serialize() { + return null; + } +} \ No newline at end of file diff --git a/src/nodes/io/Outputs.spec.js b/src/nodes/io/Outputs.spec.js new file mode 100644 index 0000000..86040f3 --- /dev/null +++ b/src/nodes/io/Outputs.spec.js @@ -0,0 +1,30 @@ +import Outputs from './Outputs'; + +describe('Outputs', () => { + it('should create with default values', () => { + const owner = { + id: 'owner', + }; + + const inputs = new Outputs({ + x: { + type: 'Number', + min: 0, + max: 1, + default: 0, + }, + y: { + type: 'Number', + default: 1 + }, + foo: { + type: 'String', + default: 'foo' + }, + }, owner) + + expect(inputs.x.type).toBe('Number'); + expect(inputs.x.value).toBe(0); + expect(inputs.y.value).toBe(1); + }); +}); \ No newline at end of file diff --git a/src/nodes/io/index.js b/src/nodes/io/index.js new file mode 100644 index 0000000..ff6088a --- /dev/null +++ b/src/nodes/io/index.js @@ -0,0 +1,4 @@ +export {default as Input} from './Input'; +export {default as Inputs} from './Inputs'; +export {default as Output} from './Output'; +export {default as Outputs} from './Outputs'; \ No newline at end of file diff --git a/ui/Components/Graph.vue b/ui/Components/Graph.vue index 49ab0ea..fc00a09 100644 --- a/ui/Components/Graph.vue +++ b/ui/Components/Graph.vue @@ -22,6 +22,7 @@ :selectedOutput="selectedOutput" @outputSelected="selectedOutput = $event" @inputSelected="selectedInput = $event" + @removeNode="removeNode" /> node === nodeToRemove); + console.log('nodeIndex', index); + this.graph.splice(index, 1); } }, computed: { diff --git a/ui/Components/Node.vue b/ui/Components/Node.vue index 15137c5..b029319 100644 --- a/ui/Components/Node.vue +++ b/ui/Components/Node.vue @@ -21,8 +21,9 @@ >× @@ -43,12 +44,18 @@ @click="$emit('outputSelected', node.out[name])" >{{ name }} +
+ × +
@@ -144,4 +154,18 @@ export default { .node__var { margin-bottom: 2px } + +.node__remove { + position: absolute; + top: -20px; + right: -20px; + background-color: red; + color: white; + border-radius: 50%; + width: 20px; + height: 20px; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/ui/Components/Value/Image.vue b/ui/Components/Value/Image.vue index 84fabd1..e53844a 100644 --- a/ui/Components/Value/Image.vue +++ b/ui/Components/Value/Image.vue @@ -1,7 +1,7 @@