Now let's go ahead and let's add some more elements to our canvas. So right now if you try and select a rectangle that's gonna work fine. But if you try and select text for example that will throw an error unknown layer type and also render a transparent or an invisible element. And same is true for everything which is not our rectangle. So we can fix that quite easily.
We have pretty much everything prepared for us. And the first thing I want to do is the ellipse, since it is the simplest and the closest to the rectangle. So let's go inside of our components here, my apologies in the app folder, board components and let's go ahead and let's pick the layer preview. So in here we only have the case for layer type rectangle. So now let's add a case for layer type ellipse.
And all we have to do is return and inside of here we're gonna go ahead and render ellipse component which we don't yet have but we're gonna create in a moment. Let's pass in the ID, let's pass in the layer on pointer down to be on pointer down and selection color to be selection color like this and this is supposed to be on the layer pointer down, my apologies. And now let's go ahead and create this one. So ellipse.vsx. Let's go ahead and mark this as...
Actually, we don't need to mark it as useClient. Instead, let's just import color to CSS from libutils. And let's also import the ellipse layer from types canvas. Now in here, let's create an interface ellipse props to accept an ID layer, which is a type of ellipse layer and on pointer down which is an event of react.pointer event and an id of string. Selection color is going to be an optional string.
And now let's export const ellipse. Let's assign ellipse props. And let's go ahead and destructure the id, layer, on pointer down and selection color. And now let's go ahead and let's return the ellipse element and let's write class name drop shadow md let's write on pointer down to get the event and passing on pointer down event and the id of the layer style is going to use transform and let's go ahead and open backdex and let's write translate and let's go ahead and pass layer.x in pixels as the first argument and layer.y in pixels as the second argument. And let's pass in Cx to be layer width divided by 2.
Cy to be layer height divided by 2. Now let's pass in rx to be layer width divided by 2 and let's go ahead and add ry to be layer.height divided by 2 Now let's pass in fill property to be layer.fill. If we have it, it's gonna be color to CSS layer.fill. Otherwise, let's use the default black color. And stroke is either gonna use the selection color if someone else is selecting it or it's going to be transparent and stroke width is going to be 1.
There we go. Now we have to go back inside the layer preview and import the ellipse component from dot slash ellipse the same way we did with the rectangle and let's go ahead and click here and there we go you can see how we have an ellipse now and it picked up the last color which we used let's confirm that we can bring it to back bring it to front we can expand it we can move it we can delete it and if I try it again it's going to use the last selected color. Perfect. Let's go ahead and confirm that I can move it with other layers. I can do that as well, no problem at all.
Great! So now that we have this, let's go ahead and let's create our text layer. So to create the text element, we're gonna need to install a package called react-content-editable. So let me open a new terminal here and run npm install react-content-editable. Like this.
And now let's go ahead and let's go inside of our components here and create a new file text.tsx You can of course name it something more specifically if you wish with a prefix Let's go ahead and import the font I want to use for text So I found this font on Google fonts that looks like handwriting. So I'm going to use that one. You can of course switch it out with any color, with any other font you want. Now let's import content editable from react-content-editable. And we're also going to need the content editable event from react content editable.
Let's import cn from libutils and color to css from libutils and let's import text layer from types canvas and let's import use mutation from live blocks dot config. And now let's define our font here. So column, I'm not sure. Subsets is going to be Latin and weight is simply going to be 400. Now let's create an interface text props to accept an id which is a string a layer which is a text layer which we've imported above and we're gonna have on pointer down to be an event of react pointer event and an ID of the layer and selection color is going to be an optional string and now let's go ahead and let's export const text Let's go ahead and assign the text props here.
Let's extract the layer on pointer down, ID and selection color. In here we can extract from the layer all the info we are going to need like the X, Y, width, height, fill and value. And then let's go ahead and return a foreign object because we are rendering this inside of an SVG element so we need to use foreign object and inside we're going to render a content editable and let's go ahead and just pass in the html for now to just be text let's pass in the on change event for now to be an empty onChange event like this. And now what I want to do is just a quick little positioning on this foreign object so we can start seeing this. So X is going to be X, Y is going to be Y, width is going to be width, height is going to be height.
And we're going to have on pointer down here to fire an event on pointer down and pass in that and the ID of our layer and style we're gonna have the outline here to look for the selection color if we have it we're gonna write 1px solid and then we're gonna use selection color otherwise is gonna be none. Actually, I think we need to use color to CSS here. Oh, actually, do we? Let's check ellipse. No, no, no.
Selection color is a string. Yes, my apologies. No, so this will work just fine. Okay. Yes, let's try it out just like this.
So now I'm going to go back inside of my layer preview and let me import text from .slash text the component I just created and let's go ahead and create a case layer type.text here and let's return the text element here and we're gonna go ahead now and pass in the ID which is ID layer on pointer down, which is on layer pointer down and we're going to pass in the selection color to be selection color. And let's see if this will already start showing something. So when I click text, there we go. You can see that we have text written here and we can already start writing here, but this is obviously not saved and it's also not transmitted to other users. So yeah, always make sure that you also do a little testing on multiplayer, right?
So you can see how I'm selecting in another browser here and you can see how that looks like. So I have an indicator that this user is selecting and moving this. The resizing works in real time. So always, you know, do a double check that all of those things are working as well. Alright so now what I want to do is I want to complete my text component so it looks a little bit better.
So let's go ahead here inside of the content editable and let's give it a class name let's use a CN and let's give it default values of hFull, widthFull flex, itemsCenter, justifyCenter, textCenter textCenter dropShadowMD outlineNone and let's add dynamicFont.className there we go So now you can see how this font now looks like it looks like it's handwritten kind of. And now what I want to do is I want to define the color for this font. So let's give this a style. Let's give it a color. Let's check for fill.
If we have fill, we're going to use color to CSS and passing the fill option. Otherwise, we're going to use the default black option like this. There we go, so now it picked up the color from the last time and I can now change it to black, blue, yellow, green, red. Perfect. So now let's go ahead and kind of enlarge our font and what I want to do is I want to create a function which will increase the font size if I increase my rectangle, right?
So if you want to you can also revisit your selection tools for example And the same way we created the color picker, you can create the font picker or the font size picker. Right? You would create an equivalent action instead of set fill, you would create set font or set weight, whatever you want, right? And then you would simply look for that inside of, let me find the text component. You would extract that from the layer right here.
And of course, you would also in your types canvas allow that to be accepted inside of the text layer. So you would have to add the font and stuff like that just in case you're interested how would you go around doing that. Right let's focus on wrapping up the text component now so I'm gonna create this little function here it's not gonna be too complicated so just calculate font size is gonna accept width which is a number and height which is a number. We're gonna define the max font-size. So for me that's gonna be 96.
If you want to you don't even have to use the maximum font size, but I want to use that. Now let's add the scale factor. So this is going to be multiplied by the amount of our box. Now let's do const font-size based on height. It's going to be height times scale factor and let's do const font-size based on width that's going to be width times scale factor and let's return math.min so the smaller of the two values, font-size based on height, font-size based on width and max font-size.
So three arguments for our Math.min method. Like that. And now let's use the calculate font size in our style here. So font size, calculate font size, width and height of our layer. And there we go.
You can see how now it's much bigger. So if I want to, I can make it even bigger by expanding until it reaches a limit or I can make it smaller like that. And you can see how you can expand the text. Oh, it's gonna have more sense when we actually add the feature to, well, synchronize the text. So how about we implement that instead first?
So let's go ahead and let's create a method called update value. Const update value is gonna be use mutation from, do do we already have useMutation? We do. UseMutation from liveBlocksConfig. Alright.
So useMutation is going to go ahead and call this method here. We're gonna go ahead and extract the storage from the first argument here and we're gonna go ahead and extract the storage from the first argument here and we're gonna go ahead and extract the new value which is a string from the second argument here and we're gonna do const liveLayers to be storage.getLayers and then liveLayers.get using the ID of the layer which we have question mark in case we don't find it .set the value field is gonna be new value And we don't have to add anything inside of the dependency array because we are not depending on anything. And let's go ahead and create a handler. So const handleContentChange is going to be an event which looks for content editable event which we have imported above and simply call the above function updateValue with event.target.value There we go and let's now assign our content here so onChange is now gonna just simply call handleContentChange and this is going to be the value which we've extracted from the layer object right here above. There we go.
So that should already be working. So let me just demonstrate that. So if we go here and change this, there we go. It is preserved. Let me go in my another browser here.
So if I move this, that works. If I make it smaller, there we go. Let's refresh just to confirm this is all still working. I want to refresh here as well. There we go.
It is fully preserved. Amazing! And now we can easily reuse this and create our sticky note component. So luckily for us the note component is very very similar to the text component. So let me close everything here and let me copy the text component inside of the underscore components folder and let me rename it to note like this.
So let's go ahead and import note layer. Let's go ahead and let's assign note layer here. Let's rename this to note props. Let's make this be note props. This is also going to be a component called note like this.
And now what we are going to do is we are simply going to modify how the foreign object looks and acts like. So the outline can stay the same but what we are going to do is we're gonna pass the background color to be fill and use color to CSS with the fill option or it's going to use a default black color. Like that. And we're also going to add a class name here of Drop Shadow MD and sorry we're going to use Shadow MD and Drop Shadow Excel and inside of the content editable here let's go ahead and modify this a little bit as well so we no longer need the drop shadow MD on the text itself because we are having it on the entire on the entire foreign object. And now what we are going to do here is we are not going to use the fill color because now it's gonna be just black on black, right?
Because the background color is the same here. So instead what we're gonna do is we're gonna go ahead and create a util component called getContrastingTextColor. So let me just go ahead inside of my util here, lib utils. Let me go to the bottom here and let's go ahead and export function getContrastingTextColor. It's a function which accepts our color object and this was honestly done using chat gpt I don't know how it works but basically it gets the luminance that threshold values and it just returns the opposite color I guess So luminance is going to be 0.299 times color.r plus 0.587 times color.g plus 0.114 times color.b and then what we're gonna do is return if luminance value is higher than 228 the font is gonna be black otherwise it's gonna be white and surprisingly this works very very well So let's go inside of our note now and let's use get contrasting text color from libutils.
Like this. There we go. And we can leave this as this. And now we have to go back inside of the layer preview and let's copy and paste this case here. Let's make it for type note and let's render the note from dot slash note.
Like this. Let's try it out now. So if I go ahead and click on sticky note, There we go. You can see how this is a sticky note now. The only thing I don't like is the text initially looks too big and I just think that generally the text is too big for the sticky note.
So what we can do is we can go inside of the notes itself and we can just quickly modify our font size for the note. So I'm gonna leave the same max font size but I'm gonna reduce the scale factor to be 0.15. So this is gonna make it look more like a sticky note. There we go. Perfect.
So let's try this out like that and let me test it out right here. Perfect. So our sticky notes are officially working. I will refresh here and I will refresh here. There we go.
So we just wrapped up pretty much all of the elements which we need to work with and let's just try it out this. So if I change the color to black you can see that the font here has changed to white. If I change to red, it's white. If I change to yellow, it's black. Right, so that contrasting thing obviously works.
I'm not sure how but ChatGPT sometimes does really, really well. Great, so let's just confirm we can do all of those things and perfect. So now what I want to do next is and the last thing I want to do is my drawing board. Great, great job.