diff --git a/src/Bonsai.Scripting.Expressions.Design/ExpressionScriptEditor.cs b/src/Bonsai.Scripting.Expressions.Design/ExpressionScriptEditor.cs
index 7d151f3..a87a1d0 100644
--- a/src/Bonsai.Scripting.Expressions.Design/ExpressionScriptEditor.cs
+++ b/src/Bonsai.Scripting.Expressions.Design/ExpressionScriptEditor.cs
@@ -4,6 +4,7 @@
using System.Windows.Forms.Design;
using System.Windows.Forms;
using Bonsai.Design;
+using System.Reactive;
namespace Bonsai.Scripting.Expressions.Design
{
@@ -53,7 +54,7 @@ public override object EditValue(ITypeDescriptorContext context, IServiceProvide
var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (editorService != null)
{
- var itType = GetDataSource(context, provider)?.ObservableType;
+ var itType = context.Instance is ExpressionSource ? typeof(Unit) : GetDataSource(context, provider)?.ObservableType;
using var editorDialog = new ExpressionScriptEditorDialog(itType);
editorDialog.Script = (string)value;
return editorService.ShowDialog(editorDialog) == DialogResult.OK
diff --git a/src/Bonsai.Scripting.Expressions/ExpressionSource.cs b/src/Bonsai.Scripting.Expressions/ExpressionSource.cs
new file mode 100644
index 0000000..7d1a96c
--- /dev/null
+++ b/src/Bonsai.Scripting.Expressions/ExpressionSource.cs
@@ -0,0 +1,78 @@
+using Bonsai.Expressions;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq.Expressions;
+using System.Reactive;
+using System.Reactive.Linq;
+
+namespace Bonsai.Scripting.Expressions
+{
+ ///
+ /// Represents an operator that uses an expression script to generate an
+ /// observable sequence with a single element.
+ ///
+ [DefaultProperty(nameof(Expression))]
+ [WorkflowElementCategory(ElementCategory.Source)]
+ [TypeDescriptionProvider(typeof(ExpressionSourceTypeDescriptionProvider))]
+ [Description("An expression script used to generate a sequence with a single element.")]
+ public class ExpressionSource : ZeroArgumentExpressionBuilder, IScriptingElement
+ {
+ ///
+ /// Gets or sets the name of the expression source.
+ ///
+ [Externalizable(false)]
+ [Category(nameof(CategoryAttribute.Design))]
+ [Description("The name of the expression source.")]
+ public string Name { get; set; }
+
+ ///
+ /// Gets or sets a description for the expression source.
+ ///
+ [Externalizable(false)]
+ [Category(nameof(CategoryAttribute.Design))]
+ [Description("A description for the expression source.")]
+ [Editor(DesignTypes.MultilineStringEditor, DesignTypes.UITypeEditor)]
+ public string Description { get; set; }
+
+ ///
+ /// Gets or sets the expression that generates the singleton element.
+ ///
+ ///
+ /// The it parameter stores the singleton object
+ /// representing no data.
+ ///
+ [Editor("Bonsai.Scripting.Expressions.Design.ExpressionScriptEditor, Bonsai.Scripting.Expressions.Design", DesignTypes.UITypeEditor)]
+ [Description("The expression that generates the singleton element.")]
+ public string Expression { get; set; } = "it";
+
+ ///
+ public override Expression Build(IEnumerable arguments)
+ {
+ var config = ParsingConfigHelper.CreateParsingConfig(typeof(Unit));
+ var generator = DynamicExpressionHelper.ParseLambda(config, typeof(Unit), null, Expression);
+ return System.Linq.Expressions.Expression.Call(typeof(ExpressionSource), nameof(Process), new[] { generator.ReturnType }, generator);
+ }
+
+ static IObservable Process(Func generator)
+ {
+ return Observable.Return(Unit.Default).Select(generator);
+ }
+
+ class ExpressionSourceTypeDescriptionProvider : TypeDescriptionProvider
+ {
+ static readonly TypeDescriptionProvider parentProvider = TypeDescriptor.GetProvider(typeof(ExpressionSource));
+
+ public ExpressionSourceTypeDescriptionProvider()
+ : base(parentProvider)
+ {
+ }
+
+ public override ICustomTypeDescriptor GetExtendedTypeDescriptor(object instance)
+ {
+ return new ScriptingElementTypeDescriptor(instance,
+ "An expression script used to generate a sequence with a single element.");
+ }
+ }
+ }
+}