Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add real time collaboration support #32

Merged
merged 55 commits into from
Aug 16, 2021
Merged

Add real time collaboration support #32

merged 55 commits into from
Aug 16, 2021

Conversation

mirka
Copy link
Member

@mirka mirka commented Jul 19, 2021

Proposed changes

  • Adds a useYjs React hook that adds real time collaborative editing features, powered by the Yjs-based AsBlocks. These features will only kick in when enabled and given a transport module via settings.collab.
  • For local Storybook testing, we use a mock transport module that passes messages via the browser's Local Storage.
  • Since "smart" undo (i.e. only undoing the user's own changes) is not implemented yet, undo is disabled while there are peers in the editor. This is to avoid data loss and user confusion.

Diagram of useYjs

TODO

  • commit peer edits to undo history
  • figure out reasonable logic to disable undo when it could delete peer edits
  • share presence and caret positions

Punted

  • Smart undo functionality that only undos the user's own changes. (I think we need to track change origins in transactions in AsBlocks. Related upstream issue: Add undo/redo youknowriad/asblocks#9)
  • Making sure the caret labels are always positioned within the bounds of the editor.

Testing instructions

  1. yarn install && yarn storybook to open a Storybook story for the collaboration-enabled editor.
  2. To see debug event messages, open devtools console and enter localStorage.debug = 'iso-editor:collab'.

Open two or more browser tabs with this same Storybook page, and see the edits sync across all the clients.

@mirka mirka self-assigned this Jul 19, 2021
mirka added 17 commits July 21, 2021 08:11
This makes the timings more realistic
Debounces `input` events without relying solely on the `change` event.

The `change` event isn't fired in a consistent manner. The expected behavior is for it to fire only on the trailing edge of the debounce, but sometimes it fires only on the leading edge. When this happens, the change will never be communicated to the handlers until another change happens.
@mirka
Copy link
Member Author

mirka commented Aug 10, 2021

@youknowriad I’m currently doing direct imports of three AsBlocks functions here.

Do you think it would make sense to:

  • Add version tags to AsBlocks or publish it to npm.
  • Make createDocument available as an “official” export.
  • Make updatePostDoc and postDocToObject included by default in createDocument (so they don’t have to be passed in if there’s no need to override).

@mirka mirka changed the title [WIP] Add real time collaboration support Add real time collaboration support Aug 10, 2021
# Conflicts:
#	package.json
#	yarn.lock
@mirka mirka marked this pull request as ready for review August 10, 2021 15:04
@mirka mirka requested review from johngodley and a team August 10, 2021 15:04
@youknowriad
Copy link
Contributor

@mirka To be honest, I think it's better to just copy/paste the code here. asblocks is an app on its own and not really meant to be "reused" as is. And to avoid future diversions to break anything here, I think it's better to just copy this code here. I also don't think it's too much code. WDYT?

@johngodley
Copy link
Member

to avoid future diversions to break anything here, I think it's better to just copy this code here

Yeah, I'd go along with that too. We can see how things develop here and revisit it in the future if it makes sense.

@johngodley
Copy link
Member

I'm excited to see the potential for this, and it'll be interesting to think how it could be used in other places.

Could you add something to the readme explaining how it might be used? I'm curious as to how peers are found.

I am having a few troubles getting it running though. Running yarn storybook shows a lot of these:

Though the "loose" option was set to "false" in your @babel/preset-env config, it will not be used for @babel/plugin-proposal-private-property-in-object since the "loose" mode option was set to "true" for @babel/plugin-proposal-private-methods. The "loose" option must be the same for @babel/plugin-proposal-class-properties, @babel/plugin-proposal-private-methods and @babel/plugin-proposal-private-property-in-object (when they are enabled): you can silence this warning by explicitly adding ["@babel/plugin-proposal-private-property-in-object", { "loose": true }]

Followed by:

ModuleNotFoundError: Module not found: Error: Can't resolve '@emotion/styled/base' in 'node_modules/@wordpress/components/build-module/focal-point-picker/styles'

There are also a couple of lint errors when building:

src/components/block-editor-contents/use-yjs.js:98:5 - error TS2322: Type 'string | undefined' is not assignable to type 'string'.
  Type 'undefined' is not assignable to type 'string'.

98     color: settings.caretColor || sample( defaultColors ),
       ~~~~~

  src/index.js:118:4
    118  * @property {string} user.color Color of the caret indicator displayed to peers.
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    119  * @property {(message: object) => void} onReceiveMessage Callback to run when a message is received.
        ~~~
    The expected type comes from property 'color' which is declared here on type '{ identity: string; name: string; color: string; }'

When I tried to use this in an external project (using wp-scripts) I got a parse error from node_modules/asblocks/src/components/editor/sync/algorithms/yjs.js

order = order.get( clientId )?.toArray();

@mirka
Copy link
Member Author

mirka commented Aug 11, 2021

@youknowriad

I think it's better to just copy/paste the code here.

Sure, that works for me. Thanks!


@johngodley

Could you add something to the readme explaining how it might be used? I'm curious as to how peers are found.

Yes, I'll add a readme for the transport module interface, it only has basic JSDoc descriptions right now. Short answer is that the code here in iso-editor does not handle peer discovery — it just waits to be notified when peers change. The consumer will have to provide a transport module that talks to their server, whether that be via WebSocket, WebRTC, or even Local Storage, like in the Storybook demo.

I addressed the build and lint issues, sorry about that!

  • "loose" option was set to "false" — There are only two of these left now, but I'm going to leave them as is to be fixed upstream in Storybook
  • Can't resolve '@emotion/styled/base' — Added workaround
  • ✅ couple of lint errors when building — Fixed
  • ✅ parse error from node_modules/asblocks/src/components/editor/sync/algorithms/yjs.js — No longer an issue

@naxoc
Copy link
Member

naxoc commented Aug 12, 2021

For an in-depth code review I think a lot of people would be better at that than me, but I did try my very best to break this by playing around with it and I was not successful. It works really well. Great work Lena!

@johngodley
Copy link
Member

Thanks, it's all working great now!

I wasn't able to break anything, either in the storybook, or when the isolated editor is used elsewhere. Everything looks great.

A minor thing, and maybe not important, but if there is a background colour the last character from a peer is black:

image

Typing (or randomly mashing the keyboard) does seem a bit slower than a plain editor, but it's possible this is some interaction with the storybook system itself. Would really need to try it in P2 itself.

Enabling the block inspector in the storybook would allow further testing, but it's not a blocker.

Bundled file sizes do seem to increase quite dramatically. The P2 Editor goes from 84KB to 167KB, for example. This is maybe ok for P2 as we want to use collaboration there, but for the other clients it's a big change.

Possibly the code could be exported as a component or a hook (useCollaboration( IsolatedBlockEditor, transport ) etc), and then Webpack might be able to tree shake it away.

I don't want to slow things down so I'm ok with this merging as-is, and then looping back to optimize later.

Short answer is that the code here in iso-editor does not handle peer discovery

Ah, that makes sense, thanks.

@mirka
Copy link
Member Author

mirka commented Aug 13, 2021

Thanks for reviewing & testing!

  • ✅ Could you add something to the readme — Added
  • ✅ there is a background colour the last character from a peer is black — Fixed
  • ✅ Enabling the block inspector in the storybook would allow further testing — Enabled

Bundled file sizes do seem to increase quite dramatically. The P2 Editor goes from 84KB to 167KB, for example. This is maybe ok for P2 as we want to use collaboration there, but for the other clients it's a big change.

You’re right, that’s about 26KB vs. 51KB gzipped, mainly the size of the Yjs library. Given that most consumers will likely not use the collab feature, I agree we should keep it tree shakeable. I’ll take note of this as a follow-up task.

@johngodley
Copy link
Member

Super stuff, a 👍 from me. I can push the changes out to npm when you're ready.

@mirka mirka mentioned this pull request Aug 16, 2021
@mirka mirka merged commit 2eaba00 into trunk Aug 16, 2021
@mirka mirka deleted the add/yjs branch August 16, 2021 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants