-
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #559 from streamich/revert
Revert patches, support undo/redo stacks
- Loading branch information
Showing
8 changed files
with
264 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
export interface UndoItem { | ||
undo(): RedoItem; | ||
} | ||
|
||
export interface RedoItem { | ||
redo(): UndoItem; | ||
} | ||
|
||
export class UndoRedoStack { | ||
private undoStack: UndoItem[] = []; | ||
private redoStack: RedoItem[] = []; | ||
|
||
public undoLength(): number { | ||
return this.undoStack.length; | ||
} | ||
|
||
public redoLength(): number { | ||
return this.redoStack.length; | ||
} | ||
|
||
public push(undo: UndoItem): RedoItem[] { | ||
const redoStack = this.redoStack; | ||
this.redoStack = []; | ||
this.undoStack.push(undo); | ||
return redoStack; | ||
} | ||
|
||
public undo(): void { | ||
const undo = this.undoStack.pop(); | ||
if (!undo) return; | ||
const redo = undo.undo(); | ||
this.redoStack.push(redo); | ||
} | ||
|
||
public redo(): void { | ||
const redo = this.redoStack.pop(); | ||
if (!redo) return; | ||
const undo = redo.redo(); | ||
this.undoStack.push(undo); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import {NodeBuilder, s, nodes} from '../../../json-crdt-patch'; | ||
import {deepEqual} from '../../../json-equal/deepEqual'; | ||
import {cmpUint8Array} from '../../../util/buffers/cmpUint8Array'; | ||
import {Model} from '../../model'; | ||
import {toSchema} from '../toSchema'; | ||
|
||
const cmp = (a: NodeBuilder, b: NodeBuilder): boolean => { | ||
if (a instanceof nodes.con && b instanceof nodes.con) return deepEqual(a.raw, b.raw); | ||
else if (a instanceof nodes.val && b instanceof nodes.val) return cmp(a.value, b.value); | ||
else if (a instanceof nodes.obj && b instanceof nodes.obj) { | ||
const objAKeys = Object.keys(a.obj); | ||
const objBKeys = Object.keys(a.obj); | ||
const objALen = objAKeys.length; | ||
const objBLen = objBKeys.length; | ||
if (objALen !== objBLen) return false; | ||
const optAKeys = Object.keys(a.opt || {}); | ||
const optBKeys = Object.keys(b.opt || {}); | ||
const optALen = optAKeys.length; | ||
const optBLen = optBKeys.length; | ||
if (optALen !== optBLen) return false; | ||
for (let i = 0; i < objALen; i++) { | ||
const key = objAKeys[i]; | ||
if (!cmp(a.obj[key], b.obj[key])) return false; | ||
} | ||
for (let i = 0; i < optALen; i++) { | ||
const key = optAKeys[i]; | ||
if (!cmp(a.opt![key], b.opt![key])) return false; | ||
} | ||
return true; | ||
} else if (a instanceof nodes.vec && b instanceof nodes.vec) { | ||
const vecA = a.value; | ||
const vecB = b.value; | ||
const len = vecA.length; | ||
if (len !== vecB.length) return false; | ||
for (let i = 0; i < len; i++) if (!cmp(vecA[i], vecA[i])) return false; | ||
return true; | ||
} else if (a instanceof nodes.str && b instanceof nodes.str) return a.raw === b.raw; | ||
else if (a instanceof nodes.bin && b instanceof nodes.bin) return cmpUint8Array(a.raw, b.raw); | ||
else if (a instanceof nodes.arr && b instanceof nodes.arr) { | ||
const arrA = a.arr; | ||
const arrB = b.arr; | ||
const len = arrA.length; | ||
if (len !== arrB.length) return false; | ||
for (let i = 0; i < len; i++) if (!cmp(arrA[i], arrB[i])) return false; | ||
return true; | ||
} | ||
return false; | ||
}; | ||
|
||
test('can infer schema of a document nodes', () => { | ||
const con = s.con('con'); | ||
const str = s.str('hello'); | ||
const obj = s.obj({ | ||
id: s.con('id'), | ||
val: s.val(s.str('world')), | ||
}); | ||
const schema = s.obj({ | ||
con, | ||
str, | ||
bin: s.bin(new Uint8Array([1, 2, 3])), | ||
obj, | ||
vec: s.vec(s.con(1), s.con({foo: 'bar'})), | ||
arr: s.arr([s.con(1), s.con({foo: 'bar'})]), | ||
}); | ||
const model = Model.withLogicalClock().setSchema(schema); | ||
const node = model.root.node(); | ||
const schema2 = toSchema(node); | ||
expect(cmp(schema, schema2)).toBe(true); | ||
const conSchema = toSchema(model.api.const('con').node); | ||
expect(cmp(con, conSchema)).toBe(true); | ||
expect(cmp(str, conSchema)).toBe(false); | ||
const strSchema = toSchema(model.api.str('str').node); | ||
expect(cmp(str, strSchema)).toBe(true); | ||
expect(cmp(con, strSchema)).toBe(false); | ||
const objSchema = toSchema(model.api.obj('obj').node); | ||
expect(cmp(obj, objSchema)).toBe(true); | ||
expect(cmp(con, objSchema)).toBe(false); | ||
}); | ||
|
||
test('can infer schema of a typed model', () => { | ||
const schema = s.obj({ | ||
id: s.con('id'), | ||
val: s.val(s.str('world')), | ||
}); | ||
const model = Model.withLogicalClock().setSchema(schema); | ||
const schema2 = toSchema(model.root.node()); | ||
expect(schema2.obj.id).toBeInstanceOf(nodes.con); | ||
expect(schema2.obj.val).toBeInstanceOf(nodes.val); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import {s} from '../../../json-crdt-patch'; | ||
import {Model} from '../../model'; | ||
import {JsonNodeToSchema, SchemaToJsonNode} from '../types'; | ||
|
||
describe('can infer schema of JSON CRDT nodes', () => { | ||
test('con', () => { | ||
const schema1 = s.con(123); | ||
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1; | ||
}); | ||
|
||
test('val', () => { | ||
const schema1 = s.val(s.con(true)); | ||
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1; | ||
}); | ||
|
||
test('obj', () => { | ||
const schema1 = s.obj({ | ||
hello: s.con('world'), | ||
}); | ||
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1; | ||
}); | ||
|
||
test('vec', () => { | ||
const schema1 = s.vec(s.con(1), s.val(s.con(2))); | ||
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1; | ||
}); | ||
|
||
test('str', () => { | ||
const schema1 = s.str('asdf'); | ||
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1; | ||
}); | ||
|
||
test('bin', () => { | ||
const schema1 = s.bin(new Uint8Array([1, 2, 3])); | ||
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1; | ||
}); | ||
|
||
test('arr', () => { | ||
const schema1 = s.arr([s.con(1), s.val(s.con(2))]); | ||
const schema2: JsonNodeToSchema<SchemaToJsonNode<typeof schema1>> = schema1; | ||
}); | ||
|
||
test('from typed model', () => { | ||
const model = Model.withLogicalClock().setSchema( | ||
s.obj({ | ||
id: s.con('asdf'), | ||
age: s.val(s.con(42)), | ||
}), | ||
); | ||
type Node = ReturnType<typeof model.root.node>; | ||
type Schema = JsonNodeToSchema<Node>; | ||
const schema: Schema = s.obj({ | ||
id: s.con('asdf'), | ||
age: s.val(s.con(42)), | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import {JsonNode, ConNode, ValNode, ObjNode, VecNode, StrNode, BinNode, ArrNode} from '../nodes'; | ||
import {NodeBuilder, s} from '../../json-crdt-patch'; | ||
import type {JsonNodeToSchema} from './types'; | ||
|
||
/** | ||
* Converts any JSON CRDT node to a schema representation. The schema can be | ||
* used to copy the structure of the JSON CRDT node to another document or | ||
* another location in the same document. | ||
* | ||
* @param node JSON CRDT node to recursively convert to schema. | ||
* @returns Schema representation of the JSON CRDT node. | ||
*/ | ||
export const toSchema = <N extends JsonNode<any>>(node: N): JsonNodeToSchema<N> => { | ||
if (node instanceof ConNode) return s.con(node.val) as any; | ||
if (node instanceof ValNode) return s.val(toSchema(node.node())) as any; | ||
if (node instanceof ObjNode) { | ||
const obj: Record<string, NodeBuilder> = {}; | ||
node.nodes((child, key) => (obj[key] = toSchema(child))); | ||
return s.obj(obj) as any; | ||
} | ||
if (node instanceof VecNode) { | ||
const arr: NodeBuilder[] = []; | ||
node.children((child) => arr.push(toSchema(child))); | ||
return s.vec(...arr) as any; | ||
} | ||
if (node instanceof StrNode) return s.str(node.view()) as any; | ||
if (node instanceof BinNode) return s.bin(node.view()) as any; | ||
if (node instanceof ArrNode) { | ||
const arr: NodeBuilder[] = []; | ||
node.children((child) => { | ||
if (child) arr.push(toSchema(child)); | ||
}); | ||
return s.arr(arr) as any; | ||
} | ||
return s.con(undefined) as any; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import type {nodes as builder} from '../../json-crdt-patch'; | ||
import type * as nodes from '../nodes'; | ||
|
||
// prettier-ignore | ||
export type SchemaToJsonNode<S> = S extends builder.str<infer T> | ||
? nodes.StrNode<T> | ||
: S extends builder.bin | ||
? nodes.BinNode | ||
: S extends builder.con<infer T> | ||
? nodes.ConNode<T> | ||
: S extends builder.val<infer T> | ||
? nodes.ValNode<SchemaToJsonNode<T>> | ||
: S extends builder.vec<infer T> | ||
? nodes.VecNode<{[K in keyof T]: SchemaToJsonNode<T[K]>}> | ||
: S extends builder.obj<infer T> | ||
? nodes.ObjNode<{[K in keyof T]: SchemaToJsonNode<T[K]>}> | ||
: S extends builder.arr<infer T> | ||
? nodes.ArrNode<SchemaToJsonNode<T>> | ||
: nodes.JsonNode; | ||
|
||
// prettier-ignore | ||
export type JsonNodeToSchema<N> = N extends nodes.StrNode<infer T> | ||
? builder.str<T> | ||
: N extends nodes.BinNode | ||
? builder.bin | ||
: N extends nodes.ConNode<infer T> | ||
? builder.con<T> | ||
: N extends nodes.ValNode<infer T> | ||
? builder.val<JsonNodeToSchema<T>> | ||
: N extends nodes.VecNode<infer T> | ||
? builder.vec<{[K in keyof T]: JsonNodeToSchema<T[K]>}> | ||
: N extends nodes.ObjNode<infer T> | ||
? builder.obj<{[K in keyof T]: JsonNodeToSchema<T[K]>}> | ||
: N extends nodes.ArrNode<infer T> | ||
? builder.arr<JsonNodeToSchema<T>> | ||
: builder.con<undefined>; |