So now let's add an ability to select one of the elements which we are able now to add to our canvas. And once we are able to select an element, later we will be able to transform them around, to resize them and also to change their color for example or completely remove it or change the depth of the layer it's in. So first thing that I want to do is I want to go back inside of my App, Board, Board ID Components, Canvas And in here we have a couple of things left unfinished. So we manually hard code the selection color and we don't pass anything for the onLayer pointer down. So the first thing I want to resolve is the selection color issue.
So I'm going to go outside of the return function here and in here I'm going to generate a method to get that selection color depending on which user is currently selecting that element so we know if someone else is editing that element. So for that, we're gonna get the selections from other users presence. We can use useOthersMap from add live blocks. So that global import file that we have. We can then extract the other user and we can use other.presence.selection.
So how do we have selection there? We have selection because in the presence type alongside our cursor, we prepared the selection. So every user will have their own selection so that way we can keep track of who is selecting what and now we're gonna write const layer IDs to color selection and that's gonna be memoized so let's add use memo here so import this from react in this use memo we're gonna go ahead and in the dependency array for now just add these selections because we're gonna work with them and in here add const layer IDs to color selection again. It's going to be a record with string inside. And let's give it a default value of an empty string.
And let's do for const user of selections. Let's extract from the user connection ID and the selection and then for const layer ID of selection we're gonna go ahead and create a map using this layer IDs to color selection here we're gonna attach a layer ID to our method connection ID to color from libutils so make sure you've imported connection ID to color from our libutils let me just collapse those two And in this layer ID, we're simply gonna pass in our connection ID For that user like that and I think I accidentally removed something here Yes, my apologies. So I just somehow removed this and close here. Don't know how. Basically, make sure you have connection ID to color imported and then passing the connection ID from that user.
There we go. So this is all supposed to be in one line. Let me just confirm that like this and then what we are going to do is just return this object layer IDs to color selection like this there we go So make sure that you have layered this color selection object here. We iterate over our other users selections. We did structure their connection IDs and their current selection.
We look at the layers they are selecting because we're gonna be able to do a map or a net of multiple selections and then we match their connection ID and create a color matching to their cursor and also in this presence bar above. So you're going to exactly know which user is currently selecting those elements. And now we can use this layer IDs to color selection down here in the selection color prop. So all we can do now is simply passing layer IDs to color selection and passing the layer ID like this. Great!
So now what I want to do is I want to go back inside of my rectangle here. So let's actually keep track of this selection color to see if we are hard coding it anywhere else. So inside of layer preview here, let's take a look. We have selection color, we have it here and we pass it to the rectangle. So that seems to be alright.
But in the rectangle, we don't actually use that. And we also don't use a fill as well. So for the selection color, what we can do, we're only gonna use it for the stroke. So it's not gonna change the color of the object, only the outline to indicate that something is going on. So we're gonna turn this into either a selection color or transparent, like this.
Naturally, this is now not going to work so I have my other browser open let me refresh so you're gonna see another user pop up here now in a second let's just wait for this to load there we go I am selecting these but nothing is happening That's because we haven't implemented a method that when we click on something, we actually add it to our selection presence. So we have a map to iterate over other user selections, but we are yet to implement the actual functionality for that. Great. And here's another thing that we have hardcoded here before we can move on and that's this fill which currently is hardcoded to color black which is technically correct because inside of our canvas we do have, where is it, Last used color to be RGB black, but let's actually use that fill property, which is a type of color, which is basically RGB object. And let's go ahead and create a util, which is gonna be called CSS, color to CSS.
That method will help us transform from an RGB into a hex code. So let's go inside of libutils here. So if you want to, you can write it along with me Or you can always visit my github and go directly inside of lib Utils right here and you can do that every time we are here in the utils So this is what we have to write now this export function color to CSS So let's go ahead and build this. So in here, let's go ahead and export function color to CSS. So this method will accept the color property, which is a type of color from our types canvas.
And you can already see that this is an RGB. So basically we're gonna use these three values to generate a hex code out of this. So let's go ahead and let's return a string beginning with a hashtag because hex codes start with a hashtag. We're going to use color.r because that's the first one. We're going to use toString16 like that, padStart to zero, like that.
Then we're going to immediately add the second which is color dot G do string 16 dot bad start to and zero again and then we're going to go and do the last one which is color.b, twoString, 16, pathStart, two and zero, like that. And then, Well, that is it because we are immediately returning that. Perfect. So you can probably understand how it works. Basically, we're using the radix in the toString on hexadecimal value.
So it's going to revert value like 255 or 255 to which would technically be 0xFF which converts to FF. You can also do an npm package for this if you want to or you can just copy it from myutils. It really doesn't matter You just need to find a way to transform an object of RGB to a hex code. Great, so now that we have this, we can go back inside of our rectangle component, app board, board ID components, rectangle. And now we can go ahead and use that right here so let's go ahead and check if we have the fill color we're going to use color to CSS from libutils and pass in the fill color or we're gonna use a default gray color like this or you can use the default black color whatever you want it to be in case for any reason you don't pass the fill or it somehow fails.
There we go So now you can see nothing really is changing. But what should happen now is that if you go ahead inside of your board canvas and change the last used color to something different. I don't even know these values specifically. But if I choose a rectangle now and press it, it should have a different color. But it seems like it's not.
So let's go ahead and debug whether we are actually passing this. Yes, it seems to still be generating plain black. What if I change everything to 255? This should be white. Oh, it looks like I just don't know RGB.
There we go, it's working. So just try it with 255 and completely black. Perfect. So I'm going to bring this back to black and now if I try all of them are black. Perfect.
So we are now no longer hard coding those colors and now we are ready to actually create a function which we have right here onLayerPointerDown. So this is the method that I was talking about. So right now in my other browser here if I press on this nothing happens because there is nothing happening onLayerPointerDown. So we have to create that method so that we can actually store selection in other users and our presence. So let's create onLayerPointerDown.
I'm going to do that just below our or let's do it above layerId's color selection here. So const onLayerPointerDown is going to be useMutation. And in here, let's go ahead and destructure self and set myPresence. Let me just immediately go ahead and return this object just so we don't have any type errors here. So self and set my presence.
And the second argument is gonna be event, which is react.pointer event. And the third argument is gonna be layer ID, which is a string. And now what we're gonna do is we're going to check if canvas state mode is canvas mode dot pencil or if canvas state mode is canvas mode inserting. So if it's any of those two we're going to break this function because it means that we are currently inserting something so no point in trying to select a layer or we are drawing something with the pencil. So only if we have our free hand selected here, that means that we are selecting something.
If we are inserting something, which is text, sticky note, rectangle or ellipse, then this method will not fire. And if we are using the pencil, which is the separate mode for itself. Great. So if we are doing this let's do history pause. Let's stop propagation and then let's get the point where the user is selecting.
So point is going to be pointer event to canvas point which we already used and it passes the pointer event and the camera. And now let's do if not self presence selection includes layer ID. So if we don't have this selected, in that case, we are going to make sure we select it. So set my presence, selection, and pass in the layer ID we are selecting and let's also add to history true like that and let's call setCanvasState after this so we reset the mode to canvasMode.translating and current is going to be our point like that and for this we need setCanvasState need camera history and we also need canvas state mode. Like that.
So now let's copy this onLayerPointer down and let's append it to onLayerPointerDown in the layer preview here. We have no type errors. Our layer preview component accepts this normally it seems. And let's just confirm that it passes it down to the rectangle. It does.
The rectangle onPointerDown is used and passes in the event and the ID. So I think that now we might already see something. I'm not 100% sure. I might just figure out that we are missing something. But if I try and click here, there we go.
I don't know if you can see it. There we go. You can see it on the white one. So now when another user selects it is very visible that they are selecting. So how does this work?
Well let's go ahead and recap. So what happens is that let's go all the way from the rectangle. When someone presses on the rectangle here we fire the on pointer down with the event so we have information about where the user clicked and we're passing the layer ID so we know that this was clicked on a layer and not randomly somewhere on the canvas. Then this on pointer down goes to the layer preview and it fires the on layer pointer down which is simply passed down from the canvas function and then on layer pointer down ensures that this was done intentionally and not during drawing or during inserting something. And then what it does is it generates our pointer event to canvas point which we use in combination with our camera, right?
And we add that to our presence storage in the form of selection array passing that new layer ID here. And then we also change the canvas state to translating so that we can... You're gonna see it in a second. So when we select something, right now on our side, nothing is happening, but on our side, it's gonna be a little bit different. Yeah, I don't know if you notice that, but if you have another account, when you click, this is visible only for the other user.
But if you yourself click here, it's not visible because we haven't implemented that yet but it is visible for the other user ironically. So that's what we're gonna do next. Now we're gonna go ahead and implement it so that when we select on this there is like a selection bound around this and then we're gonna have some handles to resize and expand that. Great, great job!