So now I want to implement the ability to select items from this toolbar and for them to stay selected. So in order to do that I first want to go inside of the toolbar here and give the toolbar component some new props. So let's create an interface toolbar props here and just above it we're gonna create a type called canvas state which for now is just gonna be any. Obviously we're gonna create this be a proper state in a moment but let's just have it as a type here. Let's go ahead and actually pass the prop called canvas state which is going to be a type of canvas state.
Then let's give it a set canvas state which is going to be a method which accepts new state which again is a type of canvas state and it is returning a void. Then let's add undo function, let's add redo function and now let's add can undo to be a boolean and can redo to be a boolean. So now with these we can control all of this on clicks and all of this is active booleans. But now what I want to do is I want to create an actual canvas state here. So let's just give this toolbar props an assignment here.
Let's extract the canvas state. Let's extract set canvas state undo, redo, can undo and can redo. Like this. And now let's go ahead, close everything and let's create a root folder called types and inside of it create canvas.ts. So these are going to be common types which we're going to use for the canvas component.
And the first type that I want to create here is an enum canvas mode. So let's export enum canvas mode. And let's go ahead and simply set none to be the default one. Like this. And then let's go ahead and let's export a type canvas state.
Which for now is just gonna be a possibility of objects right so I'm gonna add a pipe element and then I'm gonna open an object and inside I'm gonna write mode to be canvas mode dot none like this and then later in here we're gonna add some more options so that's why I'm adding a little pipe element here alright, so just go ahead and add this and now let's go back inside of our app folder board.id.components.canvas not canvas, toolbar and we can remove this type canvas state any and instead we're going to use this canvas state here. So let's go ahead and import canvas state from at slash types slash canvas like this and I will just separate this to imports there we go now what I want to do is I want to go inside of my canvas.vsx ensure that this is a used client component and now what we're gonna do is a very simple set state here. So let me just prepare the imports for that. UseState from React and let's import canvasState from types Canvas. And in here I'm gonna go ahead and set the canvas state set canvas state to be used state and let's go ahead and pass the canvas state here and inside I'm gonna go ahead and write mode canvas mode from types canvas so we have that exported as well make sure you export both the enum and the canvas state like this so it's gonna be canvas mode dot none like that and now let's go ahead and pass that to the toolbar.
So now we have the canvas state, so we can pass the canvas state. Let's also pass set canvas state to be set canvas state. And we have can redo, I believe. Let's make that false. Can redo.
Let's make that false as well But we also have undo Oops, this is... One of this should be canUndo We also have undo methods, right? And we also have redo method. There we go. So we need to fill this for now as well.
And Luckily for us, we can do that quite easily, simply by using use history and use can undo and use can redo. So let's go ahead. Let's actually keep this at the top here. Let's add const history to be useHistory. So that's gonna be from liveblocks.config.
That is our file, which we have in the root of our application right here, liveblocks.config. So in here you can see we have use, can, redo, use, can, undo and use history itself. All right, so we have use history and let's also do use, can, undo, use, can, redo. So we have all of that prepared let's assign that const can undo use can undo const can redo use can redo like that let's pass in the can redo and can undo to the prompts, make sure you don't mismatch them accidentally, so redo, redo, undo, undo alright, and now let's go ahead and use history and pass the undo and redo methods. So that's very simply just going to be history.undo and history.redo.
There we go. Undo and history.redo. There we go. And now let's go inside of our toolbar and let's actually use that for our tool button components at the bottom here so the undo and the redo buttons so on click this one is going to use undo is disabled is going to be if we cannot undo so exclamation point can undo like this and for this one we're gonna have redo and it's gonna be disabled if we cannot redo so with an exclamation point as well and there we go now you should have both of them disabled So let me comment out the disable prop to see if we're gonna get any error perhaps if we try to redo. We don't get an error but nothing happens either way.
Alright, so just make sure this is then disabled. If there's nothing we can redo or undo. Great, so now that we have that, we added some control for our undo and redo. How about we now enable the select button to be active? So we can do that quite easily now, simply by defining when this active prop should be true.
So we can set that to be if canvas state from our prop.mode is equal to canvas mode from our types canvas, make sure you've added the canvas mode import here, is also none. Like this. And there we go. You can now see that by default my cursor here is selected because that's gonna be selected when canvas state mode is canvas none but also is gonna be some more elements like translating when we are resizing, when we are pressing and when we are making a selection net around multiple elements and let's also add set canvas state here to add mode canvas mode none like this so if something else is selected and once we click here this will bring back the canvas mode to none great So now what I want to do is I want to go back inside of my canvas types here and I want to add some more types which this is going to have. So first things First let's go inside of canvas mode and let's add all the other abilities here.
So besides none we're gonna have pressing. We're also gonna have selection net. We're gonna have translating. We're also going to have inserting. Resizing.
And a pencil mode. Like this. So now I want to go ahead and go inside of the canvas state here and select some other possibilities of the state. So this one is going to be mode canvas mode dot selection net like this Then We're also going to have a mode of canvasMode.inserting. And we're also going to have a mode canvasMode.pencil.
We are going to have canvasMode.pressing. And we are going to have a canvasMode.resizing. Like this. A mode canvas mode dot resizing like this and now let's go ahead and let's use this backs inside of our toolbar dot VSX right here So first let's go ahead inside of text right here and see how we can make that be selected for example. So I'm gonna go ahead and change this text to be is active if canvas state dot mode is canvas mode dot inserting.
Like this. If canvasState.mode is canvasMode.inserting like this and I'm going to add a non-click here to change setCanvasState and it's going to call mode to be canvas mode dot inserting like this. So let's try it out now. By default this is selected but when I click here there we go. You can see that now text is selected.
If I click back then this is selected. Perfect! So we are now controlling our toolbar on the canvas mode but you might already notice that canvas mode simply isn't enough for us to determine what is selected and what isn't selected, right? Because all of multiple fields will be able to use the canvas mode in the insert mode. For example, sticky note will be inserting, rectangle will be inserting, ellipse will be inserting, the pencil, all of these elements will be inserting something.
So we need to introduce another canvas state property, which is going to be called layer type. So we're gonna create a bunch of new layer types now for each of these elements, like text, rectangle, sticky note, ellipse, and pen. Let's go back inside of our types. So inside of types, canvas.ts, And let's go ahead and let's first add some small reusable types which we're going to need. So export type color.
That's going to be a type of RGB. So R is going to be a number, G is going to be a number, and b is going to be a number and we have to add equal sign here. Let's export type camera as well. This is going to have the coordinates x and y And now let's create our layer type enum. So export enum layer type.
That's gonna have a rectangle, an ellipse, a path, text and a note. So I'm not going to refer to note as a sticky note anywhere outside of the user. Internally, I'm going to call that just a note. It's easier. And now when we have the layer type.
Let's go ahead and let's create, let's export a type called a rectangle layer. So this rectangle layer is gonna have the properties which we are going to use to determine where and how to render it on our canvas. So the type is going to be a layer type dot rectangle. Then we're going to have X, which is a number, Y, which is a number. So these are the coordinates.
We're going to get the height and the width, which we will be able to resize. We're gonna have a fill which is gonna be a type of color to determine what color something is and we're also gonna add an optional fill value which will not be used for this rectangle layer but it will be used later in note and I found it that it's easier that all of our layers have the same types so we're just gonna make the ones that are not used by all of them optional like this because later it's just going to be easier when it comes to TypeScript. Great, so now we have rectangle layer and we can now copy and paste this one and create an ellipse layer in exactly the same way. So let's rename this to ellipse layer which is gonna be a layer type of ellipse. It's gonna have the X, it's gonna have Y, height, width, fill and value and that is pretty much it.
And now let's go ahead and let's add the path layer So the path is going to be for when we are drawing something. So path layer is going to have a type of path. It's going to have X, Y, height, width. It's going to have a fill, but it's also going to have points, which is going to be a matrix of numbers like this so double twice the array make sure you do it like this great and now let's go ahead and copy and paste this again and let's create a text layer so that's gonna be a type of layer type text. And it's going to have the X coordinate, Y coordinate.
It's going to have the height and the width. It's going to have fill but it's not going to have the points. And let's copy and paste this again. And this one is simply going to be the note layer. So layer type is going to be note.
We're going to have x, y, height, width, fill and value. And I think that should be it. Great. And now I just want to create a couple of more, three more additional types, which we are going to reuse throughout the project so we don't forget them while we are here. So that's gonna be a type point which is very simply gonna be a coordinate like this.
Then we're gonna have a type X Y width and height which is gonna be used for translating something and resizing. So coordinates like in above, but also width and the height itself. And we're also going to have an enum side. So that's going to be used for when we are resizing something. We're going to have top.
We're going to have bottom. We're going to have left. And we're going to have right. Like this. So 1, 2, 4 and 8 respectfully.
Make sure that you add them exactly like this. And now we can expand this canvas state to be a bit more specific when it comes to our objects here. So the first object can stay the same but the second object canvasModeSelectionNet will also need to have an origin which is going to be a type of point and it's also going to have an optional current property which is also going to be a type of point. So we're going to have the origin from where we start selecting something to the current coordinates from where we currently are and that's how we are going to calculate which the web or the net that we are using to select certain layers and elements. Great.
For the translating, we're going to have the current property, which is going to be required, and that's going to be a point. And for the inserting, we're going to have to differentiate between a layer type. So these are the possible layer types when we are inserting something. Layer type.ellipse, layer type.rectangle, layer type.text, and layer type.note, like this. So just make sure all of this are in one line.
Alright, we have that. Canvas mode pencil can just stay as it is. No need to add anything here. For the pressing we're gonna go ahead and give it an origin of point. Like this.
And for resizing we're gonna add the initial bounds to be a type of X, Y, width and height and the corner is going to be a type of side. So we're going to use the X, Y, width and height and side for resizing. So just make sure that you write those like this. Great, great job! So now we have more information, more specific information about what's actually going on inside of our canvas state.
Now we can go back inside of our toolbar and we can be more specific when we are selecting something. So let's go inside of the app folder, board components, board id components, toolbar and let's go ahead you can already see how we have an error here because when we are inserting something we also need to have a layer type so you can see how much that types helps us. So first things first, let's improve when the select mouse pointer icon should be active. So it's not just when canvas mode is none. It's also going to be when canvas state mode is the following.
It has to be translating or it can be selection net or it can be pressing or it can be resizing. All of those modes will trigger this select icon to be active in the toolbar. So when we are moving something, when we are selecting something, when we are pressing and when we are resizing. All of that is going to fall back to this item being selected to indicate to the user that none of the new elements are going to appear because they're doing a specific action like translating, selecting, pressing, resizing or nothing at all. You know, just moving the cursor around.
And the set canvas state can stay as it is. Now let's go inside of the type text. So first things first, let's resolve this set canvas state. What we're gonna do is that if we are selecting mode inserting we also need to add one of the appropriate layer types here. So we can see that inside of our canvas here.
Let's just find... There we go. So if mode is canvas mode inserting then layer type needs to be one of this. So later if you decide to add, I don't know, a triangle you can do that. You can create a new layer type, triangle and you will have to add it here as one of the possibilities.
So that's what I did, you know, I just added all the elements I could think of and you're gonna learn how I did that so that then you can add, I don't know, a triangle, a hexagon or something. So let's go ahead and add the layer type, in this case to be layer-type.text and we have to import layer type from types canvas so just make sure you input layer type here layer type.text there we go no more error and we also have to be more specific with our isActive prop here so let's also use canvasState.layerType isLayerType.text there we go so let's try and click here and there we go it stays selected meaning that we are properly switching the canvas state and isActive. Now let's go ahead and do the same thing for the sticky note. So sticky note is gonna call setCanvasState and we can copy and paste this too. So it's gonna be inserting but the layer type is going to be note.
We can also copy the is active prop and paste it here and the is active prop is going to check for inserting yes but the layer type needs to be note right. Let's try it out so when I click on note there we go note is selected Text is selected and select is selected. Perfect. So we can differentiate between those two. So I'm going to keep copying these two props.
OnClick and IsActive for the rest. So For the rectangle, let's do the same thing. Canvas mode inserting and layer type is going to be rectangle. And for the easy active, we're going to check if the canvas state layer type is rectangle as well. When I click here, there we go, it stays selected.
Let's do the same thing for ellipse here. So layer type ellipse and layer type ellipse. When I select, there we go. And last one left is the pen. So set canvas state for the pencil right here is gonna be a bit simpler.
So it's not exactly gonna work on inserting. The reason is that pencil is kind of gonna be different because we're gonna keep drawing things and we're gonna have to translate that live to the other user and only when we press our cursor up, when we stop drawing, is that gonna become a layer. So it's not like with rectangle and ellipse when we immediately click on something that's gonna insert a new ellipse layer or a new rectangle or a new text or a new sticky note. But with pen it's not gonna officially become a layer in our storage until we do the mouse button or mouse press up event. I'm not sure what's it called.
Only then are we gonna create that. So that's why the rules are gonna be a little bit different for the pencil here. So change the set canvas state for this one to very simply just change the mode to canvas mode pencil. We have an entire separate mode for the pencil. And the only thing to check is active that's needed is check if canvas mode is pencil.
That's it. So canvas mode pencil is the simplest out of all of this here. Let's go ahead and try that out now. So I can click on ellipse and I can click on pencil. Perfect.
And if I refresh the default one should be the select icon right here. There we go. Perfect. So if you are having trouble with this you can of course always visit my github. I would actually recommend that you take a look at my source code while you're developing.
So in here you can go inside of types, inside of canvas.cs and you can visit the entire source codes here and see whether you made any mistakes, whether you should change something. Great! So now what we are going to do is we're gonna go ahead and make it so that when other users are present and they move their cursors we can see their name attached to their cursor and their cursor has a color matching their presence. Great, great job.