manu-sapiens's picture
copy of omnitool_latest - should be working
b39afbe
raw
history blame
10.3 kB
/**
* Copyright (c) 2023 MERCENARIES.AI PTE. LTD.
* All rights reserved.
*/
import Alpine from 'alpinejs';
import { getRenderTemplate } from './nodes/nodes.js';
import '../styles/rete.scss';
import { omnilog } from 'omni-shared';
window.Alpine = Alpine;
export function kebab(str) {
const replace = (s) => s.toLowerCase().replace(/ /g, '-');
return Array.isArray(str) ? str.map(replace) : replace(str);
}
export class AlpineRenderPlugin {
constructor(options = {}) {
this.name = 'alpine-render';
this.options = options;
this.editor = null;
}
setupAlpineNode(el, reteNode, bindSocket, bindControl) {
if (reteNode == null) {
throw new Error('Invalid Rete Node (null)');
}
reteNode._alpine = el;
const name = 'retenode' + reteNode.id;
el.setAttribute('x-data', name);
el.setAttribute('id', name);
// TODO: Is this possibly leaking when nodes are deleted?
new ResizeObserver(() => {
this.onNodeResize(reteNode);
}).observe(el);
reteNode.update = async (skipRedraw) => await this.update(reteNode, skipRedraw);
const reteEditor = this.editor;
Alpine.data(name, () => ({
node: null,
editor: null,
bindSocket: null,
bindControl: null,
showEditor: false,
classNameString: '',
//blockClassString: '',
tabButtonsClassString: '',
showHelp: true,
copyNotification: false,
getTitle(){
return this.node.title || this.node.name
},
selected(node) {
return this.editor?.selected.contains(node) ? 'selected' : '';
},
className(node) {
return kebab([
'rete-block', // Required for rete.scss to apply error and selected styles to blocks.
this.selected(node),
node.name,
node.errors.length ? 'error' : '',
node.active ? 'active' : ''
]);
},
async rename() {
let newName = prompt('New Name', this.node.title) || '';
if (newName === this.title) {
newName = ''; // Restore default name.
}
const prevName = this.node.data['x-omni-title'] || '';
this.node.data['x-omni-title'] = newName || undefined;
this.node.title = this.node.data['x-omni-title'] || this.title;
return prevName !== newName
},
async startEditComment(el) {
const commentEl = document.getElementById(`comment_${this.node.id}`);
commentEl.innerText = this.node.data['x-omni-comment'] || this.node.summary;
},
async getHelpText(node) {
return window.client.markdownEngine.render(this.node.data['x-omni-comment'] || this.node.summary);
},
async setComment(el) {
const commentEl = document.getElementById(`comment_${this.node.id}`);
const newText = commentEl.innerText.trim();
const prevText = this.node.data['x-omni-comment'] || '';
if (newText) {
this.node.data['x-omni-comment'] = newText;
} else {
delete this.node.data['x-omni-comment'];
}
commentEl.innerHTML = await window.client.markdownEngine.render(
this.node.data['x-omni-comment'] || this.node.summary
);
return prevText !== newText;
},
outputs(node) {
if (!node || !node.outputs) {
return [];
}
return Array.from(node?.outputs?.values?.() || []);
},
inputs(node) {
if (!node || !node.inputs) {
return [];
}
return Array.from(node?.inputs?.values?.() || []).filter((x) => x != null);
},
controls(node) {
return Array.from(node?.controls?.values?.() || []).filter((x) => {
if (x == null) {
console.warn('Null control for node - skipping render', node);
return false;
}
if (x.component == null) {
console.warn('Invalid component for control - skipping render', Alpine.raw(node), Alpine.raw(x));
return false;
}
return true;
});
},
onClickClose(node) {
// Delete the node and the component (!)
this.editor.removeNode(node);
},
toggleInfo(node) {
this.showHelp = !this.showHelp;
if (!this.showHelp) {
window.localStorage.setItem('omni/workbench/help_seen_' + node.name, 1);
} else {
window.localStorage.removeItem('omni/workbench/help_seen_' + node.name);
}
window.client.sendSystemMessage(
Object.assign({ name: node.name, title: node.title }, node.meta || {}, node.patch?.meta || {}),
'omni/component-meta',
{
commands: [
{
title: 'Add to Workbench',
id: 'add',
args: [node.name]
}
]
},
['no-picture']
);
},
onClickCopy(node) {
const copyNode = {
data: node.data,
name: node.name
};
const s = JSON.stringify(copyNode);
navigator.clipboard.writeText(s).then(
function () {
// console.log('Copying to clipboard was successful!');
},
function (err) {
console.error('Could not copy node: ', err);
}
);
this.copyNotification = true;
const that = this;
setTimeout(function () {
that.copyNotification = false;
}, 3000);
},
getContentHeight() {},
init() {
if (!reteNode) {
throw new Error('reteNode is not defined');
}
this.node = reteNode;
this.node.namespace ??= '';
this.node.category ??= '';
this.node.title ??= '';
this.node.summary ??= '';
this.node.meta ??= {};
this.node.data.xOmniEnabled ??= true;
this.showHelp = window.localStorage.getItem('omni/workbench/help_seen_' + this.node.name) == null;
this.editor = reteEditor;
this.bindSocket = bindSocket;
this.bindControl = bindControl;
//this.classNameString = this.blockClassString = this.tabButtonsClassString = this.className(reteNode)
}
}));
}
// eslint-disable-next-line no-unused-vars
createControl(el, control) {
const data = Alpine.$data(el);
return data;
}
setInnerHTML(node) {
//node._alpine.setAttribute('class', 'block')
//node._alpine.setAttribute(':class', 'blockClassString')
// POC: This will bec
if (!node) {
throw new Error('Invalid node passed into setInnerHtml');
}
node._alpine.innerHTML = node.renderTemplate
? getRenderTemplate(node.renderTemplate)
: getRenderTemplate('default');
}
updateConnections(reteNode) {
reteNode.outputs.forEach((x) => {
x.connections.forEach((connection) => {
this.editor.view.connections.get(connection)?.update();
});
});
reteNode.inputs.forEach((x) => {
x.connections.forEach((connection) => {
this.editor.view.connections.get(connection)?.update();
});
});
}
onNodeResize(reteNode) {
if (!reteNode) {
throw new Error('Invalid reteNode passed to onNodeResize');
}
this.updateConnections(reteNode);
}
async update(reteNode, skipRedraw = false) {
if (!reteNode) {
throw new Error('Invalid reteNode passed to update');
}
const ref = Alpine.$data(reteNode._alpine);
if (ref?.className) {
ref.classNameString = ref.className(reteNode);
//ref.blockClassString = ref.classNameString
ref.tabButtonsClassString = ref.classNameString;
}
if (!skipRedraw) {
this.setInnerHTML(reteNode);
}
return await new Promise((res) => {
// update() is often called with .reduce(...)
// This Promise attempts to minimize excessive updates by only updating one node per frame.
// TODO: Measure difference in battery usage.
if (!reteNode._alpine) {
res();
return;
}
Alpine.effect(() => {
requestAnimationFrame(res);
});
});
}
install(editor) {
this.editor = editor;
this.editor.on('rendernode', ({ el, node, component, bindSocket, bindControl }) => {
if (!component.render || component.render === 'alpine') {
// Create Alpine component for the node and update it
this.setupAlpineNode(el, node, bindSocket, bindControl);
this.setInnerHTML(node);
node.update();
}
});
this.editor.on('rendercontrol', ({ el, control }) => {
if (control.render && control.render !== 'alpine') return;
// Create Alpine component for the control and update it
control._alpine = this.createControl(el, control);
control.update = () => control._alpine.update();
});
this.editor.on('connectioncreated connectionremoved', (connection) => {
connection.input.node.update();
connection.output.node.update();
});
this.editor.on('nodeselected', (node) => {
if (node._alpine !== editor.previousSelectedNode?._alpine) {
editor.previousSelectedNode?.update(true); // Redraw previously selected node
editor.previousSelectedNode = node;
node.update(true);
}
});
}
}
document.addEventListener('alpine:init', () => {
omnilog.status_start('Renderer Alpine Init');
Alpine.directive('socket', (el, { expression, value }, { evaluate, effect }) => {
const foundEl = el.closest('[x-data]');
const data = Alpine.$data(foundEl);
// Alpine directive x-socket:value=expression
const io = evaluate(expression);
const currentClass = el.getAttribute('class');
el.setAttribute('class', currentClass + ' ' + io.socket.name);
const prefix = value === 'output' ? 'o_' : 'i_';
foundEl.socketDict = {
...(foundEl.socketDict || {}),
[prefix + io.key]: el
};
effect(() => {
data.bindSocket(el, value, Alpine.raw(io));
});
});
Alpine.directive('control', (el, { expression, value }, { evaluate, effect }) => {
// Alpine directive x-control:value=expression
const foundEl = el.closest('[x-data]');
const data = Alpine.$data(foundEl);
const control = evaluate(expression);
effect(() => {
data.bindControl(el, Alpine.raw(control));
});
});
});