♻️ better types system
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
},
|
||||
});
|
||||
|
||||
12
src/nodes/io.d.ts
vendored
12
src/nodes/io.d.ts
vendored
@@ -1,12 +0,0 @@
|
||||
import * as types from './types'
|
||||
|
||||
|
||||
export class Inputs<T extends string> {
|
||||
constructor(variables: T[]);
|
||||
[k: string]: types.Input<any>
|
||||
}
|
||||
|
||||
export class Outputs<T extends string> {
|
||||
constructor(variables: T[]);
|
||||
[k: string]: types.Input<any>
|
||||
}
|
||||
175
src/nodes/io.js
175
src/nodes/io.js
@@ -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;
|
||||
}
|
||||
}
|
||||
97
src/nodes/io/AbstractIO.js
Normal file
97
src/nodes/io/AbstractIO.js
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
28
src/nodes/io/AbstractIOSet.js
Normal file
28
src/nodes/io/AbstractIOSet.js
Normal file
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
49
src/nodes/io/Input.js
Normal file
49
src/nodes/io/Input.js
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/nodes/io/Inputs.js
Normal file
25
src/nodes/io/Inputs.js
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
36
src/nodes/io/Inputs.spec.js
Normal file
36
src/nodes/io/Inputs.spec.js
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
40
src/nodes/io/Output.js
Normal file
40
src/nodes/io/Output.js
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
20
src/nodes/io/Outputs.js
Normal file
20
src/nodes/io/Outputs.js
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
30
src/nodes/io/Outputs.spec.js
Normal file
30
src/nodes/io/Outputs.spec.js
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
4
src/nodes/io/index.js
Normal file
4
src/nodes/io/index.js
Normal file
@@ -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';
|
||||
Reference in New Issue
Block a user