Now that we have the sidebar for our shapes, I want to add the functionality that when we click on one of these elements, they appear as a new element centered inside of our workspace. So I don't want them centered on the entire canvas, I specifically want them centered in this element right here. So this is how I want to approach that. First of all, I'm going to go back inside of my Use Editor. So Let's go inside of the source, features, editor hooks, use editor.
And we're gonna have to add use memo here because I want to memoize my editor. So just above the init, we're gonna go ahead and find const editor right here and we're gonna use memo here. Let's define an empty function with a dependency array here and then inside I'm gonna do the following. If we have canvas in that case we're gonna return build editor like this so we don't have build editor at the moment but we are gonna have it otherwise we return undefined let's pass in the canvas here and now we have to define this build editor and let's do that outside of the hook so const build editor and let's just return an object like this there we go so this will be our editor which we are going to finally export from this hook. So right here where we export the init, we're going to...
Well, we're not exactly exporting, we are returning an object, right? So editor like this. And now that we have this editor, this is how it's going to look like. So for example, in here, we're going to have add circle, which will be a method, right? Like this.
And then if we go inside of our editor component, So inside of features, editor components, editor, we should now be able to have editor somewhere. And that is right here, editor. There we go. So then we will be able to pass that to, for example, shapes sidebar. So I go ahead and pass the editor like this.
I'm going to go inside of the shapes sidebar. I'm going to add this for now as any to do change type. And then I'm going to have access to the editor here. And then in here, when we click on the circle, we're gonna change this to be editor.add. Oh, we don't have the typings, yes.
But it would be add circle, right? And we know we're gonna have that because in here we added this. So let's try it out. You don't have to do this because we are gonna make this strictly typed. I just want to try it out.
So adding a circle. Like this. So now when I click this, just be careful not to write any typos because we don't have typings here, right? We defined this as any, right? So it's not gonna warn you if you have typos like this So just be careful with what you wrote here Let's try it out.
So now if I go ahead and open my inspect element here and select the shape and click here, there we go. It says adding a circle. Great. So that's the method. Well, not the method, but the constant we are going to export the editor and we're gonna use it to hold various methods, right?
I think this is a better solution than just exporting a bunch of features here like add circle and that. I think because we're going to have way too many of those. And this is actually taking inspiration from an existing react package I believe it's called fabric react So you can go ahead and explore that package as well. It's quite similar to this, but we do, we are going to have to do a lot of customizations. So I ended up with building our own solution rather than using that, but great package nonetheless.
All right, so now that we actually have this, I wanna go ahead and I want to define the types that will go inside of this build editor right here. So let's do that. So I'm gonna go ahead and keep that inside of the features editor types. So just below the active tool let's go ahead and let's export. Type build editor props and inside of here I'm just going to define canvas for now which will be a type of fabric.canvas and I'm not getting any errors here because we have a global namespace fabric but I feel safer if I import fabric from fabric here.
There we go. Now I just know where it's coming from. There we go. So that's what I wanted to have for now. And let's go ahead and define that in here now.
So I'm going to go ahead and define build editor props from types. I'm just going to change this to features editor types and let me move it here. And I'm going to change this to features editor hooks user size like this. And now inside of here, let's go ahead and do the following so I'm actually going to find this as well we can immediately destructure the canvas here for example and then what we can do here is we can find our add circle it's right here and we can do const object new fabric circle like this let's give it a height of 100 a width of 100 a fill of black Let's give it a stroke of black as well. And let's go ahead and do canvas.addObject.
And canvas.setActiveObject to that object. So that's why we needed the canvas prop. But now we have a little bug here at the bottom. In our editor use memo, we are not passing the canvas. So let's go ahead and pass the canvas.
And we already have it inside of our dependency array. So no need to add that. So if we've done this correctly now when you go ahead and let me just move this a bit here if I go ahead and select this there we go you can see that something has appeared but it looks like our colors are not correct Let's see if I messed up the colors perhaps. Maybe it can't be written like this. Maybe it needs to be like this in the hex or RGB.
Let's try it out now. I'm just going to refresh this. All right, it seems to still not be working. So perhaps we are doing something wrong here. Let's also add a radius here.
150. Will that be enough? Let me refresh again. There we go. So I was missing the radius.
All right. So it looks like it still has some issue here, but we are going to resolve that in a moment, but there we go. Oh, we turn it into a rectangle. Great. Okay.
We're going to play around with that later, but basically that's how we are going to add stuff right by using add circle and then we're gonna go ahead and use our past props like canvas to add them But before we go ahead and resolve this and turn it into you know not a weird transforming circle let's go ahead and do the following. What I want to do is I want to strictly define what my editor will return. So for that we're gonna go ahead and do the following. We're gonna go back inside of our types and we're gonna export an interface editor. And this will individually return specific things.
So for example we just added the add circle So let's go ahead and write it here, add circle like this. And then we go back in here and we're gonna go ahead and add the editor, right? Is that the proper interface? There we go. So we now have editor and build editor props from features editor types.
So the reason I wanted to do this is because now what we can do now that we have this editor and perhaps I'm not sure if I want to call it editor just in case if editor is a reserved keyword so let me try and removing the import all right editor is not a reserved keyword okay so everything is fine yes we can call the interface editor and the reason I wanted to do this right also you can easily try this out so if you change this to add a rectangle or something else you can see that we have an error that's because we did not add it here so we're gonna have to keep track of what's written here. What's the return of this method? And we have, we're going to have to properly type that in here. So the reason I wanted to do that is because now we can go inside of the components and in here, for example, shape sidebar, where we passed our editor, we can now remove this to do any, and we can use the editor type from, where is it? Features, editor types, right here.
There we go. And we have a slight error here because in the beginning, while our app is not initialized yet, right? The editor will be undefined. You can see that in here. When I define my editor, I only build it if we have the canvas, right?
If we don't, it's undefined. So what we're gonna simply do here, you can find many solutions for this. For example, if you want to, you can just decide not to render this shape sidebar or something. But in my original source code, I simply handled this to be either an editor or undefined. Like this.
And then in here, you're gonna have to be careful and you're gonna have to add an exclamation point here. But the cool thing is that now we have auto-complete here. So we know what we are writing. Perfect. So let's just try it out again to see if this is working.
There we go. All right, it's still a little bit weird and it's in this weird corner here. So what we're gonna do next is we're gonna go ahead and add all the methods for all of these elements right here. So first of all inside of these individual build editor when we define a circle I actually don't want to define those values directly here. I wanna keep them inside of my types.
So I'm gonna go back inside of the types here. And what I'm gonna do is I'm gonna have individual types. So for example, export const circle here, And we're gonna keep these default options. So let's call the circle options for example So radius by default will be 150 left will be 100 top will be 100 fill and Let's go ahead and define the fill color now. So I'm gonna do that here.
Expert const fill color will be RGBA 0.0.0 and one. So I'm gonna keep it in RGBA so that we can immediately have transparency. And let's go ahead and do stroke color. And we're gonna just copy and paste the same thing like this. So those are the default colors.
So fill will be the fill color, and then we're gonna have stroke, that will be the stroke color. Now let's also define stroke width. Let's give it two by default. And then in here, we can pass the stroke width to stroke width. Like this.
So just make sure not to add any typos in this. We'll see if we can, for example, define this as a type, maybe so we can have some stricter types, but let's try it out for now. So just make sure you export the circle options, go back inside of the use editor where you have the add circle. And now what we're going to do is instead of writing this manually, we're just going to spread the circle options. So let's just go ahead and export this.
So circle options. Let me see if I added this. Okay, circle, I have a typo here, circle options. And I'm just going to add the import from here. And I'm going to collapse these because I'm gonna have a lot of those.
There we go. So we now have circle options here. Let's try this one more time now. Perhaps now it's gonna be better. If I select this, there we go.
We have a beautiful circle right here. Perfect. One issue though is that when I add the circle, it's not exactly centered, right? So let's go ahead and do that. So what I want to do is I want to develop a little method that we can reuse a lot of times and it's going to be called Center and it's going to center alongside this workspace right here so it's not going to center alongside the entire canvas but specifically this white paper here so let's go ahead and do that so I'm going to go ahead and before we return this instead of the build editor we're gonna go ahead and do the following const center will accept an object an object can be any fabric object we don't care and inside what we are going to do is we're going to get our workspace.
And for that, I also want to create another reusable method called getWorkspace. So let's go ahead and do it above const getWorkspace. And we're actually going to do the same thing we did in the use auto resize here, I believe. So this, we're gonna get all objects and then we're gonna find the one named clip. So I'm just gonna copy it from here.
So we're gonna do the following, return canvas.getobjects and find an object whose name is clip because when we initialized our editor we added this initial workspace and we named it clip right? There we go. So that's our workspace. So now we have a workspace here if we are managed to find it, right? So what we can do here is Also get the center point of that workspace.
So const center Workspace question mark get center point like that and then let's just see what this will come up with so either a fabric point or undefined so what we can do here is now do canvas dot and here's another thing so there is a method here called underscore center object and you can and it accepts two arguments the object which we want to center and then the point where we want to center it. Not to be confused with canvas center object like this. So this one is different because canvas.centerObject simply accepts an object but it does not exactly tell you where to center. So if you try it out now, I'm gonna go ahead and do the following. So I'm gonna try just using canvas.centerObject because we have types for this, right?
So let's try it out. Let's go in here and let's center the object and the order of this matters. So make sure you always center your objects before you add them. This is going to come in handy later because it's gonna come in handy because of our undo history so if you go ahead and first add the object and then center it, that's gonna add multiple history states so it's just gonna look bad so let's try it out now So we can actually remove this rectangle in the middle now. It's just causing problems.
So let's remove it. So we have this test rectangle. Let's remove that. And we are not adding it. We are not centering it.
Let's try it out. So I'm gonna go ahead now and try this out. So if I go into shapes here and click, there we go. So you can see that it's not exactly centered, right? Because when we opened this and when we clicked on it, the center is actually right here.
But that's not where we want to center. We want to center here. Regardless if I have a sidebar opened, regardless if I'm zoomed in, zoomed out, I want to center in this area right here. So that's why this method center object is not something that we can use later. So it's fine to use it here initially because there are no sidebars opened or anything but when we use it here it's not exactly what we want.
That's why we want this. We want to use this underscore center object method right here which as you can see gives me a TypeScript error. But as long as you're using the Fabric version, where is it? Fabric 5.3.0-browser, you're going to have the exact same functionality as I do. So I'm going to save this and I'm actually going to leave the error as it is.
And let's try it out. So now I'm gonna go ahead here. I'm gonna select it and there we go. You can see how it's nicely centered to the point where we wanted it to be centered. Exactly inside of our workspace.
It's centered right here. Perfect. So this is what I'm going to do. So first of all if there is no center let's just break the method. This should technically never happen but since center can be undefined let's take care of that.
And then I'm gonna remove this comment and in here I'm simply gonna add this ignore. Like this. And there are of course many different ways you can center an object. So for example you can also do the following. You can get the workspace height, the workspace width, and you can get the object height and object width, right?
You can do any kind of math you want to center the object, but you can also use this handy little method that there is which exists inside of the canvas but for some reason it's not typed so that's how we are going to solve that and if you want to if you're worried about the order of these things let's go ahead and do the following. Let's do const add to canvas object, fabric object. And in here, we're gonna do the following. We're gonna do center object, and then we're gonna do canvas.add object, and then canvas set active object object. And then all we are gonna have to do is the following like this So let's try it out to see if this is still working.
So I'm going to refresh now. I'm going to select this and there we go. Now we don't care about the order because we only have to write it correct once. So just make sure center comes first. There we go.
So Now what we can do is we can go ahead and do the same logic for the following elements. So let's revisit our shape sidebar here. So next thing is the square. Let's just go ahead and see what kind of square. So it's a soft rectangle, right?
You can see how it has some little borders here. So let's go ahead and define that. So I'm going to go back inside of my, well, where is this? Let's go inside of the types, right? That's where I wanted to go.
Where we have the circle options and let's export const rectangle. Rectangle options. So in here, let's define some default left and top. So this left and top doesn't really matter because we replaced them by centering it, but you know, just in case, let's leave it. Fill will be the default fill color, stroke will be the default stroke color, stroke width will be the default stroke width like this and then let's define the width to be 400, the height to be 400 and the angle let's explicitly define zero.
So now that we have our rectangle options let's go ahead into the following. Let's go back inside of the use editor and the same way we added add circle. Let's now add add soft rectangle and we're gonna go ahead and call new fabric dot rectangle and instead of spreading the circle options, we're gonna spread the rectangle options we're gonna have to add an import for the rectangle options here make sure there are no typos, right? Rectangle options, great and now what we're gonna have to do is also pass in RX to be 10 and RY to be 10. So we add some border radius here.
And we have the type here error because we have to go inside of types. And we have to define our interface to accept this kind of thing. And now what we can do is we can go inside of the shape sidebar and change this to be editor add soft rectangle like this. Let's try it out now. So I'm gonna go ahead and refresh this.
I'm gonna select the soft rectangle and there we go. If you want to you can increase the roundedness of this. So let's just go ahead back inside of the soft rectangle. So yeah one thing about this is now when you command click on this it will lead to the types. So that's kind of meh but okay.
Let's go back inside of the use editor and if you want to you can increase this let's try 50 to see how this will look like now there we go so this looks like a soft rounded rectangle and you can already see how using these options you can play around and you can you know define as many various objects as you want to. So let's go ahead and wrap this up by adding the rest of the things. So the next thing is just a square so let's go ahead and do the following we're gonna do add rectangle and we're not gonna define any Rx or Ry, so just a rectangle let's go inside of the types let's copy and paste this and this will be add rectangle and we can then copy this on click here and this will be add rectangle. All right and now that we have the rectangle let's go ahead and try it out. It works.
Soft rectangle works perfect. Now we have to do the two triangles and we have to do the diamond so I purposely wanted those shapes because they are just a tiny bit different than the others so let's go ahead first and do the add triangle So for the triangle we can go ahead and define the types first. So I'm going to go ahead and copy the rectangle options here. I'm going to call this triangle options. The left and the top can stay the same, the fill, the stroke and the stroke width can stay the same and the width and the height, it can actually stay exactly the same, right?
Because we're gonna be inside of the use editor here once I copy and paste this and call this addTriangle we're not going to be using fabric.rect but we're going to be using fabric.triangle like this so we can actually leave the rectangle options but you know let's be explicit even though it's exactly the same Let's have the separate triangle options just in the future if we want to change them we know we can do that. And now that we have the add triangle here we also have to add that here. Let's go inside of this and add triangle. There we go. Let's try it out.
When I click here, perfect. We now have a triangle. So now we have to do the opposite of a triangle. So there are many ways you can do that. So let's go ahead and do this.
Let's copy and paste this and let's call this add inverse triangle. So if you want to, technically you can do it exactly like this and add a rotation or angle 180 I think, right? So if I go ahead and just give this to the types Let's see what will happen so add inverse triangle here and let me go inside of the shape sidebar Copy this and paste it here. So add inverse triangle and let's see what will happen now. So you can there are many ways you can do this right and there we go so technically this is working right it is the inverse triangle so if you're fine with this solution you can go ahead and do it but the problem is that you can see how it loads in reverse, right?
What I kind of expect is that it loads like this. So I want it not to appear rotated. I want it to be in this position this selection box You can see how this rotation is on the top right because that's its default angle But when I add this you can see that it's rotated below, right? So it kind of looks weird if you want to if you think this is okay, you can leave it like this. It's completely fine.
But I was inspired by other editors and I wanted to see, is there a way that we can make it seem like it's a different type of object and we can do that. But it is going to be a little bit different. So let's go back inside the use editor here and instead of adding the fabric triangle here we're gonna do the following. Let's go ahead and define the height to be 400, const width to be 400 as well and then the object will be new fabric polygon. In the first argument open an object and we're gonna write a matrix here.
So chat.gpt helped me with this. I did not exactly know myself how to build this, but I just want to give you an option of doing this because it might be coming helpful when you're doing something, some specific objects in the future. And that should be it like this. Let's try it out now. So now that we have this custom polygon here, it will still be an inverse triangle, but it should be a little bit different.
And there we go. So this is what I was talking about. You can see how now the rotation is so now it's reversed right but previous ones started as a reverse one so it was just a little bit weird So this is how I wanted them to appear, right? I want both of them to have their own natural position even though this one is reverse Well, that's what I want. I want it to be in a naturally reversed position You can of course decide for yourself how you want to do it.
And now that we know that we have this polygon option, you already know how we're going to do the last one, which is the diamond option. So I'm just going to copy the Add Inverse Triangle here. And I'm going to rename this to Add Diamond or, you know, whatever shape you want to call this. So I'm gonna call it add diamond and the fabric polygon is gonna be a little bit different. So in the first object, or let's call this, I guess this is a matrix, right?
So in the first settings here, it's going to be width divided by 2. The y-axis will stay the same. Then in this one, it's going to be height divided by 2. And then in the last one, the x will be 0. And this one will be height divided by two.
And we can reuse the rectangle options here like this. And let's go ahead and go inside of the types here, copy and paste this, write this. And I wanna be specific, so I'm just gonna copy the rectangle options even though they're exactly the same and I'm just gonna call it diamond options. So I can go back inside of the use editor here, I can add diamond options here and I want to be specific with my add diamond to use the diamond options and then we can actually use diamond options dot height here and diamond options dot width here. So the only reason I'm defining these in variables is that it's easier for you to see right here and I don't have to write this long text for tutorial every time.
And we can do the same thing here, right? So we have the triangle options. So triangleOptions.height and triangleOptions.width. There we go. Let's try it out.
So now if I believe we've done things correctly we can add a circle which is centered, a soft rectangle which is centered, a hard rectangle which is centered or sharp rectangle and we have the triangle, the inverse triangle and I forgot to add the diamond. Let's go inside of the shape sidebar and let's go ahead and add editor add diamond, make sure you execute the method. Let's try it out now. And looks like I've messed something up. So let's just refresh to confirm.
All right, looks like there is a bug here. Let's see what I did wrong. So we're gonna go back inside of my use editor here. Let's see. So it's gonna be a new fabric polygon like this.
The x here is width divided by 2 the y is 0 in here we have the width and the height divided by 2 and what I forgot is that there is another option in here before our last one where X is width divided by 2 again and Y is just height. Like this. So let's go ahead and try it out again. There we go. That is our diamond.
Perfect! So we just enabled our first sidebar here. It's fully working and as you can see some shapes are not exactly proportional, right? Like for example the circle is not exactly proportional to the rectangle and the diamond is not exactly proportional. So what you can do is you can go individually in these types, for example, diamond options, and let's go ahead and increase this to 600.
Perhaps that will make it more proportional. So I want to use this sharp rectangle as an example. And there we go. I think now if I rotate this, they match up exactly perfect. So they are actually the same width and height now.
What's left now is the circle which compared to this looks very very small. So let's find our circle options and instead of 150 let's try 300 for the radius. Twice the size. Let's see if that will increase it. So I'm using this for comparison.
And now it's a little bit better but it's still too much how about we try 225 for example there we go, now I feel like it's proportional You can of course go ahead and do the proper calculation needed for this or just, you know, do it by the eye. Great, great job. So you just learned how to add custom elements to your editor and how to center them. And also, I don't know if you've noticed, but every time, you know, we add an element, it automatically gets selected for us. So that's because of our set active object method so every time we do add to canvas let me just find it we do canvas set active object right here and again we have this little trick here with TypeScript ignore but it seems to be working just fine.
Great, great job.