Skip to content

Commit

Permalink
Attempt of Fixing Conflict of Cursors
Browse files Browse the repository at this point in the history
  • Loading branch information
R1c4rdCo5t4 committed Jun 8, 2024
1 parent 4289157 commit 5ba0007
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 28 deletions.
2 changes: 1 addition & 1 deletion code/client/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const SERVER_URL = 'http://localhost:8080';
const SERVER_URL = 'https://notespace-0es7.onrender.com';

export default {
SERVER_URL,
Expand Down
7 changes: 3 additions & 4 deletions code/client/src/domain/editor/cursor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export type Cursor = {
line: number;
column: number;
};
import { Cursor as Cursor_ } from '@notespace/shared/src/document/types/cursor';

export type Cursor = Cursor_;

export type Selection = {
start: Cursor;
Expand Down
40 changes: 25 additions & 15 deletions code/client/src/domain/editor/fugue/Fugue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,10 @@ export class Fugue {
return values.map(value => {
const node = typeof value === 'string' ? nodeInsert(value, []) : value;
const operation = this.getInsertOperation({ line, column }, node);

this.addNode(operation);

if (node.value === '\n') line++;
column = node.value === '\n' ? 0 : column + 1;

return operation;
});
}
Expand All @@ -83,22 +81,21 @@ export class Fugue {
* @param cursor
* @param insertNode
*/
private getInsertOperation({ line, column }: Cursor, { value, styles }: NodeInsert) {
private getInsertOperation(cursor: Cursor, { value, styles }: NodeInsert): InsertOperation {
const id = { sender: this.replicaId, counter: this.counter++ };
const leftOrigin = this.getNodeByCursor({ line, column })!;
const leftOrigin = this.getNodeByCursor(cursor)!;
const parent = (
isEmpty(leftOrigin.rightChildren) ? leftOrigin : this.tree.getLeftmostDescendant(leftOrigin.rightChildren[0])
).id;
const operation: InsertOperation = {
return {
type: 'insert',
id,
value,
parent,
side: isEmpty(leftOrigin.rightChildren) ? 'R' : 'L',
styles,
cursor,
};
if (value === '\n') operation.line = line;
return operation;
}

/**
Expand All @@ -110,8 +107,8 @@ export class Fugue {
* @param side
* @param styles
*/
private addNode = ({ line, id, value, parent, side, styles }: InsertOperation) => {
if (value === '\n') this.tree.addLineRoot(line || 0, id, parent, side, styles);
private addNode = ({ cursor, id, value, parent, side, styles }: InsertOperation) => {
if (value === '\n') this.tree.addLineRoot(cursor.line || 0, id, parent, side, styles);
else this.tree.addNode(id, value, parent, side, styles || []);
};

Expand All @@ -121,7 +118,16 @@ export class Fugue {
*/
deleteLocal(selection: Selection): DeleteOperation[] {
const nodes = Array.from(this.traverseBySelection(selection));
return nodes.map(node => this.removeNode(node.id));
const cursor = selection.start;
return nodes.map(node => {
if (node.value === '\n') {
cursor.line++;
cursor.column = 0;
} else {
cursor.column++;
}
return this.removeNode(node.id, cursor);
});
}

/**
Expand All @@ -130,14 +136,17 @@ export class Fugue {
*/
deleteLocalByCursor(cursor: Cursor) {
const node = this.getNodeByCursor(cursor);
if (node) return this.deleteLocalById(node.id);
if (node) return this.deleteLocalById(cursor, node.id);
}

/**
* Deletes the node based on the given operation
* @param cursor
* @param ids
*/
deleteLocalById = (...ids: Id[]): DeleteOperation[] => ids.map(id => this.removeNode(id));
deleteLocalById = ({ line, column }: Cursor, ...ids: Id[]): DeleteOperation[] => {
return ids.map(id => this.removeNode(id, { line, column: column++ }));
};

/**
* Deletes the node based on the given operation
Expand All @@ -148,10 +157,11 @@ export class Fugue {
/**
* Deletes the node based on the given node id
* @param id
* @param cursor
*/
private removeNode(id: Id): DeleteOperation {
private removeNode(id: Id, cursor: Cursor): DeleteOperation {
this.tree.deleteNode(id);
return { type: 'delete', id };
return { type: 'delete', id, cursor };
}

/**
Expand Down Expand Up @@ -321,7 +331,7 @@ export class Fugue {
const iterator = this.traverseBySeparator(' ', cursor, reverse);
const nodes: FugueNode[] = iterator.next().value;
if (!nodes) return;
return this.deleteLocalById(...nodes.map(node => node.id));
return this.deleteLocalById(cursor, ...nodes.map(node => node.id));
}

/**
Expand Down
1 change: 1 addition & 0 deletions code/client/src/domain/editor/fugue/FugueTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export class FugueTree<T> {
while (i < siblings.length) if (!(id.sender > siblings[i++].sender)) break;

siblings.splice(i, 0, id);
siblings.sort((a, b) => a.sender.localeCompare(b.sender));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Fugue } from '@domain/editor/fugue/Fugue';
import { BlockStyle, InlineStyle } from '@notespace/shared/src/document/types/styles';
import { FugueNode } from '@domain/editor/fugue/types';
import { Selection } from '@domain/editor/cursor';
import { MarkdownDomainOperations } from '@domain/editor/fugue/operations/markdown/types';
import { deleteAroundSelection } from '@domain/editor/fugue/operations/markdown/utils';
import { Communication } from '@services/communication/communication';
import { Operation } from '@notespace/shared/src/document/types/operations';
import { isSelectionEmpty } from '@domain/editor/slate/utils/selection';
import { isEqual } from 'lodash';
import { Id } from '@notespace/shared/src/document/types/types';

/**
* Handlers for markdown operations
Expand All @@ -28,8 +28,8 @@ export default (fugue: Fugue, { socket }: Communication): MarkdownDomainOperatio
if (deleteTriggerNodes) {
const cursor = { line, column: 0 };
const nodes = Array.from(fugue.traverseBySeparator(' ', cursor, false, true));
const triggerNodes: FugueNode[] = nodes[0];
const deleteOperations = triggerNodes.map(node => fugue.deleteLocalById(node.id)).flat();
const idsToDelete: Id[] = nodes[0].map(node => node.id);
const deleteOperations = fugue.deleteLocalById(cursor, ...idsToDelete);
operations.push(...deleteOperations);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ export function deleteAroundSelection(selection: Selection, amount: number, fugu
if (!nodeBefore || !nodeAfter) break;
idsToDelete.push(nodeBefore.id, nodeAfter.id);
}
return idsToDelete.map(id => fugue.deleteLocalById(id)).flat();
return fugue.deleteLocalById(selection.start, ...idsToDelete);
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function Editor({ title, fugue, communication }: SlateEditorProps) {
}, [syncEditor]);

useHistory(editor, fugue, communication);
useEvents(fugueOperations, communication, syncEditor);
useEvents(editor, fugueOperations, communication, syncEditor);

return (
<div className="editor">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,60 @@ import useSocketListeners from '@services/communication/socket/useSocketListener
import { type Operation } from '@notespace/shared/src/document/types/operations';
import { Communication } from '@services/communication/communication';
import { FugueDomainOperations } from '@domain/editor/fugue/operations/fugue/types';
import { Editor, Transforms, Selection } from 'slate';
import { Cursor } from '@domain/editor/cursor';

/**
* Hook client socket listeners to events
* @param editor
* @param fugueOperations
* @param communication
* @param onDone
*/
function useEvents(fugueOperations: FugueDomainOperations, { socket }: Communication, onDone: () => void) {
function useEvents(
editor: Editor,
fugueOperations: FugueDomainOperations,
{ socket }: Communication,
onDone: () => void
) {
function onOperation(operations: Operation[]) {
console.log('operations', operations);
fugueOperations.applyOperations(operations);

operations.forEach((op: Operation) => {
if (['insert', 'delete', 'revive'].includes(op.type)) {
const { cursor } = op as Operation & { cursor: Cursor };
const currSelection = editor.selection;

if (currSelection) {
const { anchor, focus } = currSelection;
const newStart = { ...anchor };
const newEnd = { ...focus };

if (cursor.line === anchor.path[0]) {
if (op.type === 'insert') {
if (cursor.column <= anchor.offset) {
newStart.offset += op.value.length;
}
if (cursor.column <= focus.offset) {
newEnd.offset += op.value.length;
}
} else if (op.type === 'delete') {
if (cursor.column < anchor.offset) {
newStart.offset = Math.max(anchor.offset - 1, cursor.column);
}
if (cursor.column < focus.offset) {
newEnd.offset = Math.max(focus.offset - 1, cursor.column);
}
}
}
const newSelection: Selection = {
anchor: newStart,
focus: newEnd,
};
Transforms.select(editor, newSelection);
}
}
});
onDone();
}

Expand Down
4 changes: 4 additions & 0 deletions code/shared/src/document/types/cursor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type Cursor = {
line: number;
column: number;
};
5 changes: 4 additions & 1 deletion code/shared/src/document/types/operations.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { Id } from "./types";
import { InlineStyle, BlockStyle } from "./styles";
import { Cursor } from "./cursor";

export type InsertOperation = {
line?: number;
type: "insert";
id: Id;
value: string;
parent: Id;
side: "L" | "R";
cursor: Cursor;
styles?: InlineStyle[];
};

export type DeleteOperation = {
type: "delete";
id: Id;
cursor: Cursor;
};

export type InlineStyleOperation = {
Expand All @@ -33,6 +35,7 @@ export type BlockStyleOperation = {
export type ReviveOperation = {
type: "revive";
id: Id;
// cursor: Cursor;
};

export type Operation =
Expand Down

0 comments on commit 5ba0007

Please sign in to comment.