Click here to see the project live.
Whiteboard Playground is an interactive project designed to explore the capabilities of state machines and the Canvas API for drawing and manipulating shapes on a virtual whiteboard.
- Draw Shapes: Users can draw a variety of shapes on the whiteboard.
- Select Shapes: Allows for the selection of individual or multiple shapes to perform further actions.
- Delete Shapes: Users can delete selected shapes from the whiteboard.
- Resize Shapes: Provides functionality to resize selected shapes according to user preference.
- Drag Shapes: Enables moving shapes around the whiteboard to organize or restructure content.
- Multi-Select and Multi-Drag: Users can select and simultaneously move multiple shapes.
- State Machines: Utilized to manage the complex states of the application efficiently.
- Canvas API: Used for rendering and manipulating the graphical content on the whiteboard.
This file contains a collection of type guard functions — functions that perform runtime checks to ensure that a value conforms to a specific type. They are useful for narrowing down types in TypeScript, providing more type safety and reducing the likelihood of runtime errors. Examples include checks for null
, undefined
, strings, arrays, and more.
This file defines various utility types that are used throughout the project. These types help in creating more expressive and flexible type definitions. Examples include Nil
(a union of null
and undefined
), Undefinable
, Nullable
, Nilable
, and NonEmptyArray
. These utility types simplify handling optional and nullable values, as well as ensuring non-empty arrays.
This project also makes use of match
from ts-pattern
to perform
exhaustive pattern matching. This is useful for ensuring that all
cases are handled in a switch statement. Additionally, unlike switch statements, which are not expressions, match
from ts-pattern
is an expression. This means it can directly return values, making it more versatile.
The project is organized into several key directories, each serving a specific purpose in the application architecture:
the architecture is inspired by Onion Architecture that is designed to enforce dependency inversion. The idea is that the outer layers are aware of the inner layers and the inner layers are not aware of the outer layers. This is a way to make the application more maintainable and scalable. So in other words — the ui layer is aware of the application layer and the application layer is not aware of the ui layer.
The following ruleset could be put in place and enforced by ESlint to help maintain this principle so that the pattern remains consistent as this project begins to scale over time.
-
Anything within a given
/context
can not import from another/context
directory.- if something needs to be used within multiple
/context
directories and it's currently only scoped to the/context/whiteboard
project, it should be moved into and imported from/shared
.
- if something needs to be used within multiple
-
Within a given
/context
(or within the/shared
) directory, the following direction of imports should be followed: (ESlint could help remind us)
/domain # domain can not import from application or infrastructure
👆
/application # application can import from domain but can not import from infrastructure
👆
/infrastructure # infrastructure can import from both domain and application
👆
/ui # ui can import from domain, application and infrastructure
-
domain: This directory contains all the types and constants used throughout the project. It serves as the central definition point for the data structures and fixed values.
-
application: This folder houses the business logic of the application. It is responsible for handling the operations and rules that define the core functionalities of the Whiteboard Playground.
-
infrastructure: Located here is the state machine logic. This directory is crucial for managing the various states of the application, ensuring smooth transitions and robust state handling.
-
ui: This directory is where the React components are developed. These components leverage the definitions from the domain, the business logic from the application, and the state management provided by the infrastructure to build a cohesive and interactive user interface.
First, run the development server:
npm run dev
Open http:https://localhost:3000 to see the project running locally.