Initial commit

Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com>
Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com>
Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com>
Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com>
Co-Authored-By: Johan  <4934465+joolsus@users.noreply.github.com>
Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com>
Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
Michael Cartner
2024-01-26 11:52:55 +01:00
commit b9c60b07dc
2789 changed files with 868795 additions and 0 deletions

View File

@@ -0,0 +1,296 @@
'use strict';
const { Node } = require('@noodl/runtime');
const Model = require('@noodl/runtime/src/model');
const EventEmitter = require('events').EventEmitter;
const graphEventEmitter = new EventEmitter();
graphEventEmitter.setMaxListeners(1000000);
const ParentComponentObject = {
name: 'net.noodl.ParentComponentObject',
displayNodeName: 'Parent Component Object',
category: 'Component Utilities',
color: 'component',
docs: 'https://docs.noodl.net/nodes/component-utilities/parent-component-object',
initialize() {
this._internal.inputValues = {};
this._internal.onModelChangedCallback = (args) => {
if (this.isInputConnected('fetch') !== false) return;
if (this.hasOutput('value-' + args.name)) {
this.flagOutputDirty('value-' + args.name);
}
if (this.hasOutput('changed-' + args.name)) {
this.sendSignalOnOutput('changed-' + args.name);
}
this.sendSignalOnOutput('changed');
};
//TODO: don't listen for delta updates when running deployed
this.onComponentStateNodesChanged = () => {
const id = this.findParentComponentStateModelId();
if (this._internal.modelId !== id) {
this._internal.modelId = id;
if (this.isInputConnected('fetch') === false) {
this.setModelId(this._internal.modelId);
}
}
};
graphEventEmitter.on('componentStateNodesChanged', this.onComponentStateNodesChanged);
this.updateComponentState();
},
//to search up the tree the root nodes in this component must have been initialized
//we also need the connections to be setup so we can use isInputConnected
//nodeScopeDidInitialize takes care of that
nodeScopeDidInitialize() {
//FIXME: temporary hack. Our parent's node scope might not have finished created yet
//so just wait until after this update. It'll make the parent component state
//have a delay in propagating outputs which can cause subtle bugs.
//The fix is to call this code when the entire node tree has been created,
//before running updating the next update.
if (!this._internal.modelId) {
this.context.scheduleAfterUpdate(() => {
this.updateComponentState();
});
}
},
getInspectInfo() {
const model = this._internal.model;
if (!model) return 'No parent component state found';
const modelInfo = [{ type: 'text', value: this._internal.parentComponentName }];
const data = this._internal.model.data;
return modelInfo.concat(
Object.keys(data).map((key) => {
return { type: 'text', value: key + ': ' + data[key] };
})
);
},
inputs: {
properties: {
type: {
name: 'stringlist',
allowEditOnly: true
},
displayName: 'Properties',
group: 'Properties',
set(value) {}
},
fetch: {
displayName: 'Fetch',
group: 'Actions',
valueChangedToTrue: function () {
this.setModelId(this._internal.modelId);
}
}
},
outputs: {
changed: {
type: 'signal',
displayName: 'Changed',
group: 'Events'
},
fetched: {
type: 'signal',
displayName: 'Fetched',
group: 'Events'
}
},
methods: {
updateComponentState() {
this._internal.modelId = this.findParentComponentStateModelId();
if (this.isInputConnected('fetch') === false) {
this.setModelId(this._internal.modelId);
}
},
findParentComponentStateModelId() {
function getParentComponent(component) {
let parent;
if (component.getRoots().length > 0) {
//visual
const root = component.getRoots()[0];
if (root.getVisualParentNode) {
//regular visual node
if (root.getVisualParentNode()) {
parent = root.getVisualParentNode().nodeScope.componentOwner;
}
} else if (root.parentNodeScope) {
//component instance node
parent = component.parentNodeScope.componentOwner;
}
} else if (component.parentNodeScope) {
parent = component.parentNodeScope.componentOwner;
}
//check that a parent exists and that the component is different
if (parent && parent.nodeScope && parent.nodeScope.componentOwner !== component) {
//check if parent has a Component State node
if (parent.nodeScope.getNodesWithType('net.noodl.ComponentObject').length > 0) {
return parent;
}
//if not, continue searching up the tree
return getParentComponent(parent);
}
}
const parent = getParentComponent(this.nodeScope.componentOwner);
if (!parent) return;
this._internal.parentComponentName = parent.name;
return 'componentState' + parent.getInstanceId();
},
setModelId(id) {
this._internal.model && this._internal.model.off('change', this._internal.onModelChangedCallback);
this._internal.model = undefined;
if (!id) return;
const model = Model.get(id);
this._internal.model = model;
model.on('change', this._internal.onModelChangedCallback);
for (var key in model.data) {
if (this.hasOutput('value-' + key)) {
this.flagOutputDirty('value-' + key);
}
if (this.hasOutput('changed-' + key)) {
this.sendSignalOnOutput('changed-' + key);
}
}
this.sendSignalOnOutput('changed');
this.sendSignalOnOutput('fetched');
},
scheduleStore() {
if (this.hasScheduledStore) return;
this.hasScheduledStore = true;
var internal = this._internal;
this.scheduleAfterInputsHaveUpdated(() => {
this.hasScheduledStore = false;
if (!internal.model) return;
for (var i in internal.inputValues) {
internal.model.set(i, internal.inputValues[i], { resolve: true });
}
});
},
_onNodeDeleted() {
Node.prototype._onNodeDeleted.call(this);
graphEventEmitter.off('componentStateNodesChanged', this.onComponentStateNodesChanged);
this._internal.model && this._internal.model.off('change', this._internal.onModelChangedCallback);
},
registerOutputIfNeeded(name) {
if (this.hasOutput(name)) {
return;
}
const propertyName = name.substring('value-'.length);
this.registerOutput(name, {
get() {
if (!this._internal.model) return undefined;
return this._internal.model.get(propertyName, { resolve: true });
}
});
},
registerInputIfNeeded: function (name) {
if (this.hasInput(name)) {
return;
}
if (name.startsWith('value-')) {
const propertyName = name.substring('value-'.length);
this.registerInput(name, {
set(value) {
this._internal.inputValues[propertyName] = value;
this.scheduleStore();
}
});
}
}
}
};
function updatePorts(nodeId, parameters, editorConnection) {
const ports = [];
// Add value outputs
var properties = parameters.properties && parameters.properties.split(',');
for (var i in properties) {
var p = properties[i];
ports.push({
type: {
name: '*', //parameters['type-' + p] || 'string',
allowConnectionsOnly: true
},
plug: 'input/output',
group: 'Properties',
name: 'value-' + p,
displayName: p
});
ports.push({
type: 'signal',
plug: 'output',
group: 'Changed Events',
displayName: p + ' Changed',
name: 'changed-' + p
});
}
editorConnection.sendDynamicPorts(nodeId, ports, {
detectRenamed: {
plug: 'input/output'
}
});
}
module.exports = {
node: ParentComponentObject,
setup: function (context, graphModel) {
if (!context.editorConnection || !context.editorConnection.isRunningLocally()) {
return;
}
graphModel.on('nodeAdded.net.noodl.ParentComponentObject', (node) => {
updatePorts(node.id, node.parameters, context.editorConnection);
node.on('parameterUpdated', (event) => {
updatePorts(node.id, node.parameters, context.editorConnection);
});
});
//TODO: handle additional delta update event:
// - visual parent changed
//this are the same events that'll create and delete the Comopent State instance node
//it might not have had a chance to run yet if we're first in the event list, so
//use a setTimeout
graphModel.on('nodeAdded.net.noodl.ComponentObject', (node) => {
setTimeout(() => {
graphEventEmitter.emit('componentStateNodesChanged');
}, 0);
});
graphModel.on('nodeRemoved.net.noodl.ComponentObject', (node) => {
setTimeout(() => {
graphEventEmitter.emit('componentStateNodesChanged');
});
});
}
};