In this chapter we're going to continue working on the editor component by introducing the node selector which will give us an ability to add all types of nodes to the canvas. We're also going to add our first trigger node called manual trigger which will allow users to manually start executing a workflow and we're going to add a very simple execution node called http request which will be able to execute well HTTP requests. We are of course going to create the actual node selector component and more importantly we're going to establish the editor state and once we have that editor state we're going to enable editor save functionality. Let's get started by adding two new nodes to our project. So inside of Prisma schema, inside of node type, where last time we just added initial, let's go ahead and let's extend it by adding a type, manual trigger.
And then let's also add HTTP request. So now instead of just having one node type we have three node types. Let's go ahead and let's do npx prisma migrate dev and we can just call it more nodes or new nodes. So here we have the prompt for a name, new nodes, and now we are ready to create the node selector component. I recommend restarting your Next.js server while you do this.
Every time you update your Prisma schema, do restart your server. It's definitely a good thing to do. Otherwise you might get some weird errors which you think maybe you will think you did something incorrectly with when that's not the case it's just that the cache is weird or something. Let's go ahead inside of components and let's create a new file called node selector.tsx. Let's mark it as use client.
Let's go ahead and let's import. We don't really have this package but let's go ahead and finish the import and then we're going to add it So we are going to import createid from this package which allows us to create cuids. Why cuids? Well because we use cuids everywhere And this is by far the most popular package to do that regardless if the name is not exactly It doesn't instill confidence, right but it is by far the most used Package for this so let the npm install my apologies it is cud2 so make sure to install cud2 and make sure to add cud2 here there we go then let's import use react flow from xyflow react let's go ahead and prepare from Lucid React let's import a few of these globe icon mouse pointer icon and we can remove webhook icon for now let's import use callback from react Let's import toast from sonar and now let's import everything we need from components UI sheet which is something that we've installed when we started this project. This is kind of like a sidebar but not really like a drawer.
Let's go ahead and let's import node type from Prisma and let's import separator from dot slash UI separator or you can do the components UI whatever you prefer. Let's go ahead and export type node type option give it a type of node type which we import it above, a label of string, description of a string, icon which is a React component type, and each can have an optional class name prop, or it can just be a normal string so we are going to switch between components and images depending on what we need. Now let's go ahead and let's define a constant trigger nodes let's go ahead and give it a type of node type option and it's an array of those and let's go ahead and add a type node type dot manual trigger label will be trigger manually. And the description can be, well, whatever you want. For example, I'm going to use, runs the flow on clicking a button.
Good for getting started quickly. Icon mouse pointer icon. Great. So that's all we need for the trigger nodes. Of course we are going to add more later.
Now let's go ahead and do const execution nodes. Node type option like this. The only reason we are separating these arrays is so that you can visually separate trigger nodes from execution nodes to not confuse people. Let's go ahead and give this a type of node type HTTP request label HTTP request. Description can be makes an HTTP request, a simple one, and an icon of globe icon.
You should already have it imported. Great! So now we have execution nodes and we have the trigger nodes. Now let's create the interface for the node selector component. So node selector props accepts open on open change and the children.
Then let's export function. Node selector. Node selector props. Let's go ahead and let's get open on open change children. And then let's go ahead and let's go let's see what should we do first I think it's time to actually build this so we can see what we are doing rather than just doing business logic in advance So render sheets and pass open and pass on open change to be on open change.
Then sheet trigger here is very simply going to be whatever we've wrapped the children around. So this can be anything and make sure to use as child for that specific reason. You're going to see what this will allow us to do in a second. Let's just render some sheet content here. Give it a side of right class name full with on small devices, maximum width of MD, overflow, Y auto.
Give it a header, Give it a title, what triggers this workflow. Then let's go ahead and give this a description. A trigger is a step that starts your workflow. Then outside of sheet header open a div trigger nodes dot map node type icon is going to be node type dot icon and return a div like this Go ahead and give it a key of node type, dot type. Let's go ahead and full width, justify, start, height auto, py5, px4, rounded none, cursor pointer, border L2, border transparent, hover, border L primary.
On click for now just leave it empty like this. Inside of here let's go ahead and add a class name. FlexItemsCenter Gap 6, full width, overflow, hidden. And now let's finally render the icon. So if typeof icon is just a string, it means it's an image href.
So let's use a normal image element here. Source is going to be the icon. Alt will be node type dot label. Class name is going to be size 5 object contain rounded small. Otherwise, we are going to render it as an icon as a node with a class name size 5 so I'm going to stop here so we can actually see what this even was for example let's go inside of our add node button.
This is the last component I believe we have added in the editor. So you can find it in the editor here where we render React flow, we practiced adding like a random thing in the upper right corner. So inside of source features, editor components, add node button, we have it right here. So what we could do here is wrap it inside of node selector like this. And this will allow the button to become the trigger to open that drawer.
Let's go ahead and pass in open on open change. And let's very simply introduce a state here like this. Selector open set selector open from use state react pass in selector open and pass in set selector open in the second prop. Now that we have that let's go ahead and try it out. So I'm going to refresh this and I will attempt to just you know click on the plus button here And there we go.
What triggers this workflow? A trigger is a step that starts your workflow. There we go. You can see how that looks. When I click on it nothing really happens.
So now let's keep that open. You can see it becomes full screen on mobile. And Let's continue working inside of the node selector here. So after we render the icon, let's open a new div. Let's open a span inside, node type dot label.
Now you can see we have a label here. Let's give the span a class name font medium text small. Let's give the parent div a class name flex flex column items start text left. Let's open a new span, node type, description. Give it a class name of text extra small and text muted foreground.
There we go. Now we can see how this looks with all of the details added. Perfect. And now what we can do is we can copy this entire thing. So yeah, let's copy the entire div like this.
Copy it and paste it. And then you're just going to change this from trigger nodes to execution nodes like this. And between them, you can add a separator like this. If you want to, you can also, you know, kind of like repeat the sheet header maybe you can do that instead of like separator I don't know let's see maybe what executes this workflow I don't know I think separator is clear enough and or maybe you can just change this depending on what type of nodes you have on your editor. You will be able to actually do that.
You will be able to read the state of the editor so you can maybe see if you only have this default plus node perhaps show a message what triggers this workflow and only show triggers and then once you start adding things also show both triggers and executions however you want I think this is okay now let's also make this button do that as well so the way we can do that is by going inside of our initial node component inside of source components initial node and just as we've wrapped our placeholder node within workflow node let's now do it with the node selector as simple as this they are in the same folder so they can be imported simple dot slash node selector and let's just go ahead and keep the state here. Selector open set selector open from use state from react just make sure you add that and once you have those two let's go ahead and just simply add open and unopen change. And now this big button here should serve as the on click as well. I think we have to refresh here and click plus. Looks like that is not happening and it's very clear why.
Because we never call setSelectorOpen to be true. So now let's go ahead and click on this. There we go. And now that I'm actually, Now that I think of it, maybe we don't even need to keep track of state from the outside because trigger will do that for us. But then it will be a bit harder to decide how to close it, I think.
Let's just, let's keep it like this for now. Alright, perfect. So now we have two ways. One, the initial way and one which will always be available here in the corner. Perfect.
Now what we have to do is that when we click on trigger manually, it should show the manual trigger node. When I click on HTTP request, it should add that. So let's go ahead and go inside of our node selector here and let's create the logic to do that. So the first thing we have to do is we have to get the current state of the editor. So let's call our use react flow which we have imported here.
And then we can get set nodes from here. We can get get nodes. And screen to flow position. And now let's create handle node select. Let's make it a callback like this and inside of here let's also make sure this accepts a type.
So node type is a type of node type option. Basically, the node type option is this, what we've defined above. So once we select one of those, first thing we're going to do is we're going to check if node type dot type is equal to node type dot manual trigger. Perhaps we can rename the node type in this case to be like selection, I don't know, selection dot type. Maybe that makes more sense.
Like if selection dot type is same as the manual trigger, we have to check if there is an already existing manual trigger right so you should never be able to add two manual triggers So what I'm going to do is do const nodes get nodes. Const has manual trigger nodes some node node type node type dot manual trigger. So is there any node already in the canvas which has a type of manual trigger? If has manual trigger, we're going to break this function by throwing an error. Only one manual trigger is allowed per workflow.
And let's break the function with a return. Otherwise let's call set nodes. Instead of here we are going to get the snapshot of the current nodes. And let's go ahead and do the following. Const has initial trigger.
Nodes sum, node, node type, node type dot initial. So we are checking basically, is this a state where we are adding the very first node so it's checking if this element exists because if it exists it means that whatever user just selected is the very first node that we are ever going to add. So we're just going to replace the position of that initial trigger. Const center X is going to be window inner width divided by 2 and center y is going to be window inner height divided by 2. And now let's go ahead and convert screen coordinates to react flow coordinates by doing const flow position screen to flow position x is going to be center x plus math dot random minus point five times two hundred and Y is going to be center Y.
So what we're doing here is basically if This is the logic we are preparing later when you add like more nodes, right? The way we're going to decide where to position a new node that's been added cannot just be a center because look at the canvas. If I go, you know, all the way here and if I click add node, I would expect it to appear here. So we can't always just choose the center. This is the center.
Imagine always having to scroll back. So that's what we are doing here. We are using the current camera angle. Like imagine it like that. Like we are using the current cursor position, the current canvas position and positioning it a little bit off the center so they don't like stack on top of one another.
And let's go ahead and do const new node here passing the id to be create id so we can use create id from cud to import data will be an empty object Position is going to be flow position. Type will be node selection dot type. And now let's check if has initial trigger, in that case, let's just return new node because we don't have to like append this to a list of nodes. We want to replace the existing initial trigger with this new node. And otherwise let's just return nodes, new node in a sense that we are just adding more nodes here and once we do that let's call on open change and set it to false in a sense that let's close the sidebar now.
Now we have to add all of the dependencies here. Set nodes, get nodes, on open, change, screen to flow position. Great. And now that we have that, we can use that as the on click in here. On click handle node select and pass in node type.
Copy this and make sure to also add it in the execution nodes below. There we go. Let's go ahead and refresh now. And now we should be able to add our node and this one should disappear. If I click this, there we go.
The old node disappeared and this one was added. If I go and move here and do it again, You will see I now have two of them and they follow my camera. Right? So if I initialize another one here, it's always going to be somewhere here. We slightly offset it randomly so they don't exactly stack on top of each other.
Okay, this is stacking, but at least it's not like this. Right? All right. Looks like this is working. Obviously the problem is we didn't initialize any component that should load when the type is manual trigger or when the type is an HTTP request.
So now let's go ahead and do that. Let's start by going inside of config, node components here and let's go ahead and let's prepare node type dot HTTP request and node type dot manual trigger. So the manual trigger one will be called manual trigger node and the other one will be called HTTP request node. Let's start by creating the HTTP request node here. We're going to keep it simple.
Though we will have to... Alright, let's do the following. Leave this as error for now. Let's go inside of source, components, and create a new base execution node.tsx. Base execution node.tsx, like this.
Let's mark it as use client here. Let's go ahead and let's import. Type node props and position from xyflow react let's go ahead and import type lucid icon from lucid react import image from next image import memo type react-node and use callback from react let's go ahead and now see do I have something called base handle? I only have something called base node. So I should be able to import base node and base node content from components, react flow base node.
It looks like both of them exist. Great. But we also need base handle. And I'm pretty sure that we can do that by going inside of here. Let's get sort of UI and let's try to find the handle here.
Here we have it. Base handle. So I'm going to go ahead and run this again. Again, if you, if for whatever reason, this is not working for you, using the link on the screen or the place, basically my assets folder, you can find all the components which we add in this way. So you can follow along.
Npx chat-cnui, I'm using my version and let's add a base handle. There we go. It's installing dependencies. And once we have the base handle, what I'm going to do immediately is move the base handle inside of React flow. Move.
Now I'm not sure if only base handle was added but I think that was the only component that was added. Great. Now we can focus back here in the base execution node and we can now also import the base handle so import base handle from react flow base handle and you can also change this to be like this, like a shorter line alright and let's import workflow node from .slash workflow node now let's go ahead and let's create an interface here base execution node props extends node props So node props was imported from here. The only one we won't implement for now is the status because it will require it will require another set of components and I think the best way to implement the status thing is when we implement the real time pub sub messaging because then you will actually be able to see like loading status error status or success status. So I don't really want to waste time developing that now because we won't really be able to see it in action.
Let's do export const base execution node. It's a memo like this. Type base execution node props Type base execution node props And open a function like this, all right Now let's go ahead and extract ID icon. Let's remap it to icon with a capital I, name, description, children, on settings, on double click, and I think that's it. Great.
Now let's go ahead and let's return workflow node. Let's pass in the name to be name description to be description on delete to be handle delete on settings to be on settings Let's go ahead and quickly just define const handle delete here as an empty function. Then let's render the base node. Let's pass in the... It should have on double click.
It does, great. On double click. So on double click is just a native HTML event handler that's why it's working it's just a normal div but It's it works just as the same way as on click that's right. And inside of here is the base node content. And then let's go ahead and check if type of icon is a string.
Let's render an image with a source of icon alt of name width 16 and height 16 otherwise let's render it as a node so depending are we gonna use an icon or are we going to use an image? Great. After that render the children. And now we have to define the base handles here. So render base handle, it's a self-closing tag.
Let's go ahead and give it an ID of target one. Let's give it a type of target and a position of position.left. So we imported position from XYflow React. Duplicate the base handle and give this one a source of 1 and a type of source. And this will be position.right.
And let's just end it by giving it a display name like this. So what's currently missing is the status as I've described but we are going to come back to this later. All right. So base execution node. And obviously we are also missing this.
So let me just add to do add the lead. And inside of here I'm going to do to do wrap within node status indicator which is something we're going to focus on later because we don't really have a way of displaying it right now. Let's remove the unused use callback. Now that we have the base execution node we can go ahead and develop the HTTP request node. So I'm going to go ahead inside of the following features.
Let's create a new folder called executions and let's go ahead and create a new folder called components and let's go ahead and create a new folder called HTTP request and in here we're going to create all business logic for HTTP request as well as all UI Things so let's do node.tsx like this Let's mark it as use client Let's go ahead and add some imports. We're going to need node and node props from XYFlow React as well as use React Flow. Let's import globe icon from Lucid React. Let's import memo and use state. Let's import our newly created base execution node.
You can also simplify this by doing this, I believe. Maybe not. Yeah, let's go ahead and do this. Instead of components remove the base execution node from here and let's keep it inside of executions components and if it asks to update imports you can select yes. So instead of base execution node you can now replace this with the alias because it makes more sense now.
And now you should be able to find the base execution node as your neighbor, because it will exclusively be used inside of executions here. And in here, we are going to use the same way we will build the HTTP request. We're also gonna build OpenAR. We're also gonna build Anthropic. We're gonna build Gemini this way.
So that's why I want to keep this close instead of just like randomly as in it can be used for whatever. So instead of HTTP request node.DSX, make sure that you have this now. And now let's just focus on creating HTTP request node data. Endpoint is going to be an empty string method is going to be either get or post or put or batch or delete body is going to be an empty string and then whatever other properties we might need. Now this data will require a separate model to be worked with, but let's already define it here simply so you can see that we will have some actual functionality behind these nodes.
Let's define HTTP request node type to be a node with HTTP request node data. So that's how we are going to specify a more specific type of node with specific data we expect. Let's export const HTTP request node to be memo props node props HTTP request oh we can actually know it needs to be node props HTTP request node type. All right. And then in here, what we are going to do is we're going to do const description to be node data question mark.
Let's see how do we get no data specifically just a second okay let's first define node data to be props.data as http request node data in the description let's do node data dot endpoint now if we define an endpoint for this HTTP request node we are very simply going to render the following node data dot method that we used or fall back to get and then we're simply going to display no data dot endpoints like this otherwise we're going to say not configured so basically If I know this is a lot right now and the reason it's complicated is because I just told you to write this entire thing but you don't really understand where you're going to add this fields right there will be a dialog that will open when you double click on HTTP request node and then you will be able to have inputs and forms to select these things and depending on that input we are choosing what to display on this node as in not configured like you haven't done any settings here or I will show you the exact method you chose as well as the endpoint you chose.
So now let's finally do a return here so we can see what's going on. So open a fragment and render a base execution node in here. And that's the cool thing about developing this base execution node. We are going to reuse it for every single node that we have, which is executing something. So now we're just passing some things here.
Icon will be globe icon. Name will be HTTP request. Description will be dynamic. So either not configured or the full, you know, option that we did. On settings for now empty.
On double click for now empty. So the double click will open the dialog or settings. Both of those will basically open the dialog and then the user will be able to configure the endpoint method body and key but we needed to like develop this because you can see we need for node types and let's just end this by adding the HTTP request node to be a display name like this. Great. So for now let's remove the unused use state and use React flow here.
Now that we have this, we can go back inside of node components here and we should be able to import HTTP request node from features executions components request, HTTP request node. Now we have to do the same thing for manual trigger node, but let's comment this out, just so we can see what we developed here. So I'm going to refresh and then I'm going to select an HTTP request. And this is how it looks like. Right.
So HTTP request with a globe icon. And it says not configured because it isn't. You will have to double click on it or click on the settings button which will open the dialog and that will basically allow you to pass in the method which is get put post patch or delete and the endpoint which is the URL as well as some other body configuration. And now we should do the same for this, because you can see how this one is not really configured at the moment. In order to develop the trigger node, we have to also create the base for it.
So just as we've developed the base for the execution node let's copy it and let's go ahead inside of features here and let's actually create a new folder called triggers and in here let's add components and then inside of here, paste the copy of base execution node and rename this to base trigger node. And now inside of here, make sure that you're inside that new file so you don't overwrite what existed, right? Let's go ahead and change this from base execution node props to base trigger node props Icon is gonna be the same name is gonna be the same description will be the same children we're going to comment out on the status, everything else will be the same here. So instead of base execution node here, again trigger. Same for the props here.
We're going to leave this to be just empty and we're going to work on it later. Workflow nodes, same thing. It will have its own settings, it will have handle delete, it will have description. We will also need to wrap it within a status indicator. Now, what we are going to start to do differently is this.
The base node will need a class name of roundedL to Excel. Relative and group. Let me fix the typo, relative. There we go. RoundedL to Excel.
All right. Now, instead of base node content, this is fine. Let's see. So We need source, we need alt, we need width, we need height. All of that is good I think.
Icon is good as well. Children is good. And now here's the crucial difference. You won't be able to connect to the base trigger from the target handle. So remove it.
It should only have a source on the right side. And let's go ahead and of course change these two to be base trigger node. There we go. So very simply we copied the base execution node, we renamed it in all instances, we gave a class name to base node here and we removed one base handle which was the target one. So a slight modification but quite easy to build we didn't have to do it from scratch.
Now inside of triggers here inside of components let's create a new folder called manual trigger like this and this manual trigger will now have node.tsx let's export const manual trigger node give it a memo with props props node props from XY flow react. And this one will not have any settings like our HTTP request. This is just a normal on click event, nothing special here really. So let's go ahead and just do a very simple return. Let's return a fragment because we will have more elements later.
Base trigger node. And let's spread the props. Icon will be mouse pointer icon from Lucid React. Name will be when clicking on, actually let's just do when clicking and then in quotes execute workflow. Like that.
Let's go ahead and do this. Let's pass in status to be node status and comment it out because we will need it later. So I will just add to do so I don't remember. On settings should just be it should be handle open settings so I'm going to comment that out too as well and on double click again handle open settings to do so I don't forget those things. Now that we have the manual trigger node let's go inside of node components here.
Let's enable it and let's import from features triggers components manual trigger forward slash node. And there we go. You can immediately see how this looks. So when clicking execute workflow, that's how this is looking right. Perfect.
So now you know how to you know create a sheet which can render new elements here on the canvas. And if I try to add one more trigger, I should actually get an error. Only one manual trigger is allowed per workflow. So I copied that from N8n. If you personally think for your use case, you should have multiple manual executions, feel free to remove that if clause that I added in the node selector.
Great, so obviously still room for improvement here, still a lot of confusion about why did we need all of that inside of you know base execution node How will we actually open the dialogs? How will we delete nodes a lot of questions for sure? But let's go ahead and see where we are right now. So we added the manual node HTTP request node and we built the node selector component. All of this I have to say did take more time than I thought.
So I think it's best that I leave the save functionality for the next chapter simply because in that next chapter I will also be working on creating the delete functionality So perhaps that will be kind of be a chapter where we manipulate the state of the editor. So we leave this one as a simple, okay, I learned how to create a node selector and how to remove the current initial node and how to render this executor style thingy. All right so yes this looks good. We're also going to kind of modify the CSS here a little bit. I don't I'm not sure I like them this dark.
I will see how we're going to fix that. Great. So let's call it a day here. And I'm going to prepare some things for the next chapter. So Let me go here.
16 node selector. I'm going to go ahead and commit this 13 files that I have here. I'm going to create a new branch. 16 node selector. And I'm going to go ahead and stage all of my changes here.
16 node selector, commit, and I'm going to publish the branch. Now let's go ahead and take a look at GitHub. Let's go ahead and open a pull request and let's review our code. And here we have the summary. We added a node selector panel to insert workflow nodes from the canvas to add button.
We introduced new node types, manual trigger and HTTP request. We added base components for trigger and execution nodes with consistent styling and connection handles. We enhanced initial node to open the node selector for quick starting points. We mapped new node types to their visual components in the editor. We also added a lightweight id generation library and we updated the database schema to support new node types.
Now in this specific pull request I don't believe there is anything too useful to look at a sequence diagram for but we do have some comments. So the first issue is CodeRabbit suggests not dropping all the nodes instead replace the initial node instead. So instead of just doing return new node or this, we find the initial node and replace it instead of the has initial trigger block. This is what I actually did initially. But the problem is if you scroll away, like drag the canvas away from that node, let me show you what I'm thinking about.
So if I refresh this, what it's suggesting is that this exact position gets replaced with the initial node right now that doesn't happen You can see how it's a little below it. It's basically random every time the reason I do that is because if you do like this and Then add it it's still gonna be here Otherwise if you do what code rabbit suggests it will have to be here. You'll have to go back here. So that's why I'm not doing that. All right.
In here it noticed a bug. We have double spread of props here so definitely a bug but as far as I know since this is in react flow folder it means it was added by them not us so at least we didn't write the bug but we should definitely be the ones who fix it In the add node button here we left an on click completely empty and we didn't notice because it's still working. The reason it's working is because we wrapped it inside of a node selector and we gave an open on open change here. And node selector is wrapping the button with a sheet trigger and I think that that trigger for itself calls on open change regardless that this is empty. I will make sure to take a look at this before the next chapter to give you a conclusion on whether we should remove this or maybe switch this selector open to be true here.
I'm not sure. But you know, this works. So I'm not sure what exactly to conclude. But yeah we could also add this it wouldn't hurt or we can just remove the on click altogether. And now most of these things that it's telling me are because of the unused on double click, empty handle deletes.
I'm not really going to take into consideration those comments now because this is still in to-do. We still have to properly use them. So these comments aren't really applicable right now. Now in here it told me to fix the node props generic and avoid unsafe cast. And I think that might be true because I'm doing HTTP request node type by extending node here and then I'm using node props to extend it using this.
I will see if that's really all I need. So in here it suggests doing node props and passing... Wait, but what am I doing incorrectly? Oh, I misunderstood. I thought it was referring to how I initialize the data, but it's referring to this specifically, to avoid casting like as HTTP request no data and making it work by default.
All right so yeah definitely if we can do that that would be an improvement. I will look into how we can fix that and the rest of these are just the same about our unused handlers. So let's just merge this pull request here for now and I will make sure to take a look at this fixes in the next chapter. Let's go back inside of our main branch here and let's synchronize the changes. Again amazing comments by CodeRabbit here and let's confirm we've merged by going inside of graph.
There we go. 16 node selector. So we didn't end up developing the editor state and we didn't enable editor save functionality. We will do that in the next chapter but we did finish the rest. Amazing amazing job and see you in the next chapter where we're going to explore the editor state, the save button, how to delete nodes, etc.
Amazing job and see you in the next chapter.