flow icon indicating copy to clipboard operation
flow copied to clipboard

Code Multiplexer Component

Open ColtonMcInroy opened this issue 3 years ago • 4 comments

One thing I ran into was wanting to be able to use a single code component to handle multiple inputs and outputs. This would allow for a smaller footprint when designing flows and prevent duplication of code components.

I do not have the component fully fleshed out yet, missing the readme, and because I built this before the fix for #82 still suffers the initial issue. I think this would be a great component to add, or possibly even replace current code component.

<script total>

	exports.name = 'Multiplexer';
	exports.icon = 'fa fa-exchange-alt';
	exports.author = 'Colton McInroy';
	exports.version = '1';
	exports.group = 'Common';
	exports.config = { name: 'Label', inputs: [{ id: 'input', name: 'Input' }], outputs: [{ id: 'output', name: 'Output' }], code: '// instance {FlowStreamInstance};\n// $ {FlowStreamMessage};\n// vars {Object};\n// repo {Object};\n// data {String/Number/Boolean/Date/Buffer/Object};\n// $.send(\'output\', data); // or simply send(data); which uses the first output\n// $.destroy();\n// $.throw(err);\n\n// IMPORTANT: If you do not perform re-send, you need to destroy this message via $.destroy() method\n// IMPORTANT: methods $.send(), $.destroy() and $.throw() can be executed only once\n\n// $.send(\'output\', data);\n// $.destroy();\nif ($.input === \'input\') {\n	$.send(\'output\', data);\n	return;\n}' };
	exports.inputs = [{ id: 'input', name: 'Input' }];
	exports.outputs = [{ id: 'output', name: 'Output' }];

	// exports.npm = ['npm_module_1', 'npm_module_2@version'];
	// exports.meta = { readonly: false, singleton: false, hidden: false };

	exports.make = function(instance, config) {
		let fn;
		// instance.main.variables {Object}
		// instance.main.variables2 {Object}
		// instance.save();
		// instance.replace(str); // replaces {variable_name} for values from "variables" and "variables2"
		// instance.status(obj, [refresh_delay_in_ms]);

		instance.message = function($) {
			// var data = $.data;
			// $.send('output', data);
			// or $.destroy();

			if (fn) {
				try {
					// var send = data => $.send('output', data);
					fn($.data, instance, $, $, F.require, $.send, $.repo, $.vars, $.data);
				} catch (e) {
					$.throw(e);
					$.destroy();
				}
			}
		};

		instance.configure = function() {
			if (!config.outputs.length || !config.inputs.length)
				return;

			// "config" is changed
			instance.inputs = [];
			instance.outputs = [];

			config.inputs.forEach((input, i) => {
				instance.inputs.push({ id: input.id || 'input' + (i + 1), name: input.name || 'Input #' + (i + 1) });
			});
			config.outputs.forEach((output, i) => {
				instance.outputs.push({ id: output.id || 'output' + (i + 1), name: output.name || 'Output #' + (i + 1) });
			});
			instance.save();

			try {
				if (config.code) {
					instance.status(1);
					fn = new Function('value', 'instance', '$', 'message', 'require', 'send', 'repo', 'vars', 'data', config.code);
				} else {
					instance.status(0);
					fn = null;
				}
			} catch (e) {
				fn = null;
				instance.throw('Code: ' + e.message);
			}
		};

		instance.close = function() {
			// this instance is closed
			fn = null;
		};

		instance.variables = function(variables) {
			// FlowStream variables are changed
		};

		instance.variables2 = function(variables) {
			// Global variables are changed
		};

		instance.configure();

	};

</script>

<readme>
Markdown readme

</readme>

<settings>
	<div class="padding">
		<div data---="input__?.name" class="m">Name</div>
		<section class="switch-inputs m">
			<label class="ui-input-label">Inputs</label>
			<div class="switch-thead">
				<div class="row">
					<div class="col-md-1">#</div>
					<div class="col-md-5">Input id</div>
					<div class="col-md-5">Input name</div>
					<div class="col-md-1">Action</div>
				</div>
			</div>
			<ui-bind path="?.inputs" config="template:.switch-input -> data-index" clas="block">
				<ui-component name="movable" path="?.inputs" config="selector:.dragme;exec:FUNC.switch_input_dragged">
					<script type="text/html">
						{{ foreach con in value }}
						<div class="switch-input dragme" data-index="{{ $index }}" draggable="true">
							<div class="row">
								<div class="col-md-1">
									{{ ($index + 1) }}.
								</div>
								<div class="col-md-5">
									<ui-component name="input" path="?.inputs[{{ $index }}].id"></ui-component>
								</div>
								<div class="col-md-5">
									<ui-component name="input" path="?.inputs[{{ $index }}].name"></ui-component>
								</div>
								<div class="col-md-1">
									<i class="ti ti-trash red exec" data-exec="FUNC.switch_remove_input"></i>
								</div>
							</div>
						</div>
						{{ end }}
					</script>
				</ui-component>
			</ui-bind>
			<div class="help m">Each input corresponds to an output index. First input --> First output, etc.</div>
			<button class="button-add exec" data-exec="FUNC.switch_add_input">ADD</button>
		</section>
		<section class="switch-outputs m">
			<label class="ui-input-label">Outputs</label>
			<div class="switch-thead">
				<div class="row">
					<div class="col-md-1">#</div>
					<div class="col-md-5">Output id</div>
					<div class="col-md-5">Output name</div>
					<div class="col-md-1">Action</div>
				</div>
			</div>
			<ui-bind path="?.outputs" config="template:.switch-output -> data-index" clas="block">
				<ui-component name="movable" path="?.outputs" config="selector:.dragme;exec:FUNC.switch_output_dragged">
					<script type="text/html">
						{{ foreach con in value }}
						<div class="switch-output dragme" data-index="{{ $index }}" draggable="true">
							<div class="row">
								<div class="col-md-1">
									{{ ($index + 1) }}.
								</div>
								<div class="col-md-5">
									<ui-component name="input" path="?.outputs[{{ $index }}].id"></ui-component>
								</div>
								<div class="col-md-5">
									<ui-component name="input" path="?.outputs[{{ $index }}].name"></ui-component>
								</div>
								<div class="col-md-1">
									<i class="ti ti-trash red exec" data-exec="FUNC.switch_remove_output"></i>
								</div>
							</div>
						</div>
						{{ end }}
					</script>
				</ui-component>
			</ui-bind>
			<div class="help m">Each output corresponds to an output index. First output --> First output, etc.</div>
			<button class="button-add exec" data-exec="FUNC.switch_add_output">ADD</button>
		</section>
		<button class="button exec" style="width: 200px;" data-exec="FUNC.switch_readme"><i class="ti ti-info-circle blue"></i>Show configuration info</button>
		<div class="ui-input-label">Code:</div>
		<ui-component name="codemirror" path="?.code" config="type:javascript;minheight:200;parent:auto;margin:60;tabs:true;trim:true" class="m"></ui-component>
	</div>
</settings>

<script>

	FUNC.switch_readme = function() {
		EXEC('flow/readme', flow.info.selected.component);
	};

	FUNC.switch_add_input = function(el) {
		var scope = el.scope();
		PUSH(scope.path + '.inputs', { operator: '==', type: 'string', value: '' });
	};

	FUNC.switch_remove_input = function(el) {
		var path = el.scope().path;
		var config = GET(path);
		var index = el.closest('.switch-input').attrd('index');
		config.inputs.splice(index, 1);
		SET(path, config);
		console.log(config);
	};

	FUNC.switch_input_dragged = function(list, dragged, target) {
		dragged = $(dragged);
		var dragged_index = dragged.attrd('index');
		var target_index = $(target).attrd('index');
		var path = dragged.scope().path;
		var config = GET(path);
		var dragged_item = config.inputs.splice(dragged_index, 1)[0];
		config.inputs.splice(target_index, 0, dragged_item);
		SET(path, config);
	};

	FUNC.switch_add_output = function(el) {
		var scope = el.scope();
		PUSH(scope.path + '.outputs', { });
	};

	FUNC.switch_remove_output = function(el) {
		var path = el.scope().path;
		var config = GET(path);
		var index = el.closest('.switch-output').attrd('index');
		config.outputs.splice(index, 1);
		SET(path, config);
		console.log(config);
	};

	FUNC.switch_output_dragged = function(list, dragged, target) {
		dragged = $(dragged);
		var dragged_index = dragged.attrd('index');
		var target_index = $(target).attrd('index');
		var path = dragged.scope().path;
		var config = GET(path);
		var dragged_item = config.outputs.splice(dragged_index, 1)[0];
		config.outputs.splice(target_index, 0, dragged_item);
		SET(path, config);
	};

	FUNC.switch_tooltip = function(el) {
		var opt = {};
		opt.element = el;
		var id = el.attrd('id');
		opt.html = REPO.switch_tooltips[id];

		SETTER('tooltip', 'show', opt);
	};

	// Client-side script
	// Optional, you can remove it

	// A custom helper for the component instances
	// The method below captures each instance of this component
	TOUCH(function(exports, reinit) {

		var name = exports.name + ' --> ' + exports.id;

		console.log(name, 'initialized' + (reinit ? ' : UPDATE' : ''));

		exports.settings = function(meta) {
			// Triggered when the user opens settings
			console.log(name, 'settings', meta);
		};

		exports.configure = function(config, isinit) {
			// Triggered when the config is changed
			console.log(name, 'configure', config);
			var changes = exports.instance.changes;
			if (changes && changes.newbie) {

				const inputs = [];
				for (const input of exports.instance.config.inputs) {
					let i = inputs.length+1;
					inputs.push({ id: input.id || 'input' + i, name: input.name || 'Input #' + i });
				}
				exports.instance.inputs = inputs;

				const outputs = [];
				for (const output of exports.instance.config.outputs) {
					let i = outputs.length+1;
					outputs.push({ id: output.id || 'output' + i, name: output.name || 'Output #' + i });
				}
				exports.instance.outputs = outputs;

				UPD('flow.data');
			}
		};

		exports.status = function(status, isinit) {
			// Triggered when the status is changed
			console.log(name, 'status', status);
		};

		exports.note = function(note, isinit) {
			// Triggered when the note is changed
			console.log(name, 'note', note);
		};

		exports.variables = function(variables) {
			// Triggered when the variables are changed
			console.log(name, 'variables', variables);
		};

		exports.variables2 = function(variables) {
			// Triggered when the variables2 are changed
			console.log(name, 'variables2', variables);
		};

		exports.close = function() {
			// Triggered when the instance is closing due to some reasons
			console.log(name, 'close');
		};

	});
</script>

<style>
	.CLASS footer { padding: 10px; font-size: 12px; }

	.button-add { height: 24px; font-size: 12px; border: 1px solid #E0E0E0; border-radius: var(--radius); color: #000; background-color: #f0f0f0; margin: 0; padding: 2px 10px; }
	.button-add:hover { background-color: #F8F8F8; }
	.button-add:active { background-color: #E0E0E0; }

	.ui-dark .button-add { border-color: #404040; color: #FFF; background-color: #202020; }
	.ui-dark .button-add:hover { background-color: #303030; }
	.ui-dark .button-add:active { background-color: #404040; }

	.switch-input-group { clear: both; height: 36px; }
	.switch-input-group > ui-component:first-child .ui-input-control { border-right: none; border-bottom-right-radius: 0; border-top-right-radius: 0; width: 120px; float: left; background-color: #f0f0f0; }
	.switch-input-group > ui-component:last-child .ui-input-control { border-bottom-left-radius: 0; border-top-left-radius: 0; float: left; width: calc(100% - 120px); }
	.switch-input-group.wide > ui-component:first-child .ui-input-control { width: 200px; }
	.switch-input-group.wide > ui-component:last-child .ui-input-control { width: calc(100% - 200px); }
	.switch-inputs { border: 1px solid #e0e0e0; padding: 8px; border-radius: 3px; }
	.switch-input { border: 1px solid #e0e0e0; border-radius: 3px; padding: 8px; margin-bottom:4px; }
	.switch-input > .row > .col-md-1 { height: 36px; line-height: 36px; }

	.switch-output-group { clear: both; height: 36px; }
	.switch-output-group > ui-component:first-child .ui-output-control { border-right: none; border-bottom-right-radius: 0; border-top-right-radius: 0; width: 120px; float: left; background-color: #f0f0f0; }
	.switch-output-group > ui-component:last-child .ui-output-control { border-bottom-left-radius: 0; border-top-left-radius: 0; float: left; width: calc(100% - 120px); }
	.switch-output-group.wide > ui-component:first-child .ui-output-control { width: 200px; }
	.switch-output-group.wide > ui-component:last-child .ui-output-control { width: calc(100% - 200px); }
	.switch-outputs { border: 1px solid #e0e0e0; padding: 8px; border-radius: 3px; }
	.switch-output { border: 1px solid #e0e0e0; border-radius: 3px; padding: 8px; margin-bottom:4px; }
	.switch-output > .row > .col-md-1 { height: 36px; line-height: 36px; }

	.switch-help { background-color: #e7e7ff; border-radius: 3px; padding: 4px; }
	.switch-thead { padding: 8px; margin-bottom:4px; }
</style>

<body>
	<header>
		<i class="ICON"></i>NAME (<span data-bind="CONFIG.name__text"></span>)
	</header>
</body>

ColtonMcInroy avatar Feb 06 '23 20:02 ColtonMcInroy

@ColtonMcInroy Instead of using syntax highlighter here, please upload .html files only.

petersirka avatar Feb 08 '23 23:02 petersirka

Sorry about that, here you go... Does not allow .html so here is a .html.gz

Multiplexer.html.gz

ColtonMcInroy avatar Feb 09 '23 00:02 ColtonMcInroy

@ColtonMcInroy I can publish it to the main FlowStreamComponents repo if you agree, but please add readme information. Thank you!

petersirka avatar Aug 22 '23 10:08 petersirka

Here is an updated version of this component with readme information provided. Multiplexer.zip

One thing I should mention, currently I used the "Switch" component to figure out how to handle creating the inputs/outputs. The drag/drop for re-ordering functionality in both the Switch component and this multiplexer component do not appear to work. I have not had time to look into it yet.

ColtonMcInroy avatar Aug 25 '23 00:08 ColtonMcInroy