diff --git a/Cargo.lock b/Cargo.lock index a327cd0..232990f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -473,9 +473,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "automerge" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93b5e6ed2097a1e55cce3128d64c909cdb42c800d4880411c7382f3dfa2c808d" +checksum = "b0b670b68c38e4042ea4826415f0f8101428810bce821d215e271966b24abac4" dependencies = [ "flate2", "fxhash", diff --git a/ng-app/package.json b/ng-app/package.json index 35da6ff..bebf180 100644 --- a/ng-app/package.json +++ b/ng-app/package.json @@ -16,6 +16,7 @@ "tauri": "tauri" }, "dependencies": { + "@automerge/automerge": "^2.2.8", "@codemirror/autocomplete": "^6.17.0", "@codemirror/commands": "^6.6.0", "@codemirror/lang-css": "^6.0.1", diff --git a/ng-app/src/apps/AutomergeEditor.svelte b/ng-app/src/apps/AutomergeEditor.svelte new file mode 100644 index 0000000..074d2a0 --- /dev/null +++ b/ng-app/src/apps/AutomergeEditor.svelte @@ -0,0 +1,130 @@ + + + + {#if safari_error} + {$t("errors.no_wasm_on_old_safari")} + {:else} +
+ +
+ {/if} + \ No newline at end of file diff --git a/ng-app/src/apps/AutomergeJsonSource.svelte b/ng-app/src/apps/AutomergeJsonSource.svelte new file mode 100644 index 0000000..67d3171 --- /dev/null +++ b/ng-app/src/apps/AutomergeJsonSource.svelte @@ -0,0 +1,74 @@ + + + + +{#if safari_error} + {$t("errors.no_wasm_on_old_safari")} +{:else if source} + + + +{/if} diff --git a/ng-app/src/apps/AutomergeViewer.svelte b/ng-app/src/apps/AutomergeViewer.svelte new file mode 100644 index 0000000..ec620c6 --- /dev/null +++ b/ng-app/src/apps/AutomergeViewer.svelte @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/ng-app/src/apps/ContainerView.svelte b/ng-app/src/apps/ContainerView.svelte index a25bcef..eea99d8 100644 --- a/ng-app/src/apps/ContainerView.svelte +++ b/ng-app/src/apps/ContainerView.svelte @@ -34,6 +34,7 @@ ret.push({nuri,hash}); } } + ret.sort((a, b) => a.hash.localeCompare(b.hash)); return ret; } diff --git a/ng-app/src/apps/JsonSource.svelte b/ng-app/src/apps/JsonSource.svelte deleted file mode 100644 index e69de29..0000000 diff --git a/ng-app/src/apps/MilkDownEditor.svelte b/ng-app/src/apps/MilkDownEditor.svelte index a506a9d..1edba40 100644 --- a/ng-app/src/apps/MilkDownEditor.svelte +++ b/ng-app/src/apps/MilkDownEditor.svelte @@ -83,11 +83,11 @@ return { update: (updatedView, prevState) => { - provider.update(updatedView, prevState); + provider.update(updatedView, prevState); }, destroy: () => { - provider.destroy(); - content.remove(); + provider.destroy(); + content.remove(); } } } diff --git a/ng-app/src/apps/YArrayJsonSource.svelte b/ng-app/src/apps/YArrayJsonSource.svelte deleted file mode 100644 index e69de29..0000000 diff --git a/ng-app/src/apps/automerge/ABoolean.svelte b/ng-app/src/apps/automerge/ABoolean.svelte new file mode 100644 index 0000000..172f56e --- /dev/null +++ b/ng-app/src/apps/automerge/ABoolean.svelte @@ -0,0 +1,36 @@ + + + + + + \ No newline at end of file diff --git a/ng-app/src/apps/automerge/ACounter.svelte b/ng-app/src/apps/automerge/ACounter.svelte new file mode 100644 index 0000000..e27ba69 --- /dev/null +++ b/ng-app/src/apps/automerge/ACounter.svelte @@ -0,0 +1,65 @@ + + + + + +
+ + + +
\ No newline at end of file diff --git a/ng-app/src/apps/automerge/ADate.svelte b/ng-app/src/apps/automerge/ADate.svelte new file mode 100644 index 0000000..b888df2 --- /dev/null +++ b/ng-app/src/apps/automerge/ADate.svelte @@ -0,0 +1,73 @@ + + + + + +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
\ No newline at end of file diff --git a/ng-app/src/apps/automerge/AList.svelte b/ng-app/src/apps/automerge/AList.svelte new file mode 100644 index 0000000..77674ff --- /dev/null +++ b/ng-app/src/apps/automerge/AList.svelte @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + {#each props as prop} + + + + + + + {/each} + +
List + {#if !readonly} + Push entry at the end of list: + + + {/if} +
{prop[0]} + updateScalar(prop[0],event)} /> +
+ + \ No newline at end of file diff --git a/ng-app/src/apps/automerge/AMap.svelte b/ng-app/src/apps/automerge/AMap.svelte new file mode 100644 index 0000000..6fea690 --- /dev/null +++ b/ng-app/src/apps/automerge/AMap.svelte @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + {#each props as prop} + + + + + + + {/each} + +
Map + {#if !readonly} + Add property: + + + + {/if} +
{prop[0]} + updateScalar(prop[0],event)} /> +
+ + + \ No newline at end of file diff --git a/ng-app/src/apps/automerge/ANumber.svelte b/ng-app/src/apps/automerge/ANumber.svelte new file mode 100644 index 0000000..0fbe7b3 --- /dev/null +++ b/ng-app/src/apps/automerge/ANumber.svelte @@ -0,0 +1,42 @@ + + + + + + \ No newline at end of file diff --git a/ng-app/src/apps/automerge/AString.svelte b/ng-app/src/apps/automerge/AString.svelte new file mode 100644 index 0000000..c40ae16 --- /dev/null +++ b/ng-app/src/apps/automerge/AString.svelte @@ -0,0 +1,32 @@ + + + + + + \ No newline at end of file diff --git a/ng-app/src/apps/automerge/AValue.svelte b/ng-app/src/apps/automerge/AValue.svelte new file mode 100644 index 0000000..da2d764 --- /dev/null +++ b/ng-app/src/apps/automerge/AValue.svelte @@ -0,0 +1,87 @@ + + + + + + +{#if type==="map"} + +{:else if type==="list"} + +{:else if type==="counter"} + {#if !readonly} + + {:else} + : {value} + {/if} +{:else if type==="text"} + {#if !readonly} + + {:else} + : {value} + {/if} +{:else if type==="boolean"} + {#if !readonly} + + {:else} + : {value} + {/if} +{:else if type==="number"} + {#if !readonly} + + {:else} + : {value} + {/if} +{:else if value?.toDateString || type==="timestamp"} + {#if !readonly} + + {:else} + : {render_date(value)} + {/if} +{:else} + : {value} +{/if} \ No newline at end of file diff --git a/ng-app/src/apps/automerge/utils.ts b/ng-app/src/apps/automerge/utils.ts new file mode 100644 index 0000000..582d433 --- /dev/null +++ b/ng-app/src/apps/automerge/utils.ts @@ -0,0 +1,81 @@ +// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers +// All rights reserved. +// Licensed under the Apache License, Version 2.0 +// +// or the MIT license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +import { next as A } from "@automerge/automerge/slim"; + +const UINT = Symbol.for("_am_uint") +const INT = Symbol.for("_am_int") +const F64 = Symbol.for("_am_f64") +const COUNTER = Symbol.for("_am_counter") +const TEXT = Symbol.for("_am_text") + +export function find_type(value) { + switch (typeof value) { + case "object": + if (value == null) { + return "null" + } else if (value[UINT]) { + return "uint" + } else if (value[INT]) { + return "number" + } else if (value[F64]) { + return "number" + } else if (value[COUNTER]) { + return "counter" + } else if (value instanceof Date) { + return "timestamp" + } else if (value instanceof A.RawString) { + return "str" + } else if (value instanceof Text) { + return "text" + } else if (value instanceof Uint8Array) { + return "bytes" + } else if (value instanceof Array) { + return "list" + } else if (Object.getPrototypeOf(value) === Object.getPrototypeOf({})) { + return "map" + } + case "boolean": + return "boolean" + case "number": + if (Number.isInteger(value)) { + return "number" + } else { + return "number" + } + case "string": + return "text" + } +} + +export const new_prop_types = [ + {value:'text',name:"text"}, + {value:'number',name:"number"}, + {value:'counter',name:"counter"}, + {value:'boolean',name:"boolean"}, + {value:'null',name:"null"}, + {value:'timestamp',name:"timestamp"}, + {value:'map',name:"map"}, + {value:'list',name:"list"}, + {value:'bytes',name:"bytes"} +]; + +export function new_value(new_prop_type_selected) { + switch (new_prop_type_selected) { + case 'text': return ''; + case 'map': return {}; + case 'list': return []; + case 'counter': return new A.Counter(); + case 'number': return 0; + case 'boolean': return false; + case 'null': return null; + case 'timestamp': return new Date(); + case 'bytes': return new Uint8Array(0); + } +} \ No newline at end of file diff --git a/ng-app/src/classes.ts b/ng-app/src/classes.ts index f42ab99..c05d0d8 100644 --- a/ng-app/src/classes.ts +++ b/ng-app/src/classes.ts @@ -286,15 +286,15 @@ export const official_classes = { }, "data:json": { "ng:crdt": "Automerge", - "ng:n": "JSON", - "ng:a": "JSON Data CRDT", + "ng:n": "JSON (Automerge)", + "ng:a": "Automerge JSON Data CRDT", "ng:o": "n:g:z:json_automerge_viewer", // default viewer "ng:w": "n:g:z:json_automerge_editor", // default editor "ng:compat": ["file:iana:application:json", "code:json"], }, "data:array": { "ng:crdt": "YArray", - "ng:n": "Array", + "ng:n": "Array (Yjs)", "ng:a": "Yjs Array CRDT", "ng:o": "n:g:z:json_yarray_viewer", // default viewer "ng:w": "n:g:z:json_yarray_editor", // default editor @@ -302,16 +302,16 @@ export const official_classes = { }, "data:map": { "ng:crdt": "YMap", - "ng:n": "Object", - "ng:a": "Yjs Map CRDT", + "ng:n": "Object (Yjs)", + "ng:a": "Yjs Map CRDT (JSON Object)", "ng:o": "n:g:z:json_ymap_viewer", // default viewer "ng:w": "n:g:z:json_ymap_editor", // default editor "ng:compat": ["file:iana:application:json", "code:json"], }, "data:xml": { "ng:crdt": "YXml", - "ng:n": "XML", - "ng:a": "XML Data CRDT", + "ng:n": "XML (Yjs)", + "ng:a": "Yjs XML CRDT", "ng:compat": ["file:iana:text:xml","file:iana:application:xml", "code:xml"], }, "data:table": { diff --git a/ng-app/src/lib/icons/DataClassIcon.svelte b/ng-app/src/lib/icons/DataClassIcon.svelte index d2643c4..58cbfb8 100644 --- a/ng-app/src/lib/icons/DataClassIcon.svelte +++ b/ng-app/src/lib/icons/DataClassIcon.svelte @@ -104,6 +104,8 @@ "query:graphql": GraphQLIcon, "data:graph": Sun, "data:json": JsonIcon, + "data:map": JsonIcon, + "data:array": JsonIcon, "data:table": TableCells, "data:collection": ListBullet, "data:container": Square3Stack3d, diff --git a/ng-app/src/locales/en.json b/ng-app/src/locales/en.json index 6ae836a..3413c66 100644 --- a/ng-app/src/locales/en.json +++ b/ng-app/src/locales/en.json @@ -596,7 +596,8 @@ "camera_unavailable": "Camera is unavailable.", "ServerAlreadyRunningInOtherProcess": "App is already running on this device. Check it and close it.", "cannot_load_this_file": "Cannot load this file", - "graph_not_found": "Graph not found" + "graph_not_found": "Graph not found", + "no_wasm_on_old_safari": "Your Safari browser is too old (version before 14.1). As a result we cannot load Automerge, needed for this document. Please upgrade your macOS or iOS system" }, "connectivity": { "stopped": "Stopped", diff --git a/ng-app/src/zeras.ts b/ng-app/src/zeras.ts index 8d115d0..0d73669 100644 --- a/ng-app/src/zeras.ts +++ b/ng-app/src/zeras.ts @@ -57,6 +57,7 @@ export const official_apps = { "ng:g": "n:g:z:json_automerge_editor", "ng:b": "AutomergeEditor", "ng:w": ["data:json"], + "full_width": true, }, "n:g:z:json_ymap_editor": { "ng:n": "JSON Editor", @@ -141,6 +142,7 @@ export const official_apps = { "ng:g": "n:g:z:json_automerge_viewer", "ng:b": "AutomergeViewer", "ng:o": ["data:json"], + "full_width": true, }, "n:g:z:triple_viewer": { "ng:n": "Graph Triples", @@ -318,7 +320,7 @@ export const official_apps = { "ng:c": "app", "ng:u": "source",//favicon. can be a did:ng:j "ng:g": "n:g:z:crdt_source_viewer:json", - "ng:b": "JsonSource", // displayed with highlight.js , with option to download + "ng:b": "AutomergeJsonSource", // displayed with highlight.js , with option to download "ng:o": ["data:json", "data:table", "doc:diagram:jsmind", "doc:diagram:gantt", "doc:diagram:excalidraw", "doc:viz:*", "doc:chart:*", "prod:cad"], "ng:w": [], }, diff --git a/ng-app/vite.config.ts b/ng-app/vite.config.ts index 22c4c8a..e81e595 100644 --- a/ng-app/vite.config.ts +++ b/ng-app/vite.config.ts @@ -18,7 +18,7 @@ export default defineConfig(async () => { "@milkdown/core", "@milkdown/ctx", "@milkdown/prose", "@milkdown/transformer", "@milkdown/preset-commonmark", "@milkdown/theme-nord", "@milkdown/plugin-collab", "svelte-highlight", "svelte-highlight/languages/typescript", "svelte-highlight/languages/javascript", "svelte-highlight/languages/rust", "@milkdown/preset-gfm", "@milkdown-lab/plugin-split-editing", "@milkdown/plugin-slash", "@milkdown/utils", "@milkdown/plugin-prism", "@milkdown/plugin-emoji", "@milkdown/plugin-math", "@milkdown/plugin-indent", - "svelte-jsoneditor"], + "svelte-jsoneditor", "@automerge/automerge/next", "@automerge/automerge/slim"], include: ["debug","extend","highlight.js","highlight.js/lib/core","lodash.debounce","@sindresorhus/is","char-regex","emojilib","skin-tone", 'immutable-json-patch', ] diff --git a/ng-net/src/app_protocol.rs b/ng-net/src/app_protocol.rs index b9f61d8..a2a6aaf 100644 --- a/ng-net/src/app_protocol.rs +++ b/ng-net/src/app_protocol.rs @@ -636,7 +636,7 @@ pub enum DiscreteUpdate { YXml(Vec), #[serde(with = "serde_bytes")] YText(Vec), - /// An automerge::Patch + /// An automerge::Change.raw_bytes() #[serde(with = "serde_bytes")] Automerge(Vec), } @@ -752,7 +752,7 @@ pub enum DiscretePatch { YXml(Vec), #[serde(with = "serde_bytes")] YText(Vec), - /// An automerge::Patch + /// An automerge::Change.raw_bytes() or a concatenation of several. #[serde(with = "serde_bytes")] Automerge(Vec), } diff --git a/ng-repo/src/errors.rs b/ng-repo/src/errors.rs index 9f4743e..2e3b990 100644 --- a/ng-repo/src/errors.rs +++ b/ng-repo/src/errors.rs @@ -369,6 +369,7 @@ pub enum VerifierError { CannotRemoveTriplesWhenNewBranch, PermissionDenied, YrsError(String), + AutomergeError(String), InvalidNuri, } diff --git a/ng-verifier/Cargo.toml b/ng-verifier/Cargo.toml index e3452f0..fb2901e 100644 --- a/ng-verifier/Cargo.toml +++ b/ng-verifier/Cargo.toml @@ -29,7 +29,7 @@ either = "1.8.1" futures = "0.3.24" async-trait = "0.1.64" async-std = { version = "1.12.0", features = [ "attributes", "unstable" ] } -automerge = "0.5.9" +automerge = "0.5.11" yrs = "0.19.2" sbbf-rs-safe = "0.3.2" ng-repo = { path = "../ng-repo", version = "0.1.0-preview.1" } diff --git a/ng-verifier/src/commits/transaction.rs b/ng-verifier/src/commits/transaction.rs index c0d42bf..17b2a39 100644 --- a/ng-verifier/src/commits/transaction.rs +++ b/ng-verifier/src/commits/transaction.rs @@ -183,7 +183,12 @@ impl Verifier { { match crdt { BranchCrdt::Automerge(_) => { - unimplemented!(); + let mut doc = automerge::Automerge::load(&state) + .map_err(|e| VerifierError::AutomergeError(e.to_string()))?; + let _ = doc + .load_incremental(patch.as_slice()) + .map_err(|e| VerifierError::AutomergeError(e.to_string()))?; + doc.save() } BranchCrdt::YArray(_) | BranchCrdt::YMap(_) @@ -195,7 +200,7 @@ impl Verifier { let update = yrs::Update::decode_v1(&state) .map_err(|e| VerifierError::YrsError(e.to_string()))?; txn.apply_update(update); - let update = yrs::Update::decode_v1(&patch.to_vec()) + let update = yrs::Update::decode_v1(patch.as_slice()) .map_err(|e| VerifierError::YrsError(e.to_string()))?; txn.apply_update(update); txn.commit(); diff --git a/ng-verifier/src/types.rs b/ng-verifier/src/types.rs index 2e1f6a4..17704d9 100644 --- a/ng-verifier/src/types.rs +++ b/ng-verifier/src/types.rs @@ -75,6 +75,15 @@ impl DiscreteTransaction { | Self::Automerge(v) => v.to_vec(), } } + pub fn as_slice(&self) -> &[u8] { + match self { + Self::YMap(v) + | Self::YArray(v) + | Self::YXml(v) + | Self::YText(v) + | Self::Automerge(v) => v, + } + } } #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e2ae34a..79664c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,7 @@ importers: ng-app: specifiers: + '@automerge/automerge': ^2.2.8 '@codemirror/autocomplete': ^6.17.0 '@codemirror/commands': ^6.6.0 '@codemirror/lang-css': ^6.0.1 @@ -105,6 +106,7 @@ importers: y-protocols: ^1.0.1 yjs: ^13.6.18 dependencies: + '@automerge/automerge': 2.2.8 '@codemirror/autocomplete': 6.17.0_y4udqqf6vpekb2i4jypui2pd5e '@codemirror/commands': 6.6.0 '@codemirror/lang-css': 6.2.1_@codemirror+view@6.29.1 @@ -250,6 +252,12 @@ packages: '@jridgewell/trace-mapping': 0.3.25 dev: false + /@automerge/automerge/2.2.8: + resolution: {integrity: sha512-kP6Z9lIkNeIGe/jhF8SUQAgUMwVjtXCBL0eZEgQGCoWvPNTPcg8fnQco28XkYBrfkkuqiEUAbdHhJmSp0y79ug==} + dependencies: + uuid: 9.0.0 + dev: false + /@codemirror/autocomplete/6.17.0_auqt24wwbhvfpe3kty7jtmzbdy: resolution: {integrity: sha512-fdfj6e6ZxZf8yrkMHUSJJir7OJkHkZKaOZGzLWIYp2PZ3jd+d+UjG8zVPqJF6d3bKxkhvXTPan/UZ1t7Bqm0gA==} peerDependencies: