28 | Practical combat (3): How to design a "drawing" program?

After the previous two lessons, our drawing program is basically practical. It has the following functions:

You can select global graphic styles (lineWidth, lineColor, fillColor);

Various graphics (Path, FreePath, Line, Rect, Ellipse, Circle) can be created with global graphics styles;

You can select a graphic that has been created and modify its graphic style;

Selected graphics can be deleted;

Selected graphics can be moved.

There was feedback from some students earlier, and I would like to answer it here.

One feedback was about the use of JavaScript and why I use the class keyword.

This is because I don’t really want this to be a tutorial on a certain language. What I chose is how to express the program logic in an expression that is closest to everyone’s thinking. Even if you have not systematically learned JavaScript, you should be able to understand this program. What do you want to do.

Another piece of feedback is that I hope that I will not start with the MVC model, but if there is no MVC and we use the most basic naked code, what kind of program will we write and what are the disadvantages in it? Therefore, MVC is introduced to make the program architecture clearer and decouple functions.

I think this opinion is quite pertinent. We will add a bit later to write the MVP version of the drawing program naked.

Today we start the third lecture of "Practical Combat: How to Design a 'Drawing' Program" and how to connect to the server.

Considering that everyone’s general feedback is that the content is a bit deep, we divided the server connection into two lessons to discuss. In today’s lecture we are talking about persistence on the browser side.

Why do you need persistence on the browser side?

Because we need to have a better user experience. When the user is disconnected from the Internet, this drawing program can still edit normally, and when the Internet is restored, it needs to be able to automatically synchronize all offline edited content to the server.

Based on the introductions in the previous lectures, you may immediately think of PWA promoted by Google, which pays great attention to the offline experience of browser applications.

But when we make a technology selection, obviously the first thing to consider is the compatibility of the technology. Today we do not do this based on PWA, but on the more traditional localStorage technology.

The specific code we changed is as follows:

https://github.com/qiniu/qpaint/compare/v27...v28

The core change is the Model layer. The complete model layer code for offline support is as follows:

dom.js

Object ID

In order to support persistence, we introduced two IDs to the QPaintDoc class, the root of the DOM tree of each Model layer, as follows:

localID: string

displayID: string

Among them, displayID, as the name suggests, is the ID visible to the user. The previous local debugging behavior of our painting program was to open http://localhost:8888/ to edit a document (QPaintDoc), but now it will automatically jump to http://localhost:8888/#t10001 or a similar URL. Here t10001 is the displayID of the document.

Among them, displayID starts with t, which means that this document has never been synchronized with the server since it was created, and it is a temporary document. Once it completes synchronization with the server, it uses the document ID returned by the server instead.

So, what is localID? As the name suggests, it is the local ID of this document. When the document has not been synchronized with the server, it is related to displayID. If displayID is t10001, then localID is 10001. However, after the document is saved to the server for the first time, its displayID will change, but the localID will not change.

What are the benefits of this?

The advantage is that when we store the DOM tree in localStorage, we do not save the entire document in JSON, but in layers. The shapes array in QPaintDoc only saves the shapeID.

Yes, each Shape also introduces an ID. In this way, when the Shape changes, such as modifying the graphic style or moving, we modify shapeID => shapeJsonData.

Please note that in the browser's localStorage, shapeID must be globally unique. What we actually store is QPaintDoc.localID + ":" + shape.id.

Seeing this, we can look back and understand why QPaintDoc has displayID and localID. If there is only one ID and this ID changes, then when the ID changes, all the graphical object shapeID => shapeJsonData data of this document stored in localStorage need to change accordingly.

The introduction of localID means that once QPaintDoc is initialized (QPaintDoc.init method), the ID will be fixed. You only need to ensure that it is unique under the same browser.

Therefore, the first time we visit http://localhost:8888/, the automatic jump is http://localhost:8888/#t10001, and the second time we visit, the automatic jump is http://localhost:8888/#t10002. . This is because we will not make two QPaintDoc.localIDs the same under the same browser.

Data changes

We divide data changes into two levels:

shapeChanged

documentChanged

Under what circumstances is shapeChanged called? There are three types:

Add a shape (addShape), and this new shape has shapeChanged;

Modify the graphic style (setProp) of a shape, and the modified shape is shapeChanged;

Move the position of a shape (move), and the shape whose position changes occurs shapeChanged.

Under what circumstances does documentChanged occur? There are two types:

Adding a shape (addShape) will cause the number of shapes in the document to increase by one and documentChanged occurs;

Deleting a shape (deleteShape) will cause the number of shapes in the document to be reduced by one and documentChanged will occur.

Of course, in the foreseeable future, we will support different shape exchange orders (changing Z-Order). At this time, although the number of shapes in the document remains unchanged, the contents of the shapes array still changes, and documentChanged occurs.

What to do when data changes occur?

On shapeChanged, update shapeID => shapeJsonData data in localStorage. On documentChanged, update localID => documentJsonData data.

From the perspective of future expectations, data changes will not only occur during user interaction. Consider the scenario where multiple people are editing a document at the same time. Data change messages will also come from changes in other browsers. The specific process is:

Client B operation => Client B's DOM change => Server-side data change => Client A receives data change => Client A's DOM change => Client A's View update

In the previous lectures 26 and 27, we did not introduce data change events. Instead, after the Controller changed the data, it actively called qview.invalidateRect to notify the View layer to redraw. This is relatively simple, although it does not conform to the standard MVC architecture. Because from the MVC architecture, interface updates are not triggered by the Controller, but should be triggered by the DataChanged event of the Model layer.

Storage capacity limits and security

The storage capacity of localStorage is limited, and different browsers vary. Most of them are at the 5-10M level. Under the same browser, multiple QPaintDoc data will be saved in localStorage at the same time.

This means that as time goes by, the storage space occupied by localStorage will become larger and larger, so we need to consider the data cleaning mechanism.

Currently, we use the localStorage_setItem function to uniformly take over localStorage.setItem calls. Once a QuotaExceededError exception occurs in setItem, indicating that the localStorage space is full, we will eliminate the furthest created document.

This way, we won't be unable to save because localStorage is too full. As long as we synchronize documents online in a timely manner, the data will not be lost.

The final topic is safety.

Since we have saved the data in localStorage, as long as the user opens the browser, they can view the localStorage data through specific means.

This means that if there is sensitive data in the document, it can be perceived by humans. Especially if our drawing program supports multi-tenancy in the future, when multiple user accounts log in and out under the same browser, the documents of multiple users will be visible in the same localStorage.

This means that after you log out of your account, other people using this browser can still see your data. There is a risk of privacy leakage.

The easiest way to solve this problem is to clear all documents in localStorage when the user account logs out.

Conclusion

Today we start to consider the server connection of the "Paint" program. In today's lecture, we will first make the persistence of the local browser storage of the drawing program so that it can be better offline.

Programs that support offline persistent storage will look very different. Today we talked about the difference between DOM tree storage in JavaScript memory and localStorage in conjunction with the drawing program. In order to support the granularity of updating data, the entire document is not saved every time, and the storage is divided into two levels: shape and document. Correspondingly, our data update events are also divided into two levels: shapeChanged and documentChanged.

Guess you like

Origin blog.csdn.net/qq_37756660/article/details/134971291