diff --git a/package.json b/package.json index 5f3f0e9..0061f57 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "graphfx", - "version": "0.0.15", + "version": "0.1.0", "description": "Graph image processing pipeline", - "main": "src/index.js", + "main": "src/index.ts", "scripts": { "dev": "webpack-dev-server --quiet --progress --watch --mode development", "build": "webpack --mode production --quiet --progress", diff --git a/src/canvas/CanvasPool.js b/src/canvas/CanvasPool.ts similarity index 55% rename from src/canvas/CanvasPool.js rename to src/canvas/CanvasPool.ts index b850d39..05f0dad 100644 --- a/src/canvas/CanvasPool.js +++ b/src/canvas/CanvasPool.ts @@ -1,5 +1,13 @@ +type PoolCanvas = T & { + acquire(): void + release(): void +} -class CanvasPool { +class CanvasPool { + + __ctx: any + __pool: PoolCanvas[] + __pointerCounter: WeakMap, number> constructor(ctx) { this.__ctx = ctx; @@ -9,26 +17,28 @@ class CanvasPool { __createNewCanvas() { const canvas = (this.__ctx !== 'webgl') ? new OffscreenCanvas(1,1) : document.createElement('canvas'); + // @ts-ignore canvas.acquire = this.acquireCanvas.bind(this, canvas); + // @ts-ignore canvas.release = this.releaseCanvas.bind(this, canvas); canvas.getContext(this.__ctx); - this.__pool.push(canvas); - this.__pointerCounter.set(canvas, 0); + this.__pool.push(>canvas); + this.__pointerCounter.set(>canvas, 0); } - createCanvas() { + createCanvas():PoolCanvas { if (this.__pool.length === 0) { this.__createNewCanvas(); } return this.__pool.pop(); } - acquireCanvas(canvas) { + acquireCanvas(canvas: PoolCanvas) { const inUse = this.__pointerCounter.get(canvas) + 1; this.__pointerCounter.set(canvas, inUse); } - releaseCanvas(canvas) { + releaseCanvas(canvas: PoolCanvas) { const inUse = this.__pointerCounter.get(canvas) - 1; this.__pointerCounter.set(canvas, inUse); if (inUse === 0) { @@ -38,5 +48,5 @@ class CanvasPool { } -export const canvasPool2D = new CanvasPool('2d'); -export const canvasPoolWebGL = new CanvasPool('webgl'); +export const canvasPool2D = new CanvasPool('2d'); +export const canvasPoolWebGL = new CanvasPool('webgl'); diff --git a/src/graph.js b/src/graph.ts similarity index 53% rename from src/graph.js rename to src/graph.ts index 100ca77..49f4c13 100644 --- a/src/graph.js +++ b/src/graph.ts @@ -1,14 +1,34 @@ -import * as nodes from './nodes'; +import * as nodes from './nodes/index'; +import Node from './nodes/Node' import {MultiSubject} from './helpers/listener'; +interface GraphNode { + node: Node, + x?: number, + y?: number +} export default class Graph { - constructor() { - this.nodes = []; + nodes: GraphNode[] + __nodesActive: number + __isRunningSet: WeakSet + subject: MultiSubject + + constructor(nodes=[]) { + this.__isRunningSet = new WeakSet(); + this.nodes = nodes; this.__nodesActive = 0; - this.__nodesActiveDebounce = null; this.subject = new MultiSubject(['running', 'stopped']); + this.__setupRunningListeners(); + } + + get isRunning() { + return this.__nodesActive > 0; + } + + get isStopped() { + return this.__nodesActive === 0; } serialize() { @@ -18,28 +38,38 @@ export default class Graph { })); } - static async deserialize(nodes) { - const graph = new Graph(); - const isRunningSet = new WeakSet(); - graph.nodes = await createUnconnectedNodes(nodes); - for (const {node} of graph.nodes) { - node.subject.on('running', (isRunning) => { - if (!isRunning && isRunningSet.has(node)) { - isRunningSet.delete(node); - graph.__nodesActive -= 1; - } else if (isRunning && !isRunningSet.has(node)) { - isRunningSet.add(node); - graph.__nodesActive += 1; - } - if (graph.__nodesActive > 0) { - graph.subject.next('running'); - } else { - console.info('[Graphfx] graph stopped'); - graph.subject.next('stopped'); - } - }); + addNode(node) { + this.nodes.push({node}); + this.__setupNodeRunningListener(node); + } + + __setupNodeRunningListener(node) { + node.subject.on('running', (isRunning) => { + if (!isRunning && this.__isRunningSet.has(node)) { + this.__isRunningSet.delete(node); + this.__nodesActive -= 1; + } else if (isRunning && !this.__isRunningSet.has(node)) { + this.__isRunningSet.add(node); + this.__nodesActive += 1; } - graph.nodes = await reconnectNodes(graph.nodes); + if (this.__nodesActive > 0) { + this.subject.next('running'); + } else { + console.info('[Graphfx] graph stopped'); + this.subject.next('stopped'); + } + }); + } + + __setupRunningListeners() { + for (const {node} of this.nodes) { + this.__setupNodeRunningListener(node); + } + } + + static async deserialize(nodes) { + const graph = new Graph(await createUnconnectedNodes(nodes)); + await reconnectNodes(graph.nodes); return graph; } @@ -71,17 +101,20 @@ export default class Graph { -export const createNode = async ({name, options, id}) => { +export async function createNode( + {name, options, id}:{name: T, options: any, id: string}):Promise { try { const node = new nodes[name](options); await node.deserialize({id, options}); + // @ts-ignore return node; } catch (err) { console.error('Failed to create node', name, err) } } -export const createUnconnectedNodes = async (nodes) => { +export const createUnconnectedNodes = async (nodes):Promise => { + // @ts-ignore return (await Promise.all(nodes.map(async ({node, x, y}) => { const nodeInstance = await createNode(node); return ({ @@ -113,7 +146,8 @@ export const reconnectNodes = async (nodes) => { })) } -export const createNodes = async (nodes) => { - nodes = await createUnconnectedNodes(nodes); - return reconnectNodes(nodes); +export const createNodes = async (nodes):Promise => { + const unconnectedNodes = await createUnconnectedNodes(nodes); + // @ts-ignore + return reconnectNodes(unconnectedNodes); } diff --git a/src/index.js b/src/index.ts similarity index 100% rename from src/index.js rename to src/index.ts diff --git a/src/nodes/Canvas2d/Canvas2d.js b/src/nodes/Canvas2d/Canvas2d.ts similarity index 72% rename from src/nodes/Canvas2d/Canvas2d.js rename to src/nodes/Canvas2d/Canvas2d.ts index bae1de1..f03d535 100644 --- a/src/nodes/Canvas2d/Canvas2d.js +++ b/src/nodes/Canvas2d/Canvas2d.ts @@ -1,26 +1,38 @@ import Node from '../Node'; -import {waitForMedia} from '../../utils'; +import {waitForMedia, merge} from '../../utils'; import {canvasPool2D} from '../../canvas/CanvasPool'; +import { + Variables, + ImageVar, + BooleanVar, + NumberVar +} from '../io/AbstractIOSet'; -export default class Canvas2d extends Node { - constructor(name, inputDefinition, outputDefiniton, options) { +const inputs = { + image: { + type: 'Image', + } as ImageVar, +}; + +const outputs = { + image: { + type: 'Image', + } as ImageVar, + width: { + type: 'Number', + } as NumberVar, + height: { + type: 'Number', + } as NumberVar +}; + + + +export default class Canvas2d extends Node { + constructor(name, inputDefinition: I, outputDefiniton: O, options) { super(name, - Object.assign({ - image: { - type: 'Image', - }, - }, inputDefinition), - Object.assign({ - image: { - type: 'Image', - }, - width: { - type: 'Number', - }, - height: { - type: 'Number', - } - }, outputDefiniton), + merge(inputs, inputDefinition), + merge(outputs, outputDefiniton), options ); } diff --git a/src/nodes/Canvas2d/Compose.js b/src/nodes/Canvas2d/Compose.js deleted file mode 100644 index 07ed447..0000000 --- a/src/nodes/Canvas2d/Compose.js +++ /dev/null @@ -1,154 +0,0 @@ -import Canvas2d from './Canvas2d'; -import {createCanvas, mediaSize, paintToCanvas} from '../canvas'; - -export default class Compose extends Canvas2d { - - constructor(options) { - super('Compose', { - image: null, - 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: { - type: 'Image' - } - }, options); - } - - get width() { - return this.__in.width.value; - } - - get height() { - return this.__in.height.value; - } - - get fg() { - if (!this.__in.fg.value) { - return null; - } else { - const {width, height} = this.__in.fg.value; - return { - image: this.__in.fg.value, - top: this.__in.fgY.value || 0, - left: this.__in.fgX.value || 0, - width, - height, - }; - } - } - - get bg() { - if (!this.__in.bg.value) { - return null; - } else { - const {width, height} = this.__in.bg.value; - return { - image: this.__in.bg.value, - top: this.__in.bgY.value || 0, - left: this.__in.bgX.value || 0, - width, - height, - }; - } - } - - /** - * @param {any} values - * @param {HTMLCanvasElement} canvas - * @param {CanvasRenderingContext2D} ctx - */ - async render({width, height, mode}, canvas, ctx) { - if (!width || !height) { - return; - } - if (!this.bg && !this.fg) { - return; - } - - canvas.width = width; - canvas.height = height; - - ctx.globalCompositeOperation = 'source-over'; - - if (this.bg) { - paintToCanvas(canvas, this.bg.image, this.bg); - } - - ctx.globalCompositeOperation = mode; - - if (this.fg) { - paintToCanvas(canvas, this.fg.image, this.fg); - } - - return canvas; - } -} \ No newline at end of file diff --git a/src/nodes/Canvas2d/Compose.ts b/src/nodes/Canvas2d/Compose.ts new file mode 100644 index 0000000..4578d4c --- /dev/null +++ b/src/nodes/Canvas2d/Compose.ts @@ -0,0 +1,163 @@ +import Canvas2d from './Canvas2d'; +import {createCanvas, mediaSize, paintToCanvas} from '../canvas'; +import { + ImageVar, + StringVar, + NumberVar +} from '../io/AbstractIOSet'; + +const inputs = { + image: null, + fg: { + type: 'Image' + } as ImageVar, + fgX: { + type: 'Number', + default: 0, + step: 1, + } as NumberVar, + fgY: { + type: 'Number', + default: 0, + step: 1, + } as NumberVar, + bg: { + type: 'Image', + } as ImageVar, + bgX: { + type: 'Number', + default: 0, + step: 1, + } as NumberVar, + bgY: { + type: 'Number', + default: 0, + step: 1, + } as NumberVar, + width: { + type: 'Number', + default: 100, + step: 1, + min: 1 + } as NumberVar, + height: { + type: 'Number', + default: 100, + step: 1, + min: 1, + } as NumberVar, + 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', + ] + } as StringVar +} + +const outputs = { + image: { + type: 'Image' + } as ImageVar +} + +export default class Compose extends Canvas2d { + + constructor(options={}) { + super('Compose', inputs, outputs, options); + } + + get width() { + return this.__in.width.value; + } + + get height() { + return this.__in.height.value; + } + + get fg() { + if (!this.__in.fg.value) { + return null; + } else { + const {width, height} = this.__in.fg.value; + return { + image: this.__in.fg.value, + top: this.__in.fgY.value || 0, + left: this.__in.fgX.value || 0, + width, + height, + }; + } + } + + get bg() { + if (!this.__in.bg.value) { + return null; + } else { + const {width, height} = this.__in.bg.value; + return { + image: this.__in.bg.value, + top: this.__in.bgY.value || 0, + left: this.__in.bgX.value || 0, + width, + height, + }; + } + } + + /** + * @param {any} values + * @param {HTMLCanvasElement} canvas + * @param {CanvasRenderingContext2D} ctx + */ + async render({width, height, mode}, canvas, ctx) { + if (!width || !height) { + return; + } + if (!this.bg && !this.fg) { + return; + } + + canvas.width = width; + canvas.height = height; + + ctx.globalCompositeOperation = 'source-over'; + + if (this.bg) { + paintToCanvas(canvas, this.bg.image, this.bg); + } + + ctx.globalCompositeOperation = mode; + + if (this.fg) { + paintToCanvas(canvas, this.fg.image, this.fg); + } + + return canvas; + } +} \ No newline at end of file diff --git a/src/nodes/Canvas2d/EmptySpace.js b/src/nodes/Canvas2d/EmptySpace.ts similarity index 52% rename from src/nodes/Canvas2d/EmptySpace.js rename to src/nodes/Canvas2d/EmptySpace.ts index e97f171..b5a0b70 100644 --- a/src/nodes/Canvas2d/EmptySpace.js +++ b/src/nodes/Canvas2d/EmptySpace.ts @@ -1,30 +1,35 @@ import Canvas2d from './Canvas2d'; import {createCanvas, mediaSize, paintToCanvas} from '../canvas'; +import { + NumberVar +} from '../io/AbstractIOSet'; -export default class EmptySpace extends Canvas2d { +const inputs = {}; +const outputs = { + w: { + type: 'Number', + } as NumberVar, + h: { + type: 'Number', + } as NumberVar, + top: { + type: 'Number', + } as NumberVar, + right: { + type: 'Number', + } as NumberVar, + bottom: { + type: 'Number', + } as NumberVar, + left: { + type: 'Number', + } as NumberVar +}; + +export default class EmptySpace extends Canvas2d { constructor() { - super('EmptySpace', { - }, { - w: { - type: 'Number', - }, - h: { - type: 'Number', - }, - top: { - type: 'Number', - }, - right: { - type: 'Number', - }, - bottom: { - type: 'Number', - }, - left: { - type: 'Number', - } - }); + super('EmptySpace', inputs, outputs, {}); this._update(); } @@ -42,22 +47,23 @@ export default class EmptySpace extends Canvas2d { canvas.height = height; ctx.drawImage(image, 0, 0); const imageData = ctx.getImageData(0, 0, width, height) - for (let offset=0; offset { + + constructor() { + super('Fill', inputs, outputs); + this._update(); + } + + async render({color, width, height}, canvas, ctx) { + canvas.width = width; + canvas.height = height; + ctx.fillStyle = color; + ctx.fillRect(0, 0, width, height); + return canvas; + } +} \ No newline at end of file diff --git a/src/nodes/Canvas2d/Flip.js b/src/nodes/Canvas2d/Flip.ts similarity index 63% rename from src/nodes/Canvas2d/Flip.js rename to src/nodes/Canvas2d/Flip.ts index c5ef97e..deedad2 100644 --- a/src/nodes/Canvas2d/Flip.js +++ b/src/nodes/Canvas2d/Flip.ts @@ -1,19 +1,26 @@ import Canvas2d from './Canvas2d'; import {createCanvas, mediaSize, paintToCanvas} from '../canvas'; +import { + BooleanVar + } from '../io/AbstractIOSet'; -export default class Flip extends Canvas2d { +const inputs = { + horizontal: { + type: 'Boolean', + default: false, + } as BooleanVar, + vertical: { + type: 'Boolean', + default: false, + } as BooleanVar, +} + +const outputs = {}; + +export default class Flip extends Canvas2d { constructor() { - super('Flip', { - horizontal: { - type: 'Boolean', - default: false, - }, - vertical: { - type: 'Boolean', - default: false, - }, - }, {}); + super('Flip', inputs, outputs, {}); this._update(); } diff --git a/src/nodes/Canvas2d/Resize.js b/src/nodes/Canvas2d/Resize.ts similarity index 75% rename from src/nodes/Canvas2d/Resize.js rename to src/nodes/Canvas2d/Resize.ts index 799a396..0666bc9 100644 --- a/src/nodes/Canvas2d/Resize.js +++ b/src/nodes/Canvas2d/Resize.ts @@ -1,22 +1,33 @@ import Canvas2d from './Canvas2d'; import {waitForMedia} from '../../utils'; import {createCanvas, mediaSize, paintToCanvas} from '../canvas'; +import { + ImageVar, + StringVar, + NumberVar +} from '../io/AbstractIOSet'; -export default class Resize extends Canvas2d { + + +const inputs = { + width: { + type: 'Number', + default: 100, + min: 1, + } as NumberVar, + height: { + type: 'Number', + default: 100, + min: 1, + } as NumberVar, +}; + +const outputs = {}; + +export default class Resize extends Canvas2d { constructor() { - super('Resize', { - width: { - type: 'Number', - default: 100, - min: 1, - }, - height: { - type: 'Number', - default: 100, - min: 1, - }, - }, {}); + super('Resize', inputs, outputs, {}); } /** diff --git a/src/nodes/Canvas2d/Text.js b/src/nodes/Canvas2d/Text.js deleted file mode 100644 index a2f8f74..0000000 --- a/src/nodes/Canvas2d/Text.js +++ /dev/null @@ -1,91 +0,0 @@ -import Canvas2d from './Canvas2d'; -import {waitForMedia} from '../../utils'; -import {createCanvas, mediaSize, paintToCanvas} from '../canvas'; - -const isEmpty = (x) => !x || Object.keys(x).length === 0; - -export default class Resize extends Canvas2d { - - constructor() { - super('Text', { - image: null, - text: { - type: 'String', - default: '', - }, - fontSize: { - type: 'Number', - min: 1, - default: 50, - }, - fontStyle: { - type: 'String', - default: 'normal', - enum: [ - 'normal', - 'bold', - 'italic', - 'bold italic', - ] - }, - font: { - type: 'Font', // {name, url, fontface} - default: 'Arial', - }, - width: { - type: 'Number', - default: 100, - min: 1, - }, - height: { - type: 'Number', - default: 100, - min: 1, - }, - color: { - type: 'Color', - default: '#FFFFFF' - }, - textAlign: { - type: 'String', - default: 'center', - enum: [ - 'left', - 'center', - 'right', - ] - } - }, {}); - this._update(); - } - - /** - * - * @param {{name: string, url: string, fontface: FontFace, type: 'font'}} font - */ - async loadFont(font) { - if (typeof font === 'string') { - return font; - } else if (!font) { - return; - } else { - return font.name; - } - } - - async render({font, fontSize, text, color, width, height, textAlign, fontStyle}, canvas, ctx) { - canvas.width = width; - canvas.height = height; - - ctx.font = `${fontStyle} ${fontSize}px "${(await this.loadFont(font)) || ''}"`; - // ctx.font = `${fontStyle} ${fontSize}px "Arial"`; - ctx.fillStyle = color; - ctx.textAlign = textAlign; - const x = textAlign === 'center' ? canvas.width /2 : - textAlign === 'left' ? 0 : canvas.width; - text.split('\\n').forEach((line, index) => { - ctx.fillText(line, x, fontSize * (1 + index)); - }); - return canvas; - } -} \ No newline at end of file diff --git a/src/nodes/Canvas2d/Text.ts b/src/nodes/Canvas2d/Text.ts new file mode 100644 index 0000000..93a3142 --- /dev/null +++ b/src/nodes/Canvas2d/Text.ts @@ -0,0 +1,101 @@ +import Canvas2d from './Canvas2d'; +import {waitForMedia} from '../../utils'; +import {createCanvas, mediaSize, paintToCanvas} from '../canvas'; +import { + NumberVar, + StringVar, + FontVar, + ColorVar, +} from '../io/AbstractIOSet'; + +const isEmpty = (x) => !x || Object.keys(x).length === 0; + +const inputs = { + image: null, + text: { + type: 'String', + default: '', + } as StringVar, + fontSize: { + type: 'Number', + min: 1, + default: 50, + } as NumberVar, + fontStyle: { + type: 'String', + default: 'normal', + enum: [ + 'normal', + 'bold', + 'italic', + 'bold italic', + ] + } as StringVar, + font: { + type: 'Font', // {name, url, fontface} + default: 'Arial', + } as FontVar, + width: { + type: 'Number', + default: 100, + min: 1, + } as NumberVar, + height: { + type: 'Number', + default: 100, + min: 1, + } as NumberVar, + color: { + type: 'Color', + default: '#FFFFFF' + } as ColorVar, + textAlign: { + type: 'String', + default: 'center', + enum: [ + 'left', + 'center', + 'right', + ] + } as StringVar +}; + +const outputs = {}; + +export default class Resize extends Canvas2d { + + constructor() { + super('Text', inputs, outputs, {}); + this._update(); + } + + /** + * + * @param {{name: string, url: string, fontface: FontFace, type: 'font'}} font + */ + async loadFont(font) { + if (typeof font === 'string') { + return font; + } else if (!font) { + return; + } else { + return font.name; + } + } + + async render({font, fontSize, text, color, width, height, textAlign, fontStyle}, canvas, ctx) { + canvas.width = width; + canvas.height = height; + + ctx.font = `${fontStyle} ${fontSize}px "${(await this.loadFont(font)) || ''}"`; + // ctx.font = `${fontStyle} ${fontSize}px "Arial"`; + ctx.fillStyle = color; + ctx.textAlign = textAlign; + const x = textAlign === 'center' ? canvas.width /2 : + textAlign === 'left' ? 0 : canvas.width; + text.split('\\n').forEach((line, index) => { + ctx.fillText(line, x, fontSize * (1 + index)); + }); + return canvas; + } +} \ No newline at end of file diff --git a/src/nodes/Disable.js b/src/nodes/Disable.ts similarity index 51% rename from src/nodes/Disable.js rename to src/nodes/Disable.ts index 34bb7b5..52c9827 100644 --- a/src/nodes/Disable.js +++ b/src/nodes/Disable.ts @@ -1,26 +1,32 @@ import Node from './Node'; +import {ImageVar, BooleanVar, NumberVar} from './io/AbstractIOSet'; -export default class Disable extends Node { +const inputs = { + image: { + type: 'Image', + } as ImageVar, + disabled: { + type: 'Boolean', + default: false, + } as BooleanVar, +}; + +const outputs = { + image: { + type: 'Image', + } as ImageVar, + width: { + type: 'Number', + } as NumberVar, + height: { + type: 'Number', + } as NumberVar, +}; + + +export default class Disable extends Node { constructor() { - super('Disable', { - image: { - type: 'Image', - }, - disabled: { - type: 'Boolean', - default: false, - }, - }, { - image: { - type: 'Image', - }, - width: { - type: 'Number', - }, - height: { - type: 'Number', - }, - }, {}); + super('Disable', inputs, outputs, {}); } async _update() { diff --git a/src/nodes/Font.js b/src/nodes/Font.js deleted file mode 100644 index 691c5dd..0000000 --- a/src/nodes/Font.js +++ /dev/null @@ -1,42 +0,0 @@ -import Node from './Node'; - -export default class Font extends Node { - constructor() { - super('Font', { - fontSize: { - type: 'Number', - min: 1, - default: 50, - }, - fontStyle: { - type: 'String', - default: 'normal', - enum: [ - 'normal', - 'bold', - 'italic', - 'bold italic', - ] - }, - font: { - type: 'Font', - }, - }, { - fontSize: { - type: 'Number', - }, - fontStyle: { - type: 'String' - }, - font: { - type: 'Font', - }, - }, {}); - } - - async _update() { - this.out.font.value = this.in.font.value; - this.out.fontStyle.value = this.in.fontStyle.value; - } - -} \ No newline at end of file diff --git a/src/nodes/Font.ts b/src/nodes/Font.ts new file mode 100644 index 0000000..1e921d0 --- /dev/null +++ b/src/nodes/Font.ts @@ -0,0 +1,47 @@ +import Node from './Node'; +import {FontVar, StringVar, NumberVar} from './io/AbstractIOSet'; + +const inputs = { + fontSize: { + type: 'Number', + min: 1, + default: 50, + } as NumberVar, + fontStyle: { + type: 'String', + default: 'normal', + enum: [ + 'normal', + 'bold', + 'italic', + 'bold italic', + ] + } as StringVar, + font: { + type: 'Font', + } as FontVar, +} + +const outputs = { + fontSize: { + type: 'Number', + } as NumberVar, + fontStyle: { + type: 'String' + } as StringVar, + font: { + type: 'Font', + } as FontVar, +} + +export default class Font extends Node { + constructor() { + super('Font', inputs, outputs, {}); + } + + async _update() { + this.out.font.value = this.in.font.value; + this.out.fontStyle.value = this.in.fontStyle.value; + } + +} \ No newline at end of file diff --git a/src/nodes/Math/NumberBinaryOperation.js b/src/nodes/Math/NumberBinaryOperation.js deleted file mode 100644 index e9e1316..0000000 --- a/src/nodes/Math/NumberBinaryOperation.js +++ /dev/null @@ -1,56 +0,0 @@ -import Node from '../Node'; - -export default class NumberBinaryOperation extends Node { - constructor() { - super('NumberBinaryOperation', { - x: { - type: 'Number', - default: 0, - }, - y: { - type: 'Number', - default: 0, - }, - operation: { - type: 'String', - default: '+', - enum: [ - '+', - '-', - '⨉', - '÷', - 'mod', - 'max', - 'min', - ] - } - }, { - result: { - type: 'Number' - }, - }, {}); - this.__canvas = document.createElement('canvas'); - } - - async _update() { - const x = this.in.x.value; - const y = this.in.y.value; - const op = this.in.operation.value; - const value = - op === '+' ? x + y : - op === '-' ? x - y : - op === '⨉' ? x * y : - op === '÷' ? - y === 0 ? NaN : - x / y : - op === 'mod' ? x % y : - op === 'max' ? Math.max(x, y) : - op === 'min' ? Math.min(x, y) : - NaN; - - if (!isNaN(value)) { - this.out.result.value = value; - } - } - -} \ No newline at end of file diff --git a/src/nodes/Math/NumberBinaryOperation.ts b/src/nodes/Math/NumberBinaryOperation.ts new file mode 100644 index 0000000..08e61da --- /dev/null +++ b/src/nodes/Math/NumberBinaryOperation.ts @@ -0,0 +1,63 @@ +import Node from '../Node'; +import { + NumberVar, + StringVar, +} from '../io/AbstractIOSet'; + +const inputs = { + x: { + type: 'Number', + default: 0, + } as NumberVar, + y: { + type: 'Number', + default: 0, + } as NumberVar, + operation: { + type: 'String', + default: '+', + enum: [ + '+', + '-', + '⨉', + '÷', + 'mod', + 'max', + 'min', + ] + } as StringVar +}; + +const outputs = { + result: { + type: 'Number' + } as NumberVar, +} + +export default class NumberBinaryOperation extends Node { + constructor() { + super('NumberBinaryOperation', inputs, outputs, {}); + } + + async _update() { + const x = this.in.x.value; + const y = this.in.y.value; + const op = this.in.operation.value; + const value = + op === '+' ? x + y : + op === '-' ? x - y : + op === '⨉' ? x * y : + op === '÷' ? + y === 0 ? NaN : + x / y : + op === 'mod' ? x % y : + op === 'max' ? Math.max(x, y) : + op === 'min' ? Math.min(x, y) : + NaN; + + if (!isNaN(value)) { + this.out.result.value = value; + } + } + +} \ No newline at end of file diff --git a/src/nodes/Math/Numbers.js b/src/nodes/Math/Numbers.js deleted file mode 100644 index 0d90129..0000000 --- a/src/nodes/Math/Numbers.js +++ /dev/null @@ -1,41 +0,0 @@ -import Node from '../Node'; - -export default class Numbers extends Node { - constructor() { - super('Numbers', { - x: { - type: 'Number', - default: 0, - }, - y: { - type: 'Number', - default: 0, - }, - z: { - type: 'Number', - default: 0, - }, - }, { - x: { - type: 'Number', - default: 0, - }, - y: { - type: 'Number', - default: 0, - }, - z: { - type: 'Number', - default: 0, - }, - }, {}); - this.__canvas = document.createElement('canvas'); - } - - async _update() { - this.out.x.value = this.in.x.value; - this.out.y.value = this.in.y.value; - this.out.z.value = this.in.z.value; - } - -} \ No newline at end of file diff --git a/src/nodes/Math/Numbers.ts b/src/nodes/Math/Numbers.ts new file mode 100644 index 0000000..6188d1e --- /dev/null +++ b/src/nodes/Math/Numbers.ts @@ -0,0 +1,49 @@ +import Node from '../Node'; + +import { + NumberVar +} from '../io/AbstractIOSet'; + +const inputs = { + x: { + type: 'Number', + default: 0, + } as NumberVar, + y: { + type: 'Number', + default: 0, + } as NumberVar, + z: { + type: 'Number', + default: 0, + } as NumberVar, +}; + +const outputs = { + x: { + type: 'Number', + default: 0, + } as NumberVar, + y: { + type: 'Number', + default: 0, + } as NumberVar, + z: { + type: 'Number', + default: 0, + } as NumberVar, +} + + +export default class Numbers extends Node { + constructor() { + super('Numbers', inputs, outputs, {}); + } + + async _update() { + this.out.x.value = this.in.x.value; + this.out.y.value = this.in.y.value; + this.out.z.value = this.in.z.value; + } + +} \ No newline at end of file diff --git a/src/nodes/Node.js b/src/nodes/Node.ts similarity index 80% rename from src/nodes/Node.js rename to src/nodes/Node.ts index 1a1a17b..1383f58 100644 --- a/src/nodes/Node.js +++ b/src/nodes/Node.ts @@ -1,15 +1,27 @@ -import {Inputs, Outputs} from './io'; +import {Inputs, Outputs} from './io/index'; import uuidv4 from 'uuid/v4'; +import {Variables, InputProperties, OutputProperties} from './io/AbstractIOSet'; import {MultiSubject} from '../helpers/listener'; -export default class Node { +export default class Node { - constructor(name, inputDefinition, outputDefiniton, options) { + name: string + uid: string + id: string + __in: Inputs & InputProperties + __out: Outputs & OutputProperties + __scheduledUpdate: boolean + __currentUpdate: Promise + __running: boolean + _stopped: boolean + subject: MultiSubject + + constructor(name: string, inputDefinition: I, outputDefiniton: O, options) { this.name = name; this.uid = uuidv4(); this.id = uuidv4(); - this.__in = new Inputs(inputDefinition, this); - this.__out = new Outputs(outputDefiniton, this); + this.__in = new Inputs(inputDefinition, this) as Inputs & InputProperties; + this.__out = new Outputs(outputDefiniton, this) as Outputs & OutputProperties; this.__in.update = (name) => this.__update([name]); this.__scheduledUpdate = false; this.__currentUpdate = Promise.resolve(); diff --git a/src/nodes/WebGL/BrightnessContrast.js b/src/nodes/WebGL/BrightnessContrast.ts similarity index 73% rename from src/nodes/WebGL/BrightnessContrast.js rename to src/nodes/WebGL/BrightnessContrast.ts index f34b78f..41d5830 100644 --- a/src/nodes/WebGL/BrightnessContrast.js +++ b/src/nodes/WebGL/BrightnessContrast.ts @@ -1,19 +1,24 @@ import WebGL from './WebGl'; +import { + NumberVar, +} from '../io/AbstractIOSet'; + +const inputs = { + brightness: { + type: 'Number', + default: 1, + } as NumberVar, + contrast: { + type: 'Number', + default: 1, + } as NumberVar, +}; -export default class BrightnessContrast extends WebGL { +export default class BrightnessContrast extends WebGL { constructor() { - super('BrightnessContrast', { - brightness: { - type: 'Number', - default: 1, - }, - contrast: { - type: 'Number', - default: 1, - }, - }) + super('BrightnessContrast', inputs) } get frag() { diff --git a/src/nodes/WebGL/Channels.js b/src/nodes/WebGL/Channels.ts similarity index 58% rename from src/nodes/WebGL/Channels.js rename to src/nodes/WebGL/Channels.ts index ff23a32..a2692a5 100644 --- a/src/nodes/WebGL/Channels.js +++ b/src/nodes/WebGL/Channels.ts @@ -1,39 +1,43 @@ import WebGL from './WebGl'; +import { + NumberVar +} from '../io/AbstractIOSet'; +const inputs = { + red: { + type: 'Number', + default: 1, + min: 0, + max: 1, + step: 0.1, + } as NumberVar, + green: { + type: 'Number', + default: 1, + min: 0, + max: 1, + step: 0.1, + } as NumberVar, + blue: { + type: 'Number', + default: 1, + min: 0, + max: 1, + step: 0.1, + } as NumberVar, + amount: { + type: 'Number', + default: 0, + min: 0, + max: 1, + step: 0.1, + } as NumberVar +}; -export default class Channels extends WebGL { +export default class Channels extends WebGL { constructor() { - super('Channels', { - red: { - type: 'Number', - default: 1, - min: 0, - max: 1, - step: 0.1, - }, - green: { - type: 'Number', - default: 1, - min: 0, - max: 1, - step: 0.1, - }, - blue: { - type: 'Number', - default: 1, - min: 0, - max: 1, - step: 0.1, - }, - amount: { - type: 'Number', - default: 0, - min: 0, - max: 1, - step: 0.1, - } - }) + super('Channels', inputs) } get frag() { diff --git a/src/nodes/WebGL/Greenscreen.js b/src/nodes/WebGL/Greenscreen.ts similarity index 83% rename from src/nodes/WebGL/Greenscreen.js rename to src/nodes/WebGL/Greenscreen.ts index 861ab63..6b4af83 100644 --- a/src/nodes/WebGL/Greenscreen.js +++ b/src/nodes/WebGL/Greenscreen.ts @@ -1,4 +1,8 @@ import WebGL from './WebGl'; +import { + NumberVar, + ColorVar +} from '../io/AbstractIOSet'; const hexColorTOvec3 = (val) => { const match = val.match(/#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/); @@ -9,35 +13,37 @@ const hexColorTOvec3 = (val) => { } }; -export default class GreenScreen extends WebGL { +const inputs = { + balance: { + type: 'Number', + default: 0.5, + step: 0.1, + } as NumberVar, + screen: { + type: 'Color', + default: '#2CD6A4', + } as ColorVar, + screenWeight: { + type: 'Number', + default: 1, + min: 0, + max: 1, + step: 0.1, + } as NumberVar, + clipBlack: { + type: 'Number', + default: 0, + } as NumberVar, + clipWhite: { + type: 'Number', + default: 1, + } as NumberVar, +}; + +export default class GreenScreen extends WebGL { constructor() { - super('GreenScreen', { - 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, - }, - }) + super('GreenScreen', inputs) } get frag() { diff --git a/src/nodes/WebGL/HSV.js b/src/nodes/WebGL/HSV.ts similarity index 80% rename from src/nodes/WebGL/HSV.js rename to src/nodes/WebGL/HSV.ts index ab29d58..9e76db3 100644 --- a/src/nodes/WebGL/HSV.js +++ b/src/nodes/WebGL/HSV.ts @@ -1,23 +1,28 @@ import WebGL from './WebGl'; +import { + NumberVar +} from '../io/AbstractIOSet'; + +const inputs = { + hue: { + type: 'Number', + default: 0, + } as NumberVar, + saturation: { + type: 'Number', + default: 1, + } as NumberVar, + value: { + type: 'Number', + default: 1, + } as NumberVar +}; -export default class HSV extends WebGL { +export default class HSV extends WebGL { constructor() { - super('HSV', { - hue: { - type: 'Number', - default: 0, - }, - saturation: { - type: 'Number', - default: 1, - }, - value: { - type: 'Number', - default: 1, - } - }) + super('HSV', inputs) } get frag() { diff --git a/src/nodes/WebGL/Sepia.js b/src/nodes/WebGL/Sepia.ts similarity index 82% rename from src/nodes/WebGL/Sepia.js rename to src/nodes/WebGL/Sepia.ts index 3f99eec..45936cd 100644 --- a/src/nodes/WebGL/Sepia.js +++ b/src/nodes/WebGL/Sepia.ts @@ -1,15 +1,19 @@ import WebGL from './WebGl'; +import { + NumberVar +} from '../io/AbstractIOSet'; +const inputs = { + amount: { + type: 'Number', + default: 0, + } as NumberVar, +}; -export default class Sepia extends WebGL { +export default class Sepia extends WebGL { constructor() { - super('Sepia', { - amount: { - type: 'Number', - default: 0, - }, - }) + super('Sepia', inputs) } get frag() { diff --git a/src/nodes/WebGL/WebGl.js b/src/nodes/WebGL/WebGl.ts similarity index 90% rename from src/nodes/WebGL/WebGl.js rename to src/nodes/WebGL/WebGl.ts index 8821a10..9978f5a 100644 --- a/src/nodes/WebGL/WebGl.js +++ b/src/nodes/WebGL/WebGl.ts @@ -1,25 +1,43 @@ import Node from '../Node'; import {createCanvas, mediaSize, paintToCanvas} from '../canvas'; +import {merge} from '../../utils'; import {canvasPool2D} from '../../canvas/CanvasPool'; +import { + Variables, + ImageVar, + NumberVar +} from '../io/AbstractIOSet'; -export default class WebGL extends Node { +const inputs = { + image: { + type: 'Image', + } as ImageVar, +}; - constructor(name, inputs={}) { - super(name, Object.assign({ - image: { - type: 'Image', - }, - }, inputs), { - image: { - type: 'Image', - }, - width: { - type: 'Number', - }, - height: { - type: 'Number', - } - }); +const outputs = { + image: { + type: 'Image', + } as ImageVar, + width: { + type: 'Number', + } as NumberVar, + height: { + type: 'Number', + } as NumberVar +} + +export default class WebGL extends Node { + + canvas: HTMLCanvasElement + program: WebGLProgram + + constructor(name, inputDefinition: I) { + super( + name, + merge(inputs, inputDefinition), + outputs, + {} + ) } destroy() { @@ -75,7 +93,7 @@ export default class WebGL extends Node { ` } - fragShader(gl) { + fragShader(gl: WebGLRenderingContext) { var shader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(shader, this.frag); gl.compileShader(shader); @@ -87,7 +105,7 @@ export default class WebGL extends Node { return shader; } - vertShader(gl) { + vertShader(gl: WebGLRenderingContext) { var shader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(shader, this.vert); gl.compileShader(shader); @@ -99,11 +117,7 @@ export default class WebGL extends Node { return shader; } - /** - * - * @param {WebGLRenderingContext} gl - */ - compileProgram(gl) { + compileProgram(gl: WebGLRenderingContext) { const program = gl.createProgram(); gl.attachShader(program, this.vertShader(gl)); @@ -122,7 +136,7 @@ export default class WebGL extends Node { return this.in.image.value; } - createTexture(gl) { + createTexture(gl: WebGLRenderingContext) { const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); @@ -136,7 +150,7 @@ export default class WebGL extends Node { return texture; } - _createFrameBuffer(gl, {width, height}) { + _createFrameBuffer(gl: WebGLRenderingContext, {width, height}) { const texture = this.createTexture(gl); const frameBuffer = gl.createFramebuffer(); @@ -152,7 +166,7 @@ export default class WebGL extends Node { _passes() { return [ - (gl, program, image) => { + (gl: WebGLRenderingContext, program: WebGLProgram, image) => { this._setParams(gl, program); }, ] diff --git a/src/nodes/canvas.js b/src/nodes/canvas.ts similarity index 52% rename from src/nodes/canvas.js rename to src/nodes/canvas.ts index 435a9d5..eb129db 100644 --- a/src/nodes/canvas.js +++ b/src/nodes/canvas.ts @@ -1,10 +1,4 @@ -/** - * - * @param {Number} width - * @param {Number} height - * @returns {HTMLCanvasElement} - */ -export const createCanvas = (width, height) => { +export const createCanvas = (width: number, height: number):HTMLCanvasElement => { const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; @@ -12,19 +6,11 @@ export const createCanvas = (width, height) => { return canvas; } -/** - * @typedef {HTMLImageElement|HTMLVideoElement} PaintableElement - * @typedef {{top: Number, left: Number, width: Number, height: Number}} Bounds - * @typedef {function(HTMLCanvasElement, PaintableElement, Bounds): HTMLCanvasElement} PaintToCanvasFunction - */ +type Canvas = HTMLCanvasElement | OffscreenCanvas; +export type Bounds = {top: number, left: number, width: number, height: number}; -/** - * - * @param {PaintableElement|{width: Number, height: Number}} media - * @returns {{width: Number, height: Number}} - */ -export const mediaSize = (media) => +export const mediaSize = (media: HTMLImageElement|HTMLVideoElement|{width: number, height: number}) => (media === null) ? {width: null, height: null} : (media instanceof HTMLVideoElement) ? {width: media.videoWidth, height: media.videoHeight} : (media instanceof HTMLImageElement) ? {width: media.naturalWidth, height: media.naturalHeight} : @@ -35,7 +21,11 @@ export const mediaSize = (media) => * @param {PaintableElement} media * @param {Bounds} param2 */ -export const paintToCanvas = (canvas, media, {top, left, width, height}) => { +export const paintToCanvas = ( + canvas: Canvas, + media: CanvasImageSource, + {top, left, width, height}: Bounds +) => { const ctx = canvas.getContext('2d'); if (canvas.width > 0 && canvas.height > 0 && width > 0 && height > 0) { ctx.drawImage(media, left, top, width, height); diff --git a/src/nodes/index.js b/src/nodes/index.ts similarity index 95% rename from src/nodes/index.js rename to src/nodes/index.ts index 0718a53..a671632 100644 --- a/src/nodes/index.js +++ b/src/nodes/index.ts @@ -13,7 +13,7 @@ export {default as HSV} from './WebGL/HSV'; export {default as Sepia} from './WebGL/Sepia'; export {default as GreenScreen} from './WebGL/Greenscreen'; export {default as Channels} from './WebGL/Channels'; -export {default as Blur} from './WebGL/Blur'; +// export {default as Blur} from './WebGL/Blur'; // Math export {default as Numbers} from './Math/Numbers'; export {default as NumberBinaryOperation} from './Math/NumberBinaryOperation'; diff --git a/src/nodes/io/AbstractIO.js b/src/nodes/io/AbstractIO.ts similarity index 80% rename from src/nodes/io/AbstractIO.js rename to src/nodes/io/AbstractIO.ts index 313a9f7..09cf7f8 100644 --- a/src/nodes/io/AbstractIO.js +++ b/src/nodes/io/AbstractIO.ts @@ -1,3 +1,8 @@ +import { + default as AbstractIOSet, + Variable, + VariableValueType, +} from './AbstractIOSet'; import {isNil} from '../../utils'; export const defaultConstrainSatisfied = (value) => { @@ -32,9 +37,16 @@ export const valueConstrainsSatisfied = (definition, value) => { ) }; -export default class AbstractIO { +export default class AbstractIO { - constructor(name, definition, owner) { + __name: string + __owner: AbstractIOSet + __definition: V + __value: VariableValueType + label: string + __listeners: Function[] + + constructor(name, definition: V, owner: AbstractIOSet) { this.__name = name; this.__owner = owner this.__definition = definition; @@ -64,18 +76,18 @@ export default class AbstractIO { } get value() { - return this.__getValue; + return this.__getValue(); } set value(val) { this.__setValue(val); } - __getValue() { - return (valueConstrainsSatisfied(this.__definition, this.__value)) ? this.__value : this.__defaultValue; + __getValue(): VariableValueType { + return (valueConstrainsSatisfied(this.__definition, this.__value)) ? this.__value : this.__defaultValue as VariableValueType; } - __setValue(value) { + __setValue(value: VariableValueType) { if (valueConstrainsSatisfied(this.__definition, value)) { this.__value = value; } diff --git a/src/nodes/io/AbstractIOSet.js b/src/nodes/io/AbstractIOSet.js deleted file mode 100644 index 0254681..0000000 --- a/src/nodes/io/AbstractIOSet.js +++ /dev/null @@ -1,38 +0,0 @@ - - -export default class AbstractIOSet { - - constructor(variables, owner) { - this.__values = {}; - this.__owner = owner; - this.variables = variables; - - for (let name of Object.keys(this.variables)) { - if (variables[name] === null) { - delete this.variables[name]; - continue; - } - this.__values[name] = this.__createProperty(name, variables[name]); - Object.defineProperty(this, name, { - get() { - return this.__values[name]; - }, - }) - } - } - - *[Symbol.iterator]() { - for (let name of Object.keys(this.variables)) { - yield this.__values[name]; - } - } - - __createProperty(name, definition) { - - } - - - update(changed) { - - } -} diff --git a/src/nodes/io/AbstractIOSet.ts b/src/nodes/io/AbstractIOSet.ts new file mode 100644 index 0000000..62dba66 --- /dev/null +++ b/src/nodes/io/AbstractIOSet.ts @@ -0,0 +1,122 @@ +import Node from '../Node'; +import Input from './Input'; +import Output from './Output'; +import AbstractIO from './AbstractIO'; + +type ImageType = HTMLCanvasElement + | OffscreenCanvas + | HTMLImageElement + | HTMLVideoElement + | ImageData + | ImageBitmap; + +export interface NumberVar { + type: 'Number', + min?: number + max?: number + step?: number + default?: VariableValueType +} + +export interface StringVar { + type: 'String', + default?: VariableValueType + enum?: string[] +} + +export interface BooleanVar { + type: 'Boolean', + default?: VariableValueType +} + +export interface ColorVar { + type: 'Color', + default?: VariableValueType +} + +export interface FontVar { + type: 'Font', + default?: VariableValueType +} + +export interface ImageVar { + type: 'Image' + default?: VariableValueType +} + +export type Variable = NumberVar + | StringVar + | BooleanVar + | ColorVar + | FontVar + | ImageVar + +export interface Variables { + [key: string]: Variable +} + +export type VariablesType = { + [K in keyof V]: V[K] extends Variable ? V[K] : never +}; + +export type VariableValueType = T extends NumberVar ? number : + T extends BooleanVar ? boolean : + T extends ImageVar ? ImageType : + string + +export type IOProperties = { + [K in keyof V]: AbstractIO +} + +export type InputProperties = { + [K in keyof V]: Input +} + +export type OutputProperties = { + [K in keyof V]: Output +} + +export default class AbstractIOSet { + + __values: any + __owner: Node + variables: V + + constructor(variables: V, owner: Node) { + this.__values = {}; + this.__owner = owner; + this.variables = variables; + + for (let name of Object.keys(this.variables)) { + if (variables[name] === null) { + delete this.variables[name]; + continue; + } + this.__values[name] = this.__createProperty(name, variables[name]); + Object.defineProperty(this, name, { + get() { + return this.__values[name]; + }, + }) + } + } + + get id() { + return `${this.__owner.id}-set`; + } + + *[Symbol.iterator]() { + for (let name of Object.keys(this.variables)) { + yield this.__values[name]; + } + } + + __createProperty(name, definition) { + + } + + + update(changed) { + + } +} diff --git a/src/nodes/io/Input.js b/src/nodes/io/Input.ts similarity index 78% rename from src/nodes/io/Input.js rename to src/nodes/io/Input.ts index f230f07..881643c 100644 --- a/src/nodes/io/Input.js +++ b/src/nodes/io/Input.ts @@ -1,7 +1,15 @@ import AbstractIO from './AbstractIO'; +import Output from './Output'; +import { + Variable, + VariableValueType, +} from './AbstractIOSet'; import {serialize, deserialize} from './serializer'; -export default class Input extends AbstractIO { +export default class Input extends AbstractIO { + + __output: Output + __onchangelistener: Function constructor(name, definition, owner) { super(name, definition, owner); @@ -13,11 +21,11 @@ export default class Input extends AbstractIO { } } - get value() { + get value(): VariableValueType { return this.__getValue(); } - set value(value) { + set value(value: VariableValueType) { this.__setValue(value); this.__owner.update(this.name); this.__notifyListeners(); @@ -27,7 +35,7 @@ export default class Input extends AbstractIO { return this.__output; } - connect(output) { + connect(output: Output) { if (output && output.type === this.type) { this.disconnect(); this.__output = output @@ -36,7 +44,7 @@ export default class Input extends AbstractIO { } } - disconnect(output) { + disconnect(output?) { if (this.__output) { this.__output.offchange(this.__onchangelistener); this.__output = null; diff --git a/src/nodes/io/Inputs.js b/src/nodes/io/Inputs.ts similarity index 64% rename from src/nodes/io/Inputs.js rename to src/nodes/io/Inputs.ts index 8e374fb..608954f 100644 --- a/src/nodes/io/Inputs.js +++ b/src/nodes/io/Inputs.ts @@ -1,9 +1,10 @@ import Input from './Input'; -import AbstractIOSet from './AbstractIOSet'; +import Node from '../Node'; +import {default as AbstractIOSet, Variables} from './AbstractIOSet'; -export default class Inputs extends AbstractIOSet { +export default class Inputs extends AbstractIOSet { - constructor(variables, owner) { + constructor(variables: V, owner: Node) { super(variables, owner); } diff --git a/src/nodes/io/Output.js b/src/nodes/io/Output.ts similarity index 88% rename from src/nodes/io/Output.js rename to src/nodes/io/Output.ts index 915ef3c..7cab71b 100644 --- a/src/nodes/io/Output.js +++ b/src/nodes/io/Output.ts @@ -1,6 +1,9 @@ import AbstractIO from './AbstractIO'; +import { + Variable, +} from './AbstractIOSet'; -export default class Output extends AbstractIO { +export default class Output extends AbstractIO { constructor(name, definition, owner) { super(name, definition, owner); diff --git a/src/nodes/io/Outputs.js b/src/nodes/io/Outputs.ts similarity index 65% rename from src/nodes/io/Outputs.js rename to src/nodes/io/Outputs.ts index 6c4ca94..531dd83 100644 --- a/src/nodes/io/Outputs.js +++ b/src/nodes/io/Outputs.ts @@ -1,8 +1,9 @@ +import Node from '../Node'; import Output from './Output'; -import AbstractIOSet from './AbstractIOSet'; +import {default as AbstractIOSet, Variables} from './AbstractIOSet'; -export default class Outputs extends AbstractIOSet { - constructor(variables, owner) { +export default class Outputs extends AbstractIOSet { + constructor(variables: V, owner: Node) { super(variables, owner); } diff --git a/src/nodes/io/index.js b/src/nodes/io/index.ts similarity index 100% rename from src/nodes/io/index.js rename to src/nodes/io/index.ts diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index c028f65..0000000 --- a/src/utils.js +++ /dev/null @@ -1,13 +0,0 @@ -export const isNil = (val) => val === null || val === undefined; - -const waitForImage = (img) => new Promise((resolve) => img.addEventListener('load', resolve, {once: true})); - -export const waitForMedia = async (media) => { - if (media instanceof Image && !media.complete) { - await waitForImage(media); - } - return media; -} - -export const setImmediate = (fn) => Promise.resolve().then(fn); - diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..b79379c --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,16 @@ +export const isNil = (val) => val === null || val === undefined; + +const waitForImage = (img:HTMLImageElement): Promise => new Promise((resolve) => img.addEventListener('load', () => resolve(img), {once: true})); + +export const waitForMedia = async (media:T):Promise => { + if (media instanceof Image && !media.complete) { + await waitForImage(media); + } + return media; +} + +export const setImmediate = any>(fn:T):Promise> => Promise.resolve().then(fn); + +export const merge = (...objects) => { + return objects.reduce((acc, nxt) => Object.assign(acc, nxt), {}); +} \ No newline at end of file