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 history tab and enhance visualization features to devtools #760

Merged
merged 58 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
b556881
Add devtools event
chacha912 Feb 13, 2024
7eef61a
Change Devtools protocol
chacha912 Feb 13, 2024
418a80c
Expose the `toJSForTest` method in JSONElement
chacha912 Feb 13, 2024
ead8f3b
Modify to build document from snapshot of Devtools.ChangeInfo
chacha912 Feb 13, 2024
4cf3f8e
Fix typo
chacha912 Feb 13, 2024
d3373fc
Add a `History` tab
chacha912 Feb 13, 2024
5bba8b4
Modify contents value in the toTestString of tree-edit operation
chacha912 Feb 14, 2024
621b8bb
Add attrs, index, path, and pos information to TreeNodeInfo
chacha912 Feb 14, 2024
5edaeee
Add tooltip to display crdt tree node information
chacha912 Feb 14, 2024
bf6c27d
Change offset separator in `toTestString` of CRDTTreeNodeID
chacha912 Feb 14, 2024
7ef1b64
Update the history tab to be resizable
chacha912 Feb 14, 2024
d0a5f81
Fix crdt tree node tooltip style
chacha912 Feb 15, 2024
b789b0a
Change offset separator
chacha912 Feb 15, 2024
cf6e46c
Fix errors occurring with previous versions of Yorkie-JS-SDK
chacha912 Feb 16, 2024
a036498
Merge remote-tracking branch 'origin' into devtools-mvp-2
chacha912 Feb 22, 2024
5ba651e
Extract resetDocument into a function
chacha912 Feb 22, 2024
77bc470
Change `Devtools.ChangeInfo` to `HistoryChangePack` in the protocol
chacha912 Feb 22, 2024
5b71fa0
Change `changesForTest` to `historyChanges`
chacha912 Feb 22, 2024
374ec4c
Keep focus unchanged when a specific change is selected despite new u…
chacha912 Feb 25, 2024
4a4bb68
Modify Devtools to build document from historyChanges
chacha912 Mar 3, 2024
b332457
Add bytesToChangeID and bytesToOperation
chacha912 Mar 3, 2024
031372b
Export Long
chacha912 Mar 3, 2024
1eec309
Modify Devtools to display events
chacha912 Mar 3, 2024
db34be7
Export Devtools
chacha912 Mar 4, 2024
00d492b
Cleanup codes
chacha912 Mar 7, 2024
5b5e1ef
Fix getMyPresence
chacha912 Mar 7, 2024
a5256ca
Cleanup devtools code
chacha912 Mar 7, 2024
3141320
Merge branch 'main' of https://github.com/yorkie-team/yorkie-js-sdk i…
chacha912 Mar 7, 2024
60c0474
Merge branch 'main' of https://github.com/yorkie-team/yorkie-js-sdk i…
chacha912 Mar 8, 2024
6288352
Add an option to set the Devtools environment
chacha912 Mar 8, 2024
a8cb866
Remove unused code
chacha912 Mar 12, 2024
9d7f739
Extract ChangePayload of HistoryChangePack
chacha912 Mar 13, 2024
53bc0e9
Remove unnecessary changes
chacha912 Mar 13, 2024
e438bcd
Export HistoryChangePack
chacha912 Mar 13, 2024
05a39c7
Import HistoryChangePack from yorkie-js-sdk
chacha912 Mar 13, 2024
e0b3382
Merge remote-tracking branch 'origin' into devtools-mvp-2-v2
chacha912 Mar 15, 2024
c4f8ec3
Reset SelectedChangeIndexInfo when browser reloads
chacha912 Mar 19, 2024
fdfb115
Fix issue where presence selection is not maintained in Devtools
chacha912 Mar 20, 2024
9b320f6
Reflect feedback
hackerwins Mar 20, 2024
cb87162
Reflect feedback
hackerwins Mar 20, 2024
29f647f
Set default value for enableDevtools
chacha912 Mar 26, 2024
9ffc7bc
Update yorkie-js-sdk in devtools
chacha912 Apr 4, 2024
f3f6684
Merge branch 'main' of https://github.com/yorkie-team/yorkie-js-sdk i…
chacha912 Apr 5, 2024
202cd53
Merge branch 'main' of https://github.com/yorkie-team/yorkie-js-sdk i…
chacha912 Apr 11, 2024
01fdeb3
Merge branch 'main' of https://github.com/yorkie-team/yorkie-js-sdk i…
chacha912 Apr 12, 2024
73a480c
Merge branch 'main' of https://github.com/yorkie-team/yorkie-js-sdk i…
chacha912 Apr 15, 2024
d91ec0e
Replace HistoryChangePack with DocEvent
chacha912 Apr 17, 2024
c2010a8
Replace HistoryChangePack with DocEvent in devtool
chacha912 Apr 17, 2024
8db74a6
Update comment
chacha912 Apr 18, 2024
9d8fc62
Add comment and specify type for ChangeStruct
chacha912 Apr 18, 2024
b0d310c
Define type `Array<DocEvent>` as TransactionDocEvents
chacha912 Apr 18, 2024
64c6acd
Apply TransactionDocEvents in devtool
chacha912 Apr 18, 2024
f3b0953
Merge branch 'main' of https://github.com/yorkie-team/yorkie-js-sdk i…
chacha912 Apr 19, 2024
3263adf
Lazy generating event bytes
chacha912 Apr 19, 2024
d84c1c8
Modify to collect docEvents in devtools
chacha912 Apr 22, 2024
38e7495
Remove opsForTest from root
chacha912 Apr 22, 2024
a9518d4
Update yorkie-js-sdk in devtools
chacha912 Apr 22, 2024
e766f0f
Apply feedback
hackerwins Apr 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
296 changes: 277 additions & 19 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 1 addition & 5 deletions public/devtool/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,7 @@ const objectDevtool = (
};

const displayOps = () => {
opsHolder.innerHTML = `
<div class="devtool-ops-holder">
${renderOpsHolder(doc.getOpsForTest(), 'op')}
</div>
`;
// TODO(chacha912): Implement this feature in Devtools extension.
};

const displayUndoOps = () => {
Expand Down
289 changes: 158 additions & 131 deletions src/api/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1071,147 +1071,151 @@ function fromTreeNode(pbTreeNode: PbTreeNode): CRDTTreeNode {
}

/**
* `fromOperations` converts the given Protobuf format to model format.
*/
function fromOperations(pbOperations: Array<PbOperation>): Array<Operation> {
const operations = [];
* `fromOperation` converts the given Protobuf format to model format.
*/
function fromOperation(pbOperation: PbOperation): Operation | undefined {
if (pbOperation.body.case === 'set') {
const pbSetOperation = pbOperation.body.value;
return SetOperation.create(
pbSetOperation!.key,
fromElementSimple(pbSetOperation!.value!),
fromTimeTicket(pbSetOperation!.parentCreatedAt)!,
fromTimeTicket(pbSetOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'add') {
const pbAddOperation = pbOperation.body.value;
return AddOperation.create(
fromTimeTicket(pbAddOperation!.parentCreatedAt)!,
fromTimeTicket(pbAddOperation!.prevCreatedAt)!,
fromElementSimple(pbAddOperation!.value!),
fromTimeTicket(pbAddOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'move') {
const pbMoveOperation = pbOperation.body.value;
return MoveOperation.create(
fromTimeTicket(pbMoveOperation!.parentCreatedAt)!,
fromTimeTicket(pbMoveOperation!.prevCreatedAt)!,
fromTimeTicket(pbMoveOperation!.createdAt)!,
fromTimeTicket(pbMoveOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'remove') {
const pbRemoveOperation = pbOperation.body.value;
return RemoveOperation.create(
fromTimeTicket(pbRemoveOperation!.parentCreatedAt)!,
fromTimeTicket(pbRemoveOperation!.createdAt)!,
fromTimeTicket(pbRemoveOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'edit') {
const pbEditOperation = pbOperation.body.value;
const createdAtMapByActor = new Map();
Object.entries(pbEditOperation!.createdAtMapByActor).forEach(
([key, value]) => {
createdAtMapByActor.set(key, fromTimeTicket(value));
},
);
const attributes = new Map();
Object.entries(pbEditOperation!.attributes).forEach(([key, value]) => {
attributes.set(key, value);
});
return EditOperation.create(
fromTimeTicket(pbEditOperation!.parentCreatedAt)!,
fromTextNodePos(pbEditOperation!.from!),
fromTextNodePos(pbEditOperation!.to!),
createdAtMapByActor,
pbEditOperation!.content,
attributes,
fromTimeTicket(pbEditOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'style') {
const pbStyleOperation = pbOperation.body.value;
const createdAtMapByActor = new Map();
Object.entries(pbStyleOperation!.createdAtMapByActor).forEach(
([key, value]) => {
createdAtMapByActor.set(key, fromTimeTicket(value));
},
);
const attributes = new Map();
Object.entries(pbStyleOperation!.attributes).forEach(([key, value]) => {
attributes.set(key, value);
});
return StyleOperation.create(
fromTimeTicket(pbStyleOperation!.parentCreatedAt)!,
fromTextNodePos(pbStyleOperation!.from!),
fromTextNodePos(pbStyleOperation!.to!),
createdAtMapByActor,
attributes,
fromTimeTicket(pbStyleOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'select') {
// TODO(hackerwins): Select is deprecated.
return;
} else if (pbOperation.body.case === 'increase') {
const pbIncreaseOperation = pbOperation.body.value;
return IncreaseOperation.create(
fromTimeTicket(pbIncreaseOperation!.parentCreatedAt)!,
fromElementSimple(pbIncreaseOperation!.value!),
fromTimeTicket(pbIncreaseOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'treeEdit') {
const pbTreeEditOperation = pbOperation.body.value;
const createdAtMapByActor = new Map();
Object.entries(pbTreeEditOperation!.createdAtMapByActor).forEach(
([key, value]) => {
createdAtMapByActor.set(key, fromTimeTicket(value));
},
);
return TreeEditOperation.create(
fromTimeTicket(pbTreeEditOperation!.parentCreatedAt)!,
fromTreePos(pbTreeEditOperation!.from!),
fromTreePos(pbTreeEditOperation!.to!),
fromTreeNodesWhenEdit(pbTreeEditOperation!.contents),
pbTreeEditOperation!.splitLevel,
createdAtMapByActor,
fromTimeTicket(pbTreeEditOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'treeStyle') {
const pbTreeStyleOperation = pbOperation.body.value;
const attributes = new Map();
const attributesToRemove = pbTreeStyleOperation.attributesToRemove;

for (const pbOperation of pbOperations) {
let operation: Operation;
if (pbOperation.body.case === 'set') {
const pbSetOperation = pbOperation.body.value;
operation = SetOperation.create(
pbSetOperation!.key,
fromElementSimple(pbSetOperation!.value!),
fromTimeTicket(pbSetOperation!.parentCreatedAt)!,
fromTimeTicket(pbSetOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'add') {
const pbAddOperation = pbOperation.body.value;
operation = AddOperation.create(
fromTimeTicket(pbAddOperation!.parentCreatedAt)!,
fromTimeTicket(pbAddOperation!.prevCreatedAt)!,
fromElementSimple(pbAddOperation!.value!),
fromTimeTicket(pbAddOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'move') {
const pbMoveOperation = pbOperation.body.value;
operation = MoveOperation.create(
fromTimeTicket(pbMoveOperation!.parentCreatedAt)!,
fromTimeTicket(pbMoveOperation!.prevCreatedAt)!,
fromTimeTicket(pbMoveOperation!.createdAt)!,
fromTimeTicket(pbMoveOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'remove') {
const pbRemoveOperation = pbOperation.body.value;
operation = RemoveOperation.create(
fromTimeTicket(pbRemoveOperation!.parentCreatedAt)!,
fromTimeTicket(pbRemoveOperation!.createdAt)!,
fromTimeTicket(pbRemoveOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'edit') {
const pbEditOperation = pbOperation.body.value;
const createdAtMapByActor = new Map();
Object.entries(pbEditOperation!.createdAtMapByActor).forEach(
([key, value]) => {
createdAtMapByActor.set(key, fromTimeTicket(value));
},
);
const attributes = new Map();
Object.entries(pbEditOperation!.attributes).forEach(([key, value]) => {
attributes.set(key, value);
});
operation = EditOperation.create(
fromTimeTicket(pbEditOperation!.parentCreatedAt)!,
fromTextNodePos(pbEditOperation!.from!),
fromTextNodePos(pbEditOperation!.to!),
createdAtMapByActor,
pbEditOperation!.content,
attributes,
fromTimeTicket(pbEditOperation!.executedAt)!,
if (attributesToRemove.length > 0) {
return TreeStyleOperation.createTreeRemoveStyleOperation(
fromTimeTicket(pbTreeStyleOperation!.parentCreatedAt)!,
fromTreePos(pbTreeStyleOperation!.from!),
fromTreePos(pbTreeStyleOperation!.to!),
attributesToRemove,
fromTimeTicket(pbTreeStyleOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'style') {
const pbStyleOperation = pbOperation.body.value;
const createdAtMapByActor = new Map();
Object.entries(pbStyleOperation!.createdAtMapByActor).forEach(
} else {
Object.entries(pbTreeStyleOperation!.attributes).forEach(
([key, value]) => {
createdAtMapByActor.set(key, fromTimeTicket(value));
attributes.set(key, value);
},
);
const attributes = new Map();
Object.entries(pbStyleOperation!.attributes).forEach(([key, value]) => {
attributes.set(key, value);
});
operation = StyleOperation.create(
fromTimeTicket(pbStyleOperation!.parentCreatedAt)!,
fromTextNodePos(pbStyleOperation!.from!),
fromTextNodePos(pbStyleOperation!.to!),
createdAtMapByActor,
return TreeStyleOperation.create(
fromTimeTicket(pbTreeStyleOperation!.parentCreatedAt)!,
fromTreePos(pbTreeStyleOperation!.from!),
fromTreePos(pbTreeStyleOperation!.to!),
attributes,
fromTimeTicket(pbStyleOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'select') {
// TODO(hackerwins): Select is deprecated.
continue;
} else if (pbOperation.body.case === 'increase') {
const pbIncreaseOperation = pbOperation.body.value;
operation = IncreaseOperation.create(
fromTimeTicket(pbIncreaseOperation!.parentCreatedAt)!,
fromElementSimple(pbIncreaseOperation!.value!),
fromTimeTicket(pbIncreaseOperation!.executedAt)!,
fromTimeTicket(pbTreeStyleOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'treeEdit') {
const pbTreeEditOperation = pbOperation.body.value;
const createdAtMapByActor = new Map();
Object.entries(pbTreeEditOperation!.createdAtMapByActor).forEach(
([key, value]) => {
createdAtMapByActor.set(key, fromTimeTicket(value));
},
);
operation = TreeEditOperation.create(
fromTimeTicket(pbTreeEditOperation!.parentCreatedAt)!,
fromTreePos(pbTreeEditOperation!.from!),
fromTreePos(pbTreeEditOperation!.to!),
fromTreeNodesWhenEdit(pbTreeEditOperation!.contents),
pbTreeEditOperation!.splitLevel,
createdAtMapByActor,
fromTimeTicket(pbTreeEditOperation!.executedAt)!,
);
} else if (pbOperation.body.case === 'treeStyle') {
const pbTreeStyleOperation = pbOperation.body.value;
const attributes = new Map();

const attributesToRemove = pbTreeStyleOperation.attributesToRemove;
if (attributesToRemove.length > 0) {
operation = TreeStyleOperation.createTreeRemoveStyleOperation(
fromTimeTicket(pbTreeStyleOperation!.parentCreatedAt)!,
fromTreePos(pbTreeStyleOperation!.from!),
fromTreePos(pbTreeStyleOperation!.to!),
attributesToRemove,
fromTimeTicket(pbTreeStyleOperation!.executedAt)!,
);
} else {
Object.entries(pbTreeStyleOperation!.attributes).forEach(
([key, value]) => {
attributes.set(key, value);
},
);

operation = TreeStyleOperation.create(
fromTimeTicket(pbTreeStyleOperation!.parentCreatedAt)!,
fromTreePos(pbTreeStyleOperation!.from!),
fromTreePos(pbTreeStyleOperation!.to!),
attributes,
fromTimeTicket(pbTreeStyleOperation!.executedAt)!,
);
}
} else {
throw new YorkieError(Code.Unimplemented, `unimplemented operation`);
}

operations.push(operation);
} else {
throw new YorkieError(Code.Unimplemented, `unimplemented operation`);
}
}

/**
* `fromOperations` converts the given Protobuf format to model format.
*/
function fromOperations(pbOperations: Array<PbOperation>): Array<Operation> {
const operations = [];
for (const pbOperation of pbOperations) {
const operation = fromOperation(pbOperation);
if (operation) {
operations.push(operation);
}
}
return operations;
}

Expand Down Expand Up @@ -1499,6 +1503,22 @@ function toUint8Array(hex: string): Uint8Array {
return hexToBytes(hex);
}

/**
* `bytesToChangeID` creates a ChangeID from the given bytes.
*/
function bytesToChangeID(bytes: Uint8Array): ChangeID {
const pbChangeID = PbChangeID.fromBinary(bytes);
return fromChangeID(pbChangeID);
}

/**
* `bytesToOperation` creates an Operation from the given bytes.
*/
function bytesToOperation(bytes: Uint8Array): Operation {
const pbOperation = PbOperation.fromBinary(bytes);
return fromOperation(pbOperation)!;
}

/**
* `converter` is a converter that converts the given model to protobuf format.
* is also used to convert models to bytes and vice versa.
Expand All @@ -1511,6 +1531,13 @@ export const converter = {
objectToBytes,
bytesToObject,
bytesToSnapshot,
bytesToHex,
hexToBytes,
toHexString,
toUint8Array,
toOperation,
toChangeID,
PbChangeID,
bytesToChangeID,
bytesToOperation,
};
Loading
Loading