Design Philosophy
carefree-drawboard
🎨 made assumptions that:
- It is a painful process to spend time thinking and designing the layout of various UIs.
- Most user interactions can be abstracted into "send something to server" & "receive something to server".
- A completely decoupled system is what we dreamed of, because it can be easily customized, extended and maintained.
- An infinite drawboard is capable of building (almost) all the desired applications.
So we derived the following design principles:
- Everything is a plugin on an Infinite Drawboard.
- Plugin Styles should be fully declarative and should contain a smart layout system (which should also be declarative).
- Plugin Logics should only have access to the data from:
- Itself (e.g., some input fields declared by the Styles).
- The selecting
Node
(s).
Under these principles, no matter what programming languages (even JSON, if your plugins contain no logics) you are using, you can easily extend the functionality of carefree-drawboard
🎨 by writing plugins (see Extensibility for more details).
And since:
- We love
Python
.
So (currently) we provide a python binding, the cfdraw
package, for you to write plugins (both styles and logics) using Python
.
We'll dive into the details of these principles in the following sections.
Plugins
To see the design details of our plugins, see Design Details.
In order to achieve the principles, carefree-drawboard
🎨 designs a universal plugin paradigm which balanced functionality and flexibility. Specifically, our plugins will:
- Use declarative way to build itself. More specificly, EVERY plugin should be able to be constructed from a JSON data.
In this case, not only Python
, but also any programming languages can access to carefree-drawboard
🎨 (see Extensibility for more details)!
- Be presented on the drawboard 🎨 in the form of an icon button (or text button, if necessary). When we click on it, it will either trigger an event directly or expand a panel for further interactions.
Typically, the panel expanded here will be contain some input fields used to send some data to Python
.
- Control their own position and determine whether they need to be displayed based on
Pivot
andNode
. Here are two typical examples:
- For some "global" plugins (such as the "Settings" plugin), we will anchor it in a corner
Pivot
of the drawboard 🎨 (such as the upper right corner,rt
) and keep it displayed no matter what. - For some "specific" plugins (such as plugins that can perform image processing), we want it to be displayed only when an
ImageNode
is selected, and we want it tofollow
a corner of theImageNode
(such as the upper right corner,rt
).
See Plugin Positioning for more details.
- Have a Middleware system that can facilitate our use. In short, Middleware can convert our 'raw' results (e.g.,
str
,PIL.Image
, or list of them) into data structure we actually need.
Extensibility
carefree-drawboard
🎨 is designed to be extensible. It should be easy to migrate from Python
to other (backend) programming languages, because all communications between frontend and backend are based on a static set of APIs.
In order to guide you to write your own plugins with your favorite programming languages, we'll first show you the design details of our plugins (see Design Details), and then we'll give you some references (see References).
Design Details
In this section, we will introduce the design details of the plugin system, so you can know about why we can write plugins from backend side.
- The backend should wrap the Styles and Logics into a single class / struct.
- The Styles should be JSON serializable.
- The Logics should be asynchronous functions that are responsible for sending intermediate progress / final results to the frontend.
- The backend should then 'register' this class / struct to a global 'factory' (e.g., our
PluginFactory
).- You should assign the class / struct itself, not an instance of it. Instances should be created on the fly, so we don't suffer from data sharing issues.
- You should assign a unique
identifier
when registering.
- The backend should launch a WebSocket server, and a long-lasting connection will be established from the frontend to the backend. This connection will do the following things:
- The frontend will send a one-time 'sync' request to the backend, and the backend should use the 'factory' to generate a JSON data (e.g., with our
to_react
method) which tells the frontend how to render the plugins on the drawboard 🎨. - The frontend will send a task request to the backend once users interact with the plugins. The task request is statically typed (
ISocketRequest
) and will contain theidentifier
of the plugin, so the backend knows which plugin's Logics should be executed. - During the execution, the backend will send intermediate progress / final results messages to the frontend. The messages should follow specific interface (
ISocketMessage
), so it might be better to:- Introduce a Middleware system.
- Introduce some Built-in Methods.
- The frontend will then render the progress / results on the drawboard 🎨.
- The frontend will send a one-time 'sync' request to the backend, and the backend should use the 'factory' to generate a JSON data (e.g., with our
References
The OpenAPI specification can be found at
http://localhost:8123/openapi.json
, if you start this project locally at8123
port.The websocket source codes and the
SyncSocketPlugin
source codes. You can also see Sync for a brief introduction of the underlying mechanism.The plugins are designed to use:
ISocketRequest
as input messageISocketMessage
as response message
to communicate between frontend and backend through an everlasting WebSocket connection, so once your backend can receive / send such (JSON) data structure from / to the frontend it should be all fine.
Sync
One of the main difficulties of building Python
(or, other backend programming languages) based web frameworks is the communication between frontend and backend. carefree-drawboard
🎨 tackles this problem by using WebSocket to communicate:
- The frontend will maintain a WebSocket connection to the backend, and poll for the settings. If the backend is dead, the frontend will also poll until the backend is alive again.
- Once the settings are fetched, the frontend will check the hash of the settings. If the hash is different from the current one, the frontend will update the settings and re-render the drawboard 🎨.
I have to admit: Until 2023-04-15, I was using file system for the communication. It can start up fast but it was a terrible development experience.