So now that we know the basics of routing inside of Next.js, the next thing I want to start building is the actual editor, which I think is the most interesting part of this entire course. So the whole authentication thing, the software as a service thing, the homepage. We're going to do that later. You know, I want to knock out the editor as much as I can without the need for authentication. At some point, we are going to reach a roadblock where we simply need the user id to save their progress but up until that point there's so much stuff we can do and learn about Fabric.js which is the library we're going to use for our editor and all the other things that we are going to build ourselves.
So let's go ahead and let's create the route where users will access the editor in the future. So let me go ahead and just expand my content a bit. So inside of the app folder, we're going to go ahead and we're going to create the editor and then inside we're going to create a slug which we're going to call project ID. So in the future this is where users will load their projects. So using the project ID that's how we are going to know what state to initialize in the editor.
So to turn this into a route we add page.dsx right here. Let's go ahead and call this project, actually let's call it editor project ID page. And let's go ahead and simply spell editor for now. So if you go ahead and go into localhost 3000 slash editor slash one two three there we go you have the editor text right here so my goal for this is to actually just render a component called editor inside so where do we keep this component so in here I'm going to be using the features folder So features are not reserved for Next.js. This is actually a practice that I've seen from Kent C.
So I'm gonna go ahead and create a new folder called features and in the future each entity that we create we're gonna put here. What I mean by entity is stuff like stuff that's gonna be in our database but for example we're gonna have things like projects and then inside of here we're gonna have hooks, we're gonna have components, we're gonna have API so stuff like that. So that's what the features folder is for, right? So I'm going to remove this for now because we don't need it. So for now, we're going to have the editor feature.
And inside of here, we're going to have components and more precisely, editor.tsx. So just a component called editor. So first of all I'm gonna mark this as useClient. So useClient does not necessarily turn something from a server component into a client component. It's more, it should be looked as a client boundary, right?
We're gonna see this example shown better later when we actually start working with server components. So I'm obviously going to do a proper explanation then. But for now, all you need to know is that if you want to use your usual React stuff, like onClick, useEffect, useCallback, useMemo, all of those things, you need use client at the top. So that's for now. Later, we're going to dive deep and explain the difference between a server component and a client component and why do we need this at all.
Great. So let's go ahead and let's export const editor here and inside of here well we can do the same thing let's just change this to editor component so we know it's an editor component like this And then let's go back inside of our app folder, editor project ID page. And now in here, I'm gonna go ahead and I'm going to import the editor from features, editor components, editor like that. So I can very simply just return the editor now. Like that.
There we go. Great. So now what I want to do is I want to create a hook called use editor. So let's go ahead and do that. Back inside of the components, my apologies, features editor, we're going to create a new folder called hooks.
And inside, we're going to create useEditor.ts. Like this. So let's go ahead and let's do const use editor here. And for now, what we can do, we can immediately also export it. So export const use editor.
Let's go ahead and let's create an init method for now, like this. And let's return init in an object because we are later going to export multiple stuff from this use editor hook. But for starters, I want to learn how to initialize the editor. Like that is my goal for now. So let's go ahead and go back inside of our editor component here.
And since it is a use client, we can now do the following. So we can import that use editor. You can either use slash hooks use editor because they are in the same feature folder. But for some reason, I really, really prefer using the features editor like this. So I'm gonna be doing that.
And now what I wanna do here is I wanna go ahead and I wanna get that in it from the use editor like this and then I'm going to add a use effect here I'm going to add an empty dependency array and I'm gonna call this init method and ignore this warning for now. Let's go inside of the use editor and let's change this init to add console log, initializing editor. Let's go ahead and check this out. So if I go ahead inside of here and refresh this, there we go. I have initializing editor log.
I have it logged twice because in React strict mode or development mode, user effects are rendered twice. Great. So that's what I wanted to see. I wanted this editor component to call the init method from here like that. Great!
Now let's head back inside of the editor component itself here. And I want to do the following. I want to add two refs for two specific elements. So alongside useEffect, let's add useRef here. And I'm going to go ahead and I'm going to initialize something called a canvas ref which will be null by default and I also want to have a workspace ref which will also be null by default.
Let me just turn off my copilot so it doesn't mess around. Great, so we have that now. And let's go ahead inside of this div here. And I'm gonna go ahead and give this most outer div a ref of workspace ref. So that's gonna be our entire workspace environment.
And then we're gonna have a canvas where we are going to render the actual canvas. So the reason that we need two of these is because canvas by itself is very hard to be responsive. So what we're going to do is we're gonna have this parent div and we're going to observe its changes. So we're gonna be using something called resize observer. And then every time the size of this div changes we're going to update the width and the height and you know we are going to do the zoom in zoom out whatever we have to do to keep that canvas for the user nice and smooth So that's actually for me the hardest part.
Well, maybe it won't be the hardest part to code, but for me, it was the hardest part to figure out how I have to fix this. Right. Because the problem is, you know, when you resize, when you open, when you do all of those things, you're going to have to keep the content the user created centered. Great, so we have this and now what I want to do is I want to go ahead back inside of my use editor here and I want to change what it accepts. So I'm gonna go ahead inside this use init and first of all I'm gonna mark this as use callback so that I can properly put it inside of the dependency array.
So if when I add this use callback here with an empty dependency array I can then safely use this init and paste it here. Great. So now inside of here we are going to have to accept specific things which are going to be the initial canvas and the initial workspace. Let's go ahead and give them a type. So initial canvas will be a type of...
Let's give it any for now. We're gonna fix this later. But the initial container... My apologies. Yes, it's initial container, not workspace.
So the initial container will be HTML development. Like this. Great. Let's go ahead now and just try this out. So let's see, can this structure property of initial canvas param as it is undefined alright that makes sense because we are calling the init without passing anything inside so we can also do this we can change this to container ref that will have that will have more sense there we go let's go ahead and add those So we need the canvas ref and we need, my apologies, we need the initial canvas.
And let's go ahead and just give this an empty string. And we need a container, initial container, which will be the container ref.current, Like this. Now we just have to fix the types for this. So in use editor, we define HTML element. So that's what we're going to have to define here.
And let's also add an exclamation point at the end because we know we're going to have this container here because it is immediately added right here. And no, you don't need to add refs inside of the dependency array. Right? They don't have to be put here. Great.
But as you can see, our initial canvas is not exactly working as expected. So we're gonna have to change that a bit. We're gonna have to give our initial canvas a specific type here first, and we're gonna have to get that type from Fabric. Now let's go ahead and let's get familiar with the Fabric package. So this is the FabricJS package, which we are going to use.
So I'm gonna zoom in a bit, and here's the thing about FabricJS. So it is a very, very powerful package, right? But there is one thing that I found problematic about it. I'm not sure if it's written here in the gotchas or here just in the installation. So let's just do one thing first.
We are not going to be using the version 6. So version 6 is a big change and it's currently in beta. So that's why we are not going to use it. We're going to use a specific version that I used in my original code so we know that it is working. Right, so we have a bit of a problem with fabric and it's this.
Fabric depends on NodeCanvas. So when I was installing this I found an issue because all the instructions I looked at NodeCanvas were not working for me. Now that isn't to say that eventually you know obviously we can all install NodeCanvas at some point but for this kind of tutorial it's very hard for me to give you advice on how to install this because you can see that Mac OS has its own command, Ubuntu has its own, Fedora has its own, Windows has a whole wiki page on how to do it. So I thought, well, this is impossible. There's no way I can explain to everyone how they should do it.
I don't know where they're coming from. But luckily for us, other people had this issue as well. And then the maintainers of this project started to care about browser only fabric, which is what we need. So it looks like in version 6 that's going to be the default case. So you won't even need NodeCanvas if you use the browser version.
But we are not using version 6. We are using version 5. Which means by default we're gonna have this node canvas. But there is a little trick we can do. There is a specific package.
There is a specific package version for Fabric which allows you to use the version 5 for browser only. So I'm gonna go ahead and shut down my terminal and I'm gonna do bun add or npm install right or yarn add whatever you use so I use bun So bun add fabric at 5.3.0-browser. So this is the important part. Dash browser will allow you to use Fabric without the Node Canvas peer dependency. So let's go ahead and add this to our project.
There we go. I'm going to go ahead and do bun run dev inside. And let me just quickly go inside of my package.json here. And if you take a look now I should have fabric 5.3.0-browser. So just ensure that you have this exact version.
This is very very important and remember you can always take a look at the source code where you can find the exact versions that I'm using of everything. Great! Now that we have Fabric, we can go back inside of our Use Editor here and we can import Fabric from Fabric. So let's go ahead and import Fabric. And we also need to install the types for that so let's just go ahead and quickly do that so back inside of the terminal here I will just shut down my app.
BunAdd-d or npm install d, basically, dev dependencies, right? AddTypes slash fabric, like this. Actually, let's be specific, right? I wanna be specific because in my package.json, as you can see, I'm using 5.3.0, but my types right now are 5.3.7. So I don't want that mismatch.
So let me go ahead and do bun add-d, add types slash fabric, add 5.3.0. There we go. So I want it on the same version that I have my fabric package and I recommend that you do the same so you can do the same thing with npm install. Let's keep our app running. I don't think anything bad is going to happen if you have a different version of types but you might have some mismatch between what exists and what does not exist.
Great! So now in here in the fabric what we can do is we can more precisely define what our initial canvas is supposed to be. So it's supposed to be a fabric.canvas. Like this. There we go.
So now let's go inside of the editor here back and now we can actually define this properly. So we're gonna go ahead and we're gonna say const canvas is gonna be new fabric which we have to import again so import fabric from fabric new fabric dot canvas and we're gonna pass in as the first argument the canvas wrap dot current and for the second argument we're gonna add some options so controls above overlay are going to be true and preserve object stacking is going to be true. And then we're gonna go ahead and pass this canvas here. There we go, no more type errors. Every now and then you're gonna see me use this format document width and I'm gonna select prettier.
So this is what I want to do. I just want to, you know, fix the formatting. Great. So why did we add these two options? So I added them because I know we're gonna have to use them in the future, but I'm gonna come back and turn them off so you can see what they're used for exactly.
Great, let's go ahead and let's just do one thing before we try and check this out. So I want to go inside of my source folder I believe. Inside of my app folder, globals.css. And I'm gonna go ahead inside of here and just format the entire thing like that. And I'm just gonna add HTML body.
You don't have to format. So you don't have to do this, right? I just like to do it. You can see that I had some spaces here, but it doesn't matter. All you need to do is add HTML body by 100%.
That's it. Now let's go ahead back inside of our editor component here and I want to wrap the entire thing in another div where I'm just going to give it a full height like this. And let me go ahead and just give this div a class name of flex1. Height full as well and let's give this flex as well. And I want to give this a background color of muted by default.
Like this. I think that should be good enough and let's also do a flex call here. All right so we added the top div now which just assigns the full height here and starts the flex box and our container now will have the full flex right here because later we're gonna have some other elements here so that's why we're gonna need this flex1 but for now all that we should see is that we now have kind of a muted background on our local host. Let's see, there we go. So if I remove BG muted, there we go, it's white.
And if I add BG muted, it's kind of toned down, which is what we want. Great. So now what I want to do is I want to actually initialize this canvas right here. So for that we have to go back inside of the use editor here and now that we have the initial canvas let's go ahead and let's actually do something with it right here. So right now if you try dragging you know in your project there is this specific room where you will be able to see fabric.
Try in the most left top corner That's where most likely you're gonna see your canvas. So what is that? So that is this. The canvas. Once we've added new fabric canvas and assigned it to the ref right here it automatically became interactive.
We can try and drag in here but there is an issue as you can see right here. We can only drag so far. It looks like there is some overflow going on here. So we're gonna go ahead and try to debug that now. What I'm gonna do is I'm gonna go inside of the use editor here and I'm going to go ahead and I'm going to use the initial container and I'm going to use its height and width to assign it to the initial canvas.
So we no longer need this. Instead what I'm going to write is initial canvas dot set width and I'm gonna set the initial container dot width, my apology, offset width like this. Let me just expand this a bit. And then I'm gonna do the initial canvas set height, initial container, offset height. Like this.
So let me just format this So it looks nicer. There we go. Let's refresh and let's try this out. There we go. You can see how now I can use my entire space here.
But if you mess around with resizing, you're gonna notice that it's still not exactly perfect. Right? I can only, but if I refresh from here, then I can use the entire space, right? But if you load your browser in a very small browser, now you have the full space and then you expand and try, you're gonna see that it did not resize again. That's what I was telling you in the beginning.
It is kind of difficult to create responsive canvases so that's why we're gonna have to use an observer. Right now I just want to you know kind of be able to set up the initial height and width which is going to be using this container ref right here. But there's one thing that I want to do before we do that and that's the... I want to add like a little paper in the middle of my project here. I want to have a little paper box, right?
Because we are of course gonna be able to select everything but we are only gonna be having our work visible in a little box in the center. So let's go ahead and do that. We can do that by creating a rect and then clipping that with the initial canvas. So let's go ahead and do it like that. I'm gonna go ahead and add initial workspace to be new fabric dot rect rectangle right and I'm gonna give it a width of the well, let's go ahead and give it, let's hard code it for now.
So width is gonna be 900, height is gonna be 1, 200 or 1, 200, if that's how you say it in the US, I'm not sure. Name is going to be clip, fill color is going to be white by default selectable is going to be false pass controls will be false as well and let's add shadow, new fabric.shadow color, rgba 0.0.0 and then alpha is going to be 0.8 And let's add a blur of 5. Like this. So that will just create a rectangle, but now what we have to do is the following. So after we set the width and the height, let's add the initial canvas again, and let's first add the initial workspace, which we've just created.
And then let's go ahead and do another thing. Let's call initial canvas center object and pass in the initial workspace. So it's in the center. And then we're going to go ahead and call initial canvas dot clip path initial workspace. So every element that is outside of this initial workspace will actually not be visible.
So let's go ahead and try this out now. So I'm gonna go ahead and expand my screen a bit and refresh. And there we go. You can now see we have this huge white space right now at the moment. So everything is this white rectangle.
We are gonna change that later. I think that I can actually fix it if I zoom out a bit. There we go. So I will zoom out to 125 and there we go. You can see now how I have a rectangle.
So this is gonna be our workspace, right? We're gonna be able to draw inside, but everything outside of this canvas will actually not be visible. Let me see if I can expand even further. There we go. So something like this.
I have to zoom out a lot at the moment for this to be active. That's because we don't have any elements at the top or on the side to push it, right? But this is what we wanted to achieve. Great. So why did we add this specific controls here?
Selectable and has controls. So if you don't add these features now, and if you refresh right here, you're gonna see that this just turns into, your basic rectangle, right? And you can resize it, you can expand it, right? So if you want, you can play along with that. But that's not what we wanted to achieve with this.
We wanted to create our initial, you know, paper. If you want to, you can go ahead and add another rectangle here. So I'm gonna go ahead and do this. I'm gonna add const test new fabric.rect. Let's give it a height of 100, a width of 100, and let's give it a fill of black like this.
And then we're gonna go ahead and do initial canvas.add, and we're gonna add test, and initial canvas center object is gonna be test. So it's in the middle. There we go. And there we go. We now have a little box to play around with.
And now this is what I wanted to demonstrate to you. As you can see, because we use this clip path here it means that only stuff inside of this little white paper let's call it, we're gonna refer to this as our workspace. So only that part is visible right but as you can see we still have the controls outside of it, but that is not enabled by default. We enabled that, if you remember, right here, I told you that I was gonna explain what this features are. So if you turn these off, and let's refresh here again, and let me just expand this, and if I go outside, you can see that now we don't have controls outside.
So it's quite difficult to understand what's going on. But if you enable this, actually, I don't think the preserve objects stacking is related to that, but we are going to need that as well. But if you enable this and refresh, make sure you're zoomed out so you can see nicely, right? So I'm zoomed out to 67% now. So now when we have that enabled, you can see we have the controls outside.
So it's pretty nice looking at the moment. Great. And I just want to do one more thing to wrap up this chapter where we are getting introduced to use editor and fabric. And that is, I want to show you how you can style the controls. So right now there are barely visible controls if you ask me but there is a very easy way you can change that.
So inside of the use editor here we can do fabric.object.prototype.set and now let's go ahead and do the following. Let's add corner color here to be just plain white. Corner style will be a circle. Then we're gonna have border color. Let's use hashtag 3B82F6.
Border scale factor will be 1.5. Transparent colors, corners, my apologies, will be false. Border opacity, when moving, will be one. And then copy this color and give it at the end corner stroke color Let's go ahead and try this out now. So I'm going to go ahead and expand this, refresh.
And there we go. Now this looks much better if you ask me. Great. So we're going to go ahead and continue with this later. And what we're gonna do next is we're gonna create that you know, resize observer because right now you can see when I expand it doesn't exactly, you know, follow my size of the screen and also I want this to always be centered.
So stuff like that, those little details are what we're going to have to focus on next. But this is a great, great start. Great job.