@selenite/graph-editor
Version:
A graph editor for visual programming, based on rete and svelte.
189 lines (188 loc) • 5.38 kB
JavaScript
import { cloneDeep } from 'lodash-es';
import { ClassicPreset, getUID } from 'rete';
import { valueConverters } from '../common';
import { untrack } from 'svelte';
export function applyParams(target, constructor, params) {
if (Object.keys(params).length === 0)
return;
const ref = new constructor();
Object.assign(target, Object.fromEntries(Object.entries(params).filter(([k]) => k in ref)));
}
/**
* A control represents widgets the user can interact with.
*/
export class Control extends ClassicPreset.Control {
placeInHeader = $state(false);
constructor(params = {}) {
super();
applyParams(this, Control, params);
}
}
/**
* Supported types of input control.
*/
export const inputControlTypes = [
'text',
'number',
'checkbox',
'textarea',
'integer',
'vector',
'remote-file',
'select',
'group-name-ref'
];
export function isInputControlType(type) {
return inputControlTypes.includes(type);
}
/**
* Default values for each type of input control.
*/
export const defaultInputControlValues = {
text: '',
number: 0,
checkbox: true,
textarea: '',
integer: 0,
vector: { x: 0, y: 0, z: 0 },
'remote-file': '',
select: '',
'group-name-ref': ''
};
export const inputControlSocketType = {
'group-name-ref': 'groupNameRef',
'remote-file': 'path',
checkbox: 'boolean',
integer: 'integer',
number: 'number',
select: 'options',
text: 'string',
textarea: 'string',
vector: 'vector'
};
export const socketToControl = {
path: 'remote-file',
string: 'text',
integer: 'integer',
number: 'number',
boolean: 'checkbox',
vector: 'vector',
any: 'text',
groupNameRef: 'group-name-ref'
};
export const socketTypesWithControl = Object.keys(socketToControl);
let testa;
export function assignControl(socketType, default_) {
return socketType in socketToControl ? socketToControl[socketType] : default_;
}
export function getDatastructure({ datastructure, type, values }) {
const defaultValue = defaultInputControlValues[type];
if (!values || values.length === 0)
values = [defaultValue];
switch (datastructure) {
case 'scalar':
return values[0];
case 'array':
return [...values];
default:
throw new Error('Invalid datastructure');
}
}
export function getDatastructureValues({ datastructure, data }) {
switch (datastructure) {
case 'scalar':
return [data];
case 'array':
return [...data];
default:
throw new Error('Invalid datastructure');
}
}
export class InputControl extends Control {
#value = $state();
readonly = $state(false);
onChange;
label = $state('');
type = $state('text');
#datastructure = $state('scalar');
#socketType = $state('any');
changeType = $state();
canChangeType = $state(false);
props = $state({});
options = $state();
autoType = false;
constructor(params) {
super();
this.props = params.props ?? {};
this.id = getUID();
this.#datastructure = params.datastructure;
this.readonly = params.readonly ?? false;
this.label = params.label ?? '';
this.onChange = params.onChange;
this.#value = params.initial ?? getDatastructure(params);
// untrack(() => {
// if (params.onChange) {
// params.onChange(this.#value);
// }
// });
this.type = params.type;
this.changeType = params.changeType;
this.canChangeType = params.canChangeType ?? false;
this.#socketType = params.socketType;
this.options = params.options;
}
get value() {
return this.#value;
}
set value(v) {
this.#value = v;
if (this.onChange && !this.readonly)
this.onChange(v);
}
get socketType() {
return this.#socketType;
}
set socketType(t) {
console.debug(`Converting values from ${this.#socketType} to ${t}`);
const converter = valueConverters[t];
let values;
if (!converter) {
console.warn('No converter for', t);
}
else {
values = getDatastructureValues({
datastructure: this.datastructure,
data: this.value
}).map(converter);
}
this.value = getDatastructure({
datastructure: this.datastructure,
values,
type: socketToControl[t]
});
this.type = assignControl(t, this.type);
this.#socketType = t;
}
get datastructure() {
return this.#datastructure;
}
set datastructure(d) {
if (d === this.#datastructure)
return;
if (d === 'scalar') {
if (this.#value?.length > 0)
this.#value = this.#value[0];
else
this.#value = defaultInputControlValues[this.type];
}
else {
if (this.value === undefined)
this.#value = [];
else
this.#value = [this.#value];
}
this.#datastructure = d;
this.onChange?.($state.snapshot(this.#value));
}
}
// const test = new InputControl({ type: 'checkbox', initial: true });