Skip to content

Commit

Permalink
Merge pull request #25 from NoteSpaceTeam/dev
Browse files Browse the repository at this point in the history
Dev merge
  • Loading branch information
R1c4rdCo5t4 committed May 6, 2024
2 parents 924f71a + 206a8ae commit 46e9a6f
Show file tree
Hide file tree
Showing 29 changed files with 188 additions and 215 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
pnpm-lock.yaml
*.env
coverage
/code/client/bun.lockb
12 changes: 6 additions & 6 deletions code/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@emotion/styled": "^11.11.5",
"@mui/material": "^5.15.16",
"@notespace/shared": "file:..\\shared",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/jest-dom": "^6.4.5",
"dotenv": "^16.4.5",
"eslint-plugin-playwright": "^1.6.0",
"lodash": "^4.17.21",
Expand All @@ -41,7 +41,7 @@
"@testing-library/dom": "^10.1.0",
"@testing-library/react": "^15.0.6",
"@testing-library/user-event": "^14.5.2",
"@types/lodash": "^4.17.0",
"@types/lodash": "^4.17.1",
"@types/node": "^20.12.8",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
Expand All @@ -51,8 +51,8 @@
"@typescript-eslint/parser": "^7.8.0",
"@vite-pwa/assets-generator": "^0.2.4",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^1.5.3",
"@vitest/ui": "^1.5.3",
"@vitest/coverage-v8": "^1.6.0",
"@vitest/ui": "^1.6.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
Expand All @@ -61,14 +61,14 @@
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.6",
"jsdom": "^24.0.0",
"knip": "^5.11.0",
"knip": "^5.12.2",
"prettier": "^3.2.5",
"sass": "^1.76.0",
"typescript": "^5.4.5",
"vite": "^5.2.11",
"vite-plugin-qrcode": "^0.2.3",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.5.3"
"vitest": "^1.6.0"
},
"packageManager": "[email protected]+sha256.0624e30eff866cdeb363b15061bdb7fd9425b17bc1bb42c22f5f4efdea21f6b3"
}
68 changes: 39 additions & 29 deletions code/client/src/domain/editor/crdt/fugue.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Id, Nodes } from '@notespace/shared/crdt/types/nodes';
import { type Id } from '@notespace/shared/crdt/types/nodes';
import { BlockStyle, InlineStyle } from '@notespace/shared/types/styles';
import { FugueTree } from '@notespace/shared/crdt/FugueTree';
import { generateReplicaId, nodeInsert } from './utils';
Expand All @@ -10,6 +10,7 @@ import {
DeleteOperation,
InlineStyleOperation,
InsertOperation,
Operation,
ReviveOperation,
} from '@notespace/shared/crdt/types/operations';

Expand All @@ -27,12 +28,28 @@ export class Fugue {
this.tree = new FugueTree();
}

/**
* Builds the tree from the given nodes map.
* @param nodes
*/
init(nodes: Nodes<string>): void {
this.tree.setTree(nodes);
applyOperations(operations: Operation[]) {
for (const operation of operations) {
switch (operation.type) {
case 'insert':
this.insertRemote(operation);
break;
case 'delete':
this.deleteRemote(operation);
break;
case 'inline-style':
this.updateInlineStyleRemote(operation);
break;
case 'block-style':
this.updateBlockStyleRemote(operation);
break;
case 'revive':
this.reviveRemote(operation);
break;
default:
throw new Error('Invalid operation type');
}
}
}

/**
Expand Down Expand Up @@ -69,8 +86,10 @@ export class Fugue {
*/
private getInsertOperation({ line, column }: Cursor, { value, styles }: NodeInsert): InsertOperation {
const id = { sender: this.replicaId, counter: this.counter++ };
const lineNode = line === 0 ? this.tree.root : this.findNode('\n', line);

const lineNode = this.tree.getLineRoot(line);
const leftOrigin = column === 0 ? lineNode : this.getNodeByCursor({ line, column })!;

if (isEmpty(leftOrigin.rightChildren)) {
return { type: 'insert', id, value, parent: leftOrigin.id, side: 'R', styles };
}
Expand All @@ -87,7 +106,11 @@ export class Fugue {
* @param styles
*/
private addNode({ id, value, parent, side, styles }: InsertOperation) {
this.tree.addNode(id, value, parent, side, styles);
if (value === '\n') {
this.tree.addLineRoot(id, value, parent, side, styles);
} else {
this.tree.addNode(id, value, parent, side, styles);
}
}

/**
Expand All @@ -105,7 +128,7 @@ export class Fugue {
*/
deleteLocalByCursor(cursor: Cursor) {
const node =
cursor.line > 0 && cursor.column === 0 ? this.findNode('\n', cursor.line - 1) : this.getNodeByCursor(cursor);
cursor.line > 0 && cursor.column === 0 ? this.tree.getLineRoot(cursor.line) : this.getNodeByCursor(cursor);

if (node) return this.deleteLocalById(node.id);
}
Expand Down Expand Up @@ -148,7 +171,7 @@ export class Fugue {
*/
reviveLocalByCursor(cursor: Cursor) {
const node =
cursor.line > 0 && cursor.column === 0 ? this.findNode('\n', cursor.line - 1) : this.getNodeByCursor(cursor);
cursor.line > 0 && cursor.column === 0 ? this.tree.getLineRoot(cursor.line) : this.getNodeByCursor(cursor);

if (node) return this.reviveNode(node.id);
}
Expand Down Expand Up @@ -254,10 +277,13 @@ export class Fugue {
*/
*traverseBySelection(selection: Selection, returnDeleted: boolean = false): IterableIterator<FugueNode> {
const { start, end } = selection;
let lineCounter = 0,
let lineCounter = start.line,
columnCounter = 0,
inBounds = false;
for (const node of this.traverseTree(returnDeleted)) {

const lineRootNode = this.tree.getLineRoot(start.line);

for (const node of this.tree.traverse(lineRootNode, returnDeleted)) {
// start condition
if (lineCounter === start.line && columnCounter === start.column) {
inBounds = true;
Expand Down Expand Up @@ -330,22 +356,6 @@ export class Fugue {
return iterator.next().value;
}

/**
* Finds the node skip-th node with the given value
* @param value
* @param skip
*/
private findNode(value: string, skip: number): FugueNode {
let lastMatch = this.tree.root;
for (const node of this.traverseTree()) {
if (node.value === value) {
lastMatch = node;
if (--skip === 0) return lastMatch;
}
}
return lastMatch;
}

/**
* Returns the string representation of the tree.
*/
Expand Down
4 changes: 2 additions & 2 deletions code/client/src/domain/editor/crdt/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { type InlineStyle } from '@notespace/shared/types/styles';
import { Node } from '@notespace/shared/crdt/types/nodes';
import { NodeType } from '@notespace/shared/crdt/types/nodes';

export type NodeInsert = {
value: string;
styles: InlineStyle[];
};

export type FugueNode = Node<string>;
export type FugueNode = NodeType<string>;
31 changes: 1 addition & 30 deletions code/client/src/domain/editor/operations/fugue/operations.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,8 @@
import { Operation } from '@notespace/shared/crdt/types/operations';
import { Fugue } from '@/domain/editor/crdt/fugue';
import { Document } from '@notespace/shared/crdt/types/document';
import { FugueDomainOperations } from '@/domain/editor/operations/fugue/types';

export default (fugue: Fugue): FugueDomainOperations => {
function applyOperations(operations: Operation[]) {
for (const operation of operations) {
switch (operation.type) {
case 'insert':
fugue.insertRemote(operation);
break;
case 'delete':
fugue.deleteRemote(operation);
break;
case 'inline-style':
fugue.updateInlineStyleRemote(operation);
break;
case 'block-style':
fugue.updateBlockStyleRemote(operation);
break;
case 'revive':
fugue.reviveRemote(operation);
break;
default:
throw new Error('Invalid operation type');
}
}
}

const initDocument = ({ nodes }: Document) => fugue.init(nodes);

return {
applyOperations,
initDocument,
applyOperations: operations => fugue.applyOperations(operations),
};
};
2 changes: 0 additions & 2 deletions code/client/src/domain/editor/operations/fugue/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Operation } from '@notespace/shared/crdt/types/operations';
import { Document } from '@notespace/shared/crdt/types/document';

export type FugueDomainOperations = {
applyOperations: (operations: Operation[]) => void;
initDocument: (document: Document) => void;
};
4 changes: 2 additions & 2 deletions code/client/src/services/documentServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { HttpCommunication } from '@domain/communication/http/httpCommunication'
import { Document } from '@notespace/shared/crdt/types/document';

async function getDocument(http: HttpCommunication, id: string): Promise<Document> {
const { nodes, title } = await http.get(`/documents/${id}`);
return { nodes, title } as Document;
const { operations, title } = await http.get(`/documents/${id}`);
return { operations, title } as Document;
}

async function createDocument(http: HttpCommunication): Promise<string> {
Expand Down
4 changes: 2 additions & 2 deletions code/client/src/ui/pages/document/Document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ function Document() {
useEffect(() => {
async function fetchDocument() {
if (!id) return;
const { nodes, title } = await services.getDocument(id);
fugue.init(nodes);
const { operations, title } = await services.getDocument(id);
fugue.applyOperations(operations);
setTitle(title);
socket.emit('joinDocument', id);
setLoaded(true);
Expand Down
15 changes: 8 additions & 7 deletions code/client/tests/editor/domain/document/fugueOperations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import getFugueOperations from '@/domain/editor/operations/fugue/operations';
import { FugueDomainOperations } from '@/domain/editor/operations/fugue/types';
import { Document } from '@notespace/shared/crdt/types/document';
import { Node } from '@notespace/shared/crdt/types/nodes';
import { Node, RootNode } from '@notespace/shared/crdt/types/nodes';
import { rootNode, treeNode } from '@notespace/shared/crdt/utils';

describe('Fugue Operations', () => {
Expand Down Expand Up @@ -86,21 +86,22 @@ describe('Fugue Operations', () => {

test('should initialize document', () => {
// given
const root: Node<string> = rootNode();
const root: RootNode<string> = rootNode();
const node1: Node<string> = treeNode({ sender: 'A', counter: 0 }, 'a', root.id, 'R', 1);
const node2: Node<string> = treeNode({ sender: 'A', counter: 1 }, 'b', node1.id, 'R', 2);
root.rightChildren = [node1.id];
node1.rightChildren = [node2.id];
const document: Document = {
id: 'test',
title: 'test',
nodes: {
root: [root],
A: [node1, node2],
},
operations: [
{ type: 'insert', ...node1, parent: root.id, styles: [] },
{ type: 'insert', ...node2, parent: node1.id, styles: [] },
],
};

// when
fugueOperations.initDocument(document);
fugueOperations.applyOperations(document.operations);

// then
expect(fugue.toString()).toEqual('ab');
Expand Down
Binary file added code/server/bun.lockb
Binary file not shown.
9 changes: 5 additions & 4 deletions code/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@
"firebase-admin": "^12.1.0",
"lodash": "^4.17.21",
"socket.io": "^4.7.5",
"supertest": "^6.3.4"
"supertest": "^6.3.4",
"uuid": "^9.0.1"
},
"devDependencies": {
"@babel/preset-env": "^7.24.5",
"@babel/preset-typescript": "^7.24.1",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.0",
"@types/lodash": "^4.17.1",
"@types/node": "^20.12.8",
"@types/supertest": "^6.0.2",
"@types/uuid": "^9.0.8",
Expand All @@ -38,13 +39,13 @@
"eslint": "^8.57.0",
"express-promise-router": "^4.1.1",
"jest": "^29.7.0",
"knip": "^5.11.0",
"knip": "^5.12.2",
"prettier": "^3.2.5",
"socket.io-client": "^4.7.5",
"test-jest": "^1.0.1",
"ts-jest": "^29.1.2",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.8.2",
"tsx": "^4.9.1",
"typescript": "^5.4.5"
},
"packageManager": "[email protected]+sha256.0624e30eff866cdeb363b15061bdb7fd9425b17bc1bb42c22f5f4efdea21f6b3"
Expand Down
36 changes: 17 additions & 19 deletions code/server/src/database/firestore/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { Document, DocumentData, DocumentStorageData } from '@notespace/shared/c
import { v4 as uuid } from 'uuid';
import { NotFoundError } from '@domain/errors/errors';
import { Operation } from '@notespace/shared/crdt/types/operations';
import { firestore } from 'firebase-admin';
import FieldValue = firestore.FieldValue;

export default function DocumentFirestoreDatabase(): DocumentDatabase {
initializeApp({
Expand Down Expand Up @@ -35,36 +37,32 @@ export default function DocumentFirestoreDatabase(): DocumentDatabase {
}

async function getDocument(id: string): Promise<DocumentStorageData> {
const doc = await documents.doc(id).get();
if (!doc.exists) {
throw new NotFoundError(`Document with id ${id} not found`);
}
return doc.data() as DocumentStorageData;
const doc = await getDoc(id);
return (await doc.get()).data() as DocumentStorageData;
}

async function deleteDocument(id: string) {
const doc = await documents.doc(id).get();
if (!doc.exists) {
throw new NotFoundError(`Document with id ${id} not found`);
}
await documents.doc(id).delete();
const doc = await getDoc(id);
await doc.delete();
}

async function updateDocument(id: string, newOperations: Operation[]) {
const doc = await documents.doc(id).get();
if (!doc.exists) {
throw new NotFoundError(`Document with id ${id} not found`);
}
const { operations } = doc.data() as DocumentStorageData;
await documents.doc(id).update({ operations: [...operations, ...newOperations] });
const doc = await getDoc(id);
await doc.update({ operations: FieldValue.arrayUnion(newOperations) });
}

async function updateTitle(id: string, title: string) {
const doc = await documents.doc(id).get();
if (!doc.exists) {
const doc = await getDoc(id);
await doc.update({ title });
}

async function getDoc(id: string) {
const query = documents.where('id', '==', id);
const data = await query.get();
if (data.empty) {
throw new NotFoundError(`Document with id ${id} not found`);
}
await documents.doc(id).update({ title });
return data.docs[0].ref;
}

return {
Expand Down
Loading

0 comments on commit 46e9a6f

Please sign in to comment.