Now let's go ahead and create the selection box which is going to be visible to us. The person that is actually clicking on this element and not just the other person like this. So let's go ahead back inside of our canvas.csx right here. Let me just close everything but the canvas. And what we're going to do is we're going to go inside of our SVG element right below the layer iteration.
And in here we're going to add a new component called selection box. It's gonna be a self-closing tag and it's going to accept one prop. On, resize, handle, pointer, down. For now that's just gonna be an empty arrow function. Now let's go inside of our components and let's create the selection box.
So the selection box is going to be a client component. Let's go ahead and import memo from react like this. Also I don't know if you notice but you didn't have to always define use client so if the child element if the child elements parent is use client that will automatically turn this into a client component as well. I don't know if you knew that. Except if you are passing it through children.
So if you did for example children here then children could be a server component. But if you don't mark this as used client, this will still be a client component. The reason I like to mark it is so I explicitly know that this is a client component. Also, if you were to import this in something else, which is not a canvas, then you would get an error because it doesn't have use client in it. Just a quick tip about that.
And let's go ahead and create an interface selection box props and let's pass in on resize handle pointer down and let's just make it an empty void for now and let's export const selection box be a memo method my apologies that's not how you write a how you write a memo function. Like this, selection box, display name, selection box. And let's go ahead and this structure selection box props and on resize handle pointer down and in here we can return a div well just a div for now so we don't have any errors And now we can go back inside of the canvas and we can import the selection box from ./.selectionbox Great, so now we're going to continue working in this selection box right here. And the first thing I want to do is I want to properly add the types for this onResizeHandlePointer down. So the props it's going to accept is the corner, which will have a type of side from our types canvas and initial bounds, which will be a type of X, Y, W and H like that.
Great. And now let's go ahead and add a constant handle width to be 8. You're gonna see why we need this in a second. So it's gonna be the width of our handles which are going to appear in the corners so that we can resize our elements. What we have to do now is we have to find our layer id.
So const soleLayerId is going to be useSelf from LiveBlocksConfig. Let's get me. And then, so just import useSelf from LiveBlocksConfig here. Then we have access to ourselves. So we're going to write me.presence.selection.length is equal to one.
And let's go ahead in that case choose me.presence.selection the first in that array otherwise null And now let's go ahead and write const isShowingHandles. So we're not always gonna show the handles to resize. So we're gonna use that dynamically. UseStorage. Again, you can import this from LiveBlocks config.
So we're going to get the root. Like this. And let's go ahead and write if soleLayerId. If we have soleLayerId and if rootLayers get soleLayerId type make sure you put a little question mark here because it's possible that it doesn't exist so we are only going to show the handles if type is not layerType.path and we have to import layerType.fromTypes.canvas so make sure you have layerType.side and this.fromTypes.canvas like that And now we have to create a hook called useSelectionBounds so that we actually know how to display how far to select an element. So right now it's easy.
We know that we just have to use the height and some points around these boxes. But how about later when we allow a selection net? How will we know how big that selection box needs to be? Well for that we're gonna need a reusable hook called useSelectionBounds. So as always You can go directly in my github if you want to inside of the hooks folder or you can follow along so use selection bounds Let's go ahead and import shallow from Liveblocks client Actually, Let's import it from LiveBlocks React like this.
And now let's import layer. And let's import x, y, w, h. Let's import useStorage and useSelf from LiveBlocks.config. Now let's create a method bounding box to accept layers, which will be an array of our layer. And it's going to return x, y, width and height or nothing or if we cannot find the bound.
And now let's go ahead and get the first layer so layers 0. If there is no first layer we can immediately break and return null. Otherwise we are able to calculate some boundings. So let's write for let I is equal 1, I is less than layers.length and i++ so we incremented every iteration. Now let's extract from the layer so we get the current layer using layers and then I.
Let's extract the X, Y coordinates, width and height. And now using those, we're going to be able to assign the proper values for left, right, top and bottom. So if left is larger than the X coordinate, the new left value is going to be x, right? And I forgot that we have to assign these values first, so let me just remove this for now. So before we go into the iteration loop, we're going to add the initial values for left, right, top and bottom.
So let left is going to be firstX, let right is going to be firstX plus firstWidth, so it goes all the way to the other corner. Let top is going to be firstY and leftBottom is going to be first.y plus first.height so all the way to the bottom. And now we're going to go ahead and check whether truly these are the largest bounds we can find or if there are some other combinations. For example, if left is larger than X, in that case left is the new X value. Then if right is smaller than the combination of X plus width, in that case right is X plus width now.
If top is larger then this layer's Y coordinate. In that case, top is going to become the new Y coordinate and if bottom is smaller than a combination of Y plus height in that case the new bottom coordinate is going to be Y coordinate plus height like that And now what we have to do is return X to be left, Y to be top, width to be right minus left, and height to be bottom minus top, Like that. So that's our calculation method here and now what we can do is export const useSelectionBounds and let's write const selection to be useSelf. Let me just write const selection useSelf let's get ourselves and write me presenceSelection and then let's go ahead and return useStorage let's get the root Let's get the selected layers to be selection.map layer ID and we're going to get root layers get layer ID and let's go ahead and put an exclamation point at the end to indicate that this can never be false because we have to use the filter on this and we're simply going to filter by a boolean so I think if you remove the exclamation point you might get an error but okay it looks like not but I'm just gonna keep it here just in case because I think we are going to need it and let's return bounding box and selected layers inside and let's add a shallow which we imported above from live block react to our user storage So make sure you have this shallow here.
Great, so that is our useSelectionBounds ready to be used. So we can go back inside of our selection box component, inside of underscore components in the board ID here, selection box and then we can do const bounds to be used selection bounds like this so import useSelectionBounds from hooks useSelectionBounds and then if there are no bounds we are going to return null. There is nothing for us to select here. Otherwise, we are ready to create a fragment here and then we can add a rect element which is going to be a self-closing tag and let's go ahead and give it a class name of fill transparent stroke blue 500 stroke 1 and pointer events none and let's pass in the style to be transform open backticks translate and now we're going to use the bounds x value in pixels and bounds y value in pixels The x is going to be 0, y is going to be 0 initially as a property. And let's now pass in the width again from bounds width and the height from bounds height.
Like this. There we go. And now just make sure that you're actually rendering the selection box in here. Alright, and I think that this should already be working. So let me just refresh my screen here.
So now if I try to select something, so not the other user, there we go. You can see how we have a nice bound right here. Perfect. And if another user tries to do it, you can see how we have a matching of their color. So we can now do both theirs and we can do ours right here.
Perfect. So what I want to add now is further indication that we can resize this element by adding it little handles here. So let's go back inside of our selection box component. And in here we already prepared this boolean isShowingHandles. So now we can use it to actually display the handles below this rect here.
So let's write if is showing handles, only then are we gonna render the following. So let's write rect again, which is gonna be a self-closing tag. And now we have to find the corners and we have to attach it there. So let's write class name here. Bill White stroke 1 stroke blue 500.
X is going to be 0, Y is going to be 0. And now style is going to be dynamic. So cursor in here is going to be NWSE resize. Width is going to be dynamic. So let's go ahead and add handle width in pixels and let's go ahead and copy that for the height as well so it matches because it's a square and now let's add transform to put it in a corner so let's open backdex and write translate And we're going to use bounds.x and we are going to add an offset of our handle width like that.
And we're going to divide the handle width by two and let's write pixels outside. So that's the first argument in the translate then write comma. The second argument is going to be bounds.y minus handleBid divided by 2 again in pixels like this. So let me go ahead and try and there we go. You can see that now when I click on something I have a new indicator in a little box here that I can try and resize this.
You can see how it shows me that I'm going to be able to do that in this or this direction right here. Great! So now we have to do that eight times for different points around our box right here. So let's go ahead and just add one more thing here which is going to be on pointer down and in here we're gonna extract the event and there's just one more thing that I want to do here which is event.stopPropagation like this and then in here I'm gonna add to do addResizeHandler because we're gonna copy and paste this so that we don't forget to do that. So let's copy and paste this now and now we have to create it for a for the other corner right so let's change the cursor to be NS resize the width and the height can stay the same but the translate function is going to be different so what this is going to do let's actually remove it so it's easier for us to do it so we're gonna write translate the first one is gonna be bounds.x plus bounds.width divided by 2 minus handle width divided by 2 like that so that's gonna be the first argument.
And the second argument is going to be bounds.y minus handle width divided by two pixels. So don't forget this pixels here. And there we go. You can see how now This one is in the middle and now we're going to create this one right here. And we have to do that throughout the entire element.
Great, so this is good. So let's go ahead and I'm going to copy the first one again because I think it's going to be more similar. So let's go ahead and paste that here so this one is gonna have a different cursor of course this one is gonna be N-E-S-W resize like that and let me just remove the inside of the translate so it's easier for us to do this. So translate in here is going to do bounds. Oops, bounds.x minus handle width divided by two plus bounds.width in pixels, comma, bounds.y coordinate minus handle underscore width divided by two in pixels and there we go, we now have it in this corner like that and now we have to do the other ones so let's copy this one and let's just indent this properly alright so the next one here is gonna be ew resize alright and let's clear up the translate so it's easier for us.
So the first value in translate is gonna be bounds.x minus handleWidth divided by two plus bounds.widthPixels. And the second argument is gonna be bounds.y plus bounds.height divided by 2 minus handle width divided by 2 in pixels and there we go that puts us here in the middle and always make sure that your cursors are correct right they need to make sense where you will be able to resize. Great let's copy this one. Now we're gonna create the one in this corner here so that's gonna be nwse resize nwse resize like that Let's clear up our translate as always. So let's write the first one is bounds.x minus handle width divided by 2 plus Bounds.width pixels.
The second argument is going to be Bounds.y minus Bounds.y minus handle width divided by two plus Bounds.height pixels. And I think I've messed something up because it added it here at the top oh it's because I do a double divide here there we go now it's working Let me see if I can kind of display this in a nicer way for you. Is this possible? Yeah, this looks clearer, right? Like this.
Okay, I'm going to do it like this for now. This looks much clearer for both of us, okay. And now let's copy this. Also, if you think you're not learning much for just repeating this, you can just go into my GitHub and copy this. Like I understand this is just tedious work.
All right now let's go ahead and create the middle one here. So that one is going to be NS resize and let's go ahead and modify the first value in the translate here. So that's going to be boundsX plus boundsWidth divided by 2 minus handleWidth divided by 2 in pixels. And then the other one is going to be bounds.y minus handle width divided by 2 plus bounds height in pixels. There we go.
We added that one successfully and it goes up and down, corner, left, right, corner, up and down. Great. And two more to go. So let me copy this. Let me just align this properly.
Are the other ones aligned? They are. Okay. So this one, let me ensure this is the last one well almost last this one is going to be nesw resize and let's clear up the translate here so this one is going to have the first one of bounds.x minus handle width divided by 2 pixels and the other one bounds.y minus handle width divided by 2 plus bounds.height pixels. There we go And let's confirm this is a corner.
Perfect. And we are down to the last one. So this one is going to have EW resize. And let's clear up the translate here. So the first one is going to be bounds X minus handle width divided by two pixels.
And the second one is going to be bounds Y minus handle width divided by two plus bounds height divided by two pixels. There we go. We've wrapped it up. And what I want to do is I just want to go through all of my transforms here. Okay.
So this one is a simple transform and this one could have been displayed better. So let me just go ahead and display this in a better way. And you can do this with your code as well if you're genuinely trying to learn how this works. Right? If you don't want to just type it.
If you just, If you don't care, if you just want to type it out, that's okay. But just in case you want to specifically learn how it works, you can do this. It just makes it easier for the eyes to look at it in this way. And I have this one left. Right, you can of course just visit my GitHub.
I'm also doing this so it's easier for you to look at in the final GitHub. I know this is tedious work, but I don't know what is a better way to kind of teach you how to do this. You just have to do it for all of the points we need. Okay, so we have this, and let me just collapse this as well. Okay, so just make sure all of your translates are correct.
You can always visit directly in my github and copy the individual transform methods here if you're having any problems. And of course confirm that when you select something it matches. So this should be up or down, this should be the corner, left, right, corner, up, down, left, corner, left, right and another corner. Great. So what we're gonna do next is the actual functionality to move these layers around when we hold and when we hold on each of these boxes that's going to fire this event which we have in to do which is going to be called on resize handle pointer down.
Great, great job. Again if you're having problems with this you can always visit my github and just find the correct transforms for this.