Hello together,
I have created a wireframe solution: https://mydraft.cc. It is my playground project where I test new stuff. Today I discovered yjs because Github suggested my tldraw (GitHub - tldraw/tldraw: a very good whiteboard) and I would like to give it a dry for learning purposes.
For my understanding I use a good shared type and then I subscribe to changes and update my local model. I am trying to figure out what a good type could be for my application. Right now the structure is as followed:
// Simplified
root: (UndoRedo)
current: (Editor)
page: (Document)
shape: (Shape)
So it is very nested. For undo / redo I leverage the immutability data structure, so my actions do not need to implement an undo operation. When I store a a document to the server I use the snapshot of the event a few changes ago and the last 20 applied actions (to make undo possible after loading a document). So it is very similar to snapshotting in event sourcing. I was hoping to implement yjs on top of my current structure to leverage immutability and redux features with the redux developer tools and everything that comes with that.
For my understanding the tldraw model is basically a simple map of records: (see TLRecord.ts)
So it maps naturally to Y.Map
. But I have no idea how to map a deep data yjs. I would also like to implement this on top of my existing solution and not integrate yjs
to deep into the system.
I have 2 ideas:
1. State as sequence of actions
I could store the state as sequence of actions and use that for synchronization. Not all actions are idempotent, through. For example undo
is not and need to be refactored to something like switchTo(snapshotId)
internally.
Furthermore the data structure would be very big because it would contain all changes from the past because I am not sure if we can implement something like snapshots.
2. Represent everything as map
This is basically the same idea as tlsdraw
. I could represent my document as simple map. Some of the keys would probably be fixed like cursor or the current document, some others not. We could store documents, shapes, user settings and everything into this map and use Ids to reference other items from the list.
Advantages
- This would then map easily to
Y.Map
. - The undo / redo operation can also be modelled, by calculating all changes records and transfer them. But for some operation this can be very big, e.g. when a big document has been deleted.
Disadvantages
- Because the actual structure is not flat, so we probably need complex resolvers to create some hierarchical structures again if they are needed later.
- Some operations are much more complex. For example: When you delete an document, you have to loop over all the shapes and also deleted them. Then you synchronize all changes.
- We can only edit whole objects. This could loose in data loss, because when multiple users edit user properties at the same time, this change would be lost.
Perhaps someone can guide a me a little bit with his or her experience.
3. Integrate yjs deeply into the system and build an object graph
According to the documentation, yjs primitives can be nested and therefore it should be possible to refactor the current object graph based on yjs. I am not sure if I understand how this would work with redux because the object graph would be mutable and many features in react and redux are based on immutability.