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

Modify Snapshot Event to publish updated local changes #923

Merged
merged 10 commits into from
Nov 7, 2024
51 changes: 44 additions & 7 deletions examples/vanilla-codemirror6/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
/* eslint-disable jsdoc/require-jsdoc */
import yorkie, { DocEventType } from 'yorkie-js-sdk';
import type { TextOperationInfo, EditOpInfo } from 'yorkie-js-sdk';
import type { EditOpInfo, OperationInfo } from 'yorkie-js-sdk';
import { basicSetup, EditorView } from 'codemirror';
import { keymap } from '@codemirror/view';
import {
markdown,
markdownKeymap,
markdownLanguage,
} from '@codemirror/lang-markdown';
import { Transaction } from '@codemirror/state';
import { Transaction, TransactionSpec } from '@codemirror/state';
import { network } from './network';
import { displayLog, displayPeers } from './utils';
import { YorkieDoc } from './type';
import { YorkieDoc, YorkiePresence } from './type';
import './style.css';

const editorParentElem = document.getElementById('editor')!;
Expand All @@ -28,7 +28,7 @@ async function main() {
await client.activate();

// 02-1. create a document then attach it into the client.
const doc = new yorkie.Document<YorkieDoc>(
const doc = new yorkie.Document<YorkieDoc, YorkiePresence>(
`codemirror6-${new Date()
.toISOString()
.substring(0, 10)
Expand All @@ -55,10 +55,21 @@ async function main() {
// 02-2. subscribe document event.
const syncText = () => {
const text = doc.getRoot().content;
view.dispatch({
const selection = doc.getMyPresence().selection;
const transactionSpec: TransactionSpec = {
changes: { from: 0, to: view.state.doc.length, insert: text.toString() },
annotations: [Transaction.remote.of(true)],
});
};

if (selection) {
// Restore the cursor position when the text is replaced.
const cursor = text.posRangeToIndexRange(selection);
transactionSpec['selection'] = {
anchor: cursor[0],
head: cursor[1],
};
}
view.dispatch(transactionSpec);
};
doc.subscribe((event) => {
if (event.type === 'snapshot') {
Expand Down Expand Up @@ -98,6 +109,32 @@ async function main() {
});
}
}

const hasFocus =
viewUpdate.view.hasFocus && viewUpdate.view.dom.ownerDocument.hasFocus();
const sel = hasFocus ? viewUpdate.state.selection.main : null;

doc.update((root, presence) => {
if (sel && root.content) {
const selection = root.content.indexRangeToPosRange([
sel.anchor,
sel.head,
]);

if (
JSON.stringify(selection) !==
JSON.stringify(presence.get('selection'))
) {
presence.set({
selection,
});
}
} else if (presence.get('selection')) {
presence.set({
selection: undefined,
});
}
});
});

// 03-2. create codemirror instance
Expand All @@ -113,7 +150,7 @@ async function main() {
});

// 03-3. define event handler that apply remote changes to local
function handleOperations(operations: Array<TextOperationInfo>) {
function handleOperations(operations: Array<OperationInfo>) {
for (const op of operations) {
if (op.type === 'edit') {
handleEditOp(op);
Expand Down
6 changes: 5 additions & 1 deletion examples/vanilla-codemirror6/src/type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { type Text } from 'yorkie-js-sdk';
import { TextPosStructRange, type Text } from 'yorkie-js-sdk';

export type YorkieDoc = {
content: Text;
};

export type YorkiePresence = {
selection?: TextPosStructRange;
};
37 changes: 19 additions & 18 deletions packages/sdk/src/document/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1180,11 +1180,26 @@ export class Document<T, P extends Indexable = Indexable> {
}

// NOTE(hackerwins): If the document has local changes, we need to apply
// them after applying the snapshot. We need to treat the local changes
// as remote changes because the application should apply the local
// changes to their own document.
// them after applying the snapshot, as local changes are not included in the snapshot data.
// Afterward, we should publish a snapshot event with the latest
// version of the document to ensure the user receives the most up-to-date snapshot.
if (hasSnapshot) {
this.applyChanges(this.localChanges, OpSource.Remote);
this.applyChanges(this.localChanges, OpSource.Local);
this.publish([
{
type: DocEventType.Snapshot,
source: OpSource.Remote,
value: {
serverSeq: pack.getCheckpoint().getServerSeq().toString(),
snapshot: this.isEnableDevtools()
? converter.bytesToHex(pack.getSnapshot()!)
: undefined,
snapshotVector: converter.versionVectorToHex(
pack.getVersionVector()!,
),
},
},
]);
devleejb marked this conversation as resolved.
Show resolved Hide resolved
}

// 03. Update the checkpoint.
Expand Down Expand Up @@ -1420,20 +1435,6 @@ export class Document<T, P extends Indexable = Indexable> {

// drop clone because it is contaminated.
this.clone = undefined;

this.publish([
{
type: DocEventType.Snapshot,
source: OpSource.Remote,
value: {
serverSeq: serverSeq.toString(),
snapshot: this.isEnableDevtools()
? converter.bytesToHex(snapshot)
: undefined,
snapshotVector: converter.versionVectorToHex(snapshotVector),
},
},
]);
}

/**
Expand Down
Loading