base64 support, ApiJson, Eval string inputs
This commit is contained in:
145
src/nodes/ApiJson.ts
Normal file
145
src/nodes/ApiJson.ts
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import {
|
||||||
|
NumberVar,
|
||||||
|
StringVar,
|
||||||
|
BooleanVar,
|
||||||
|
} from './io/AbstractIOSet';
|
||||||
|
import Node from './Node';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
|
propertyPath0: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
propertyPath1: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
propertyPath2: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
propertyPath3: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
propertyPath4: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
propertyPath5: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
propertyPath6: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
propertyPath7: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
url: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
payload: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
method: {
|
||||||
|
type: 'String',
|
||||||
|
default: 'GET',
|
||||||
|
enum: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||||
|
} as StringVar,
|
||||||
|
throttleMs: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 10000,
|
||||||
|
} as NumberVar,
|
||||||
|
compress: {
|
||||||
|
type: 'Boolean',
|
||||||
|
default: false,
|
||||||
|
} as BooleanVar,
|
||||||
|
quality: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 95,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
} as NumberVar,
|
||||||
|
};
|
||||||
|
|
||||||
|
const outputs = {
|
||||||
|
value0: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
value1: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
value2: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
value3: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
value4: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
value5: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
value6: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
value7: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ApiJson extends Node<typeof inputs, typeof outputs> {
|
||||||
|
private fetchDataThrottled;
|
||||||
|
private throttleWaitMs;
|
||||||
|
|
||||||
|
constructor(options = {}) {
|
||||||
|
super('ApiJson', inputs, outputs);
|
||||||
|
this.throttleWaitMs = this.in.throttleMs.value;
|
||||||
|
this.fetchDataThrottled = _.throttle(this.fetchData, this.throttleWaitMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _update() {
|
||||||
|
const url = this.in.url.value;
|
||||||
|
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.throttleWaitMs !== this.in.throttleMs.value) {
|
||||||
|
this.throttleWaitMs = this.in.throttleMs.value;
|
||||||
|
this.fetchDataThrottled = _.throttle(this.fetchData, this.throttleWaitMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: object;
|
||||||
|
try {
|
||||||
|
data = await this.fetchDataThrottled();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, this.throttleWaitMs));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.out.value0.value = _.get(data, this.in.propertyPath0.value);
|
||||||
|
this.out.value1.value = _.get(data, this.in.propertyPath1.value);
|
||||||
|
this.out.value2.value = _.get(data, this.in.propertyPath2.value);
|
||||||
|
this.out.value3.value = _.get(data, this.in.propertyPath3.value);
|
||||||
|
this.out.value4.value = _.get(data, this.in.propertyPath4.value);
|
||||||
|
this.out.value5.value = _.get(data, this.in.propertyPath5.value);
|
||||||
|
this.out.value6.value = _.get(data, this.in.propertyPath6.value);
|
||||||
|
this.out.value7.value = _.get(data, this.in.propertyPath7.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchData() {
|
||||||
|
const response = await fetch(this.in.url.value, {
|
||||||
|
method: this.in.method.value,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
...(this.in.payload.value ? {body: this.in.payload.value} : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status >= 400) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
}
|
||||||
84
src/nodes/Base64ToImg.ts
Normal file
84
src/nodes/Base64ToImg.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import {
|
||||||
|
ImageVar,
|
||||||
|
NumberVar,
|
||||||
|
StringVar,
|
||||||
|
} from './io/AbstractIOSet';
|
||||||
|
import Node from './Node';
|
||||||
|
import {canvasPool2D} from '../canvas/CanvasPool';
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
|
base64: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
};
|
||||||
|
|
||||||
|
const outputs = {
|
||||||
|
image: {
|
||||||
|
type: 'Image',
|
||||||
|
} as ImageVar,
|
||||||
|
width: {
|
||||||
|
type: 'Number',
|
||||||
|
} as NumberVar,
|
||||||
|
height: {
|
||||||
|
type: 'Number',
|
||||||
|
} as NumberVar,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: after successful tests move this to the library
|
||||||
|
*/
|
||||||
|
export default class Base64ToImg extends Node<typeof inputs, typeof outputs> {
|
||||||
|
constructor(options = {}) {
|
||||||
|
super('Base64ToImg', inputs, outputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _update() {
|
||||||
|
let base64String = this.in.base64.value;
|
||||||
|
|
||||||
|
if (!base64String) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!base64String.startsWith('data:image/')) {
|
||||||
|
base64String = `data:${this.estimateImageMimeType(base64String)};base64,${base64String}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = canvasPool2D.createCanvas();
|
||||||
|
canvas.acquire();
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
const canvasContext = canvas.getContext('2d');
|
||||||
|
canvasContext.drawImage(img, 0, 0);
|
||||||
|
resolve(img);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = (e) => {
|
||||||
|
reject(new Error(e as string));
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = base64String;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.out.image.value = canvas;
|
||||||
|
this.out.width.value = canvas.width;
|
||||||
|
this.out.height.value = canvas.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
private estimateImageMimeType(base64String: string): string {
|
||||||
|
// Attempt to estimate the MIME type based on base64 signature.
|
||||||
|
if (base64String.startsWith('iVBORw0KGgo=')) {
|
||||||
|
return 'image/png';
|
||||||
|
} else if (base64String.startsWith('/9j/')) {
|
||||||
|
return 'image/jpeg';
|
||||||
|
} else if (base64String.startsWith('UklGR')) {
|
||||||
|
return 'image/webp';
|
||||||
|
} else {
|
||||||
|
// Default or unknown type
|
||||||
|
return 'application/octet-stream';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,11 @@ import {
|
|||||||
StringVar,
|
StringVar,
|
||||||
BooleanVar,
|
BooleanVar,
|
||||||
} from './io/AbstractIOSet';
|
} from './io/AbstractIOSet';
|
||||||
import get from 'lodash/get';
|
import _ from 'lodash';
|
||||||
|
|
||||||
const inputs = {
|
const inputs = {
|
||||||
image: {
|
image: {
|
||||||
type: 'Image'
|
type: 'Image',
|
||||||
} as ImageVar,
|
} as ImageVar,
|
||||||
i0: {
|
i0: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
@@ -43,6 +43,38 @@ const inputs = {
|
|||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 0,
|
default: 0,
|
||||||
} as NumberVar,
|
} as NumberVar,
|
||||||
|
i8: {
|
||||||
|
type: 'String',
|
||||||
|
default: undefined,
|
||||||
|
} as StringVar,
|
||||||
|
i9: {
|
||||||
|
type: 'String',
|
||||||
|
default: undefined,
|
||||||
|
} as StringVar,
|
||||||
|
i10: {
|
||||||
|
type: 'String',
|
||||||
|
default: undefined,
|
||||||
|
} as StringVar,
|
||||||
|
i11: {
|
||||||
|
type: 'String',
|
||||||
|
default: undefined,
|
||||||
|
} as StringVar,
|
||||||
|
i12: {
|
||||||
|
type: 'String',
|
||||||
|
default: undefined,
|
||||||
|
} as StringVar,
|
||||||
|
i13: {
|
||||||
|
type: 'String',
|
||||||
|
default: undefined,
|
||||||
|
} as StringVar,
|
||||||
|
i14: {
|
||||||
|
type: 'String',
|
||||||
|
default: undefined,
|
||||||
|
} as StringVar,
|
||||||
|
i15: {
|
||||||
|
type: 'String',
|
||||||
|
default: undefined,
|
||||||
|
} as StringVar,
|
||||||
formula: {
|
formula: {
|
||||||
type: 'String',
|
type: 'String',
|
||||||
default: '',
|
default: '',
|
||||||
@@ -50,41 +82,52 @@ const inputs = {
|
|||||||
waitForInput: {
|
waitForInput: {
|
||||||
type: 'Boolean',
|
type: 'Boolean',
|
||||||
default: true,
|
default: true,
|
||||||
} as BooleanVar
|
} as BooleanVar,
|
||||||
};
|
};
|
||||||
|
|
||||||
const outputs = {
|
const outputs = {
|
||||||
image: {
|
image: {
|
||||||
type: 'Image'
|
type: 'Image',
|
||||||
} as ImageVar,
|
} as ImageVar,
|
||||||
result: {
|
result: {
|
||||||
type: 'Number'
|
type: 'Number',
|
||||||
} as NumberVar,
|
} as NumberVar,
|
||||||
}
|
resultString: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
};
|
||||||
|
|
||||||
export default class Eval extends Node<typeof inputs, typeof outputs> {
|
export default class Eval extends Node<typeof inputs, typeof outputs> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('Eval', inputs, outputs);
|
super('Eval', inputs, outputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolate() {
|
interpolate(defaultValue: any) {
|
||||||
return this.in.formula.value.replace(/\{(\w+)\}/g, (_, paramName) =>
|
return this.in.formula.value.replace(/\{((i\d)+)}/g, (originalString, paramName) => {
|
||||||
get(this.in, [paramName, 'value'], 0)
|
return _.get(this.in, [paramName, 'value'], defaultValue);
|
||||||
)
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAllConnectedInputsValue() {
|
hasAllConnectedInputsValue() {
|
||||||
if (this.in.waitForInput.value) {
|
if (!this.in.waitForInput.value) {
|
||||||
return super.hasAllConnectedInputsValue();
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const inputName of Object.keys(this.in.variables)) {
|
||||||
|
// allow empty string
|
||||||
|
if (this.in[inputName].output && !this.in[inputName].value && !_.isString(this.in[inputName].value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate() {
|
evaluate() {
|
||||||
try {
|
try {
|
||||||
this.out.result.value = eval(this.interpolate());
|
this.out.result.value = eval(this.interpolate(0));
|
||||||
|
this.out.resultString.value = eval(this.interpolate(''));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e, this.in);
|
||||||
}
|
}
|
||||||
this.out.image.value = this.in.image.value;
|
this.out.image.value = this.in.image.value;
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/nodes/ImgToBase64.ts
Normal file
62
src/nodes/ImgToBase64.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import {
|
||||||
|
ImageVar,
|
||||||
|
StringVar,
|
||||||
|
} from './io/AbstractIOSet';
|
||||||
|
import Node from './Node';
|
||||||
|
import {canvasPool2D} from '../canvas/CanvasPool';
|
||||||
|
import {waitForMedia} from '../utils';
|
||||||
|
import {mediaSize} from './canvas';
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
|
image: {
|
||||||
|
type: 'Image',
|
||||||
|
} as ImageVar,
|
||||||
|
type: {
|
||||||
|
type: 'String',
|
||||||
|
enum: ['image/png', 'image/jpeg', 'image/webp', 'image/gif'],
|
||||||
|
default: 'image/png',
|
||||||
|
} as StringVar,
|
||||||
|
};
|
||||||
|
|
||||||
|
const outputs = {
|
||||||
|
base64: {
|
||||||
|
type: 'String',
|
||||||
|
} as StringVar,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ImgToBase64 extends Node<typeof inputs, typeof outputs> {
|
||||||
|
constructor(options = {}) {
|
||||||
|
super('ImgToBase64', inputs, outputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _update() {
|
||||||
|
const media = this.in.image.value;
|
||||||
|
|
||||||
|
if (!media) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await waitForMedia(media);
|
||||||
|
const {width, height} = mediaSize(media);
|
||||||
|
|
||||||
|
const canvas = canvasPool2D.createCanvas();
|
||||||
|
canvas.acquire();
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
canvas.getContext('2d').drawImage(media, 0, 0, width, height);
|
||||||
|
|
||||||
|
const blob = await canvas.convertToBlob({type: this.in.type.value});
|
||||||
|
|
||||||
|
this.out.base64.value = await new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => {
|
||||||
|
resolve(reader.result as string);
|
||||||
|
};
|
||||||
|
reader.onerror = (e) => {
|
||||||
|
reject(e);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user