edit title and intro

master
Niko PLP 2 months ago
parent 711fa42621
commit 254ad56e5b
  1. 1
      Cargo.lock
  2. 1
      RELEASE-NOTE.md
  3. 9
      ng-app/index-web.html
  4. 49
      ng-app/src-tauri/src/lib.rs
  5. 2
      ng-app/src/api.ts
  6. 34
      ng-app/src/apps/ContainerView.svelte
  7. 4
      ng-app/src/apps/SparqlQueryEditor.svelte
  8. 77
      ng-app/src/classes.ts
  9. 4
      ng-app/src/lib/Document.svelte
  10. 123
      ng-app/src/lib/FullLayout.svelte
  11. 10
      ng-app/src/lib/icons/DataClassIcon.svelte
  12. 70
      ng-app/src/lib/popups/Header.svelte
  13. 13
      ng-app/src/locales/en.json
  14. 4
      ng-app/src/routes/WalletInfo.svelte
  15. 42
      ng-app/src/routes/WalletLogin.svelte
  16. 61
      ng-app/src/store.ts
  17. 6
      ng-app/src/styles.css
  18. 7
      ng-app/src/tab.ts
  19. 32
      ng-app/src/zeras.ts
  20. 56
      ng-net/src/app_protocol.rs
  21. 26
      ng-oxigraph/src/oxigraph/sparql/dataset.rs
  22. 12
      ng-oxigraph/src/oxigraph/storage/mod.rs
  23. 11
      ng-repo/src/repo.rs
  24. 90
      ng-repo/src/store.rs
  25. 17
      ng-repo/src/types.rs
  26. 62
      ng-sdk-js/app-node/index.js
  27. 110
      ng-sdk-js/src/lib.rs
  28. 1
      ng-verifier/Cargo.toml
  29. 99
      ng-verifier/src/commits/transaction.rs
  30. 159
      ng-verifier/src/request_processor.rs
  31. 2
      ng-verifier/src/site.rs
  32. 13
      ng-verifier/src/types.rs
  33. 100
      ng-verifier/src/verifier.rs

1
Cargo.lock generated

@ -3507,6 +3507,7 @@ dependencies = [
"either",
"futures",
"getrandom 0.2.10",
"lazy_static",
"ng-net",
"ng-oxigraph",
"ng-repo",

@ -40,6 +40,5 @@ The `ngcli` daemon is release with the basic features listed in `ngcli --help`.
- you cannot share documents with other users. Everything is ready for this internally, but there is still some wiring to do that will take some more time.
- the Rich text editors (both for normal Post/Article and in Markdown) do not let you insert images nor links to other documents.
- your documents listed in your 3 stores do not display any title or content type, making it difficult to understand which document is which by just reading the 7-character ID of the documents. This will be addressed very quickly, as soon as the "Header branch" feature will be implemented. For the same reason (lack of this feature), and in the web-app only, when you will have created many docs with many modifications, the loading of your app can take some time because it is loading all the content of all the docs at startup. The native apps are already working well and do not suffer from this caveat. For the web-app, it is not the intended behaviour of course, but we need the "Header branch" feature to fix this.
- The webapp has some limitation for now when it is offline, because it doesn't have a UserStorage. it works differently than the native apps, as it has to replay all the commits at every load. This will stay like that for now, as the feature "Web UserStorage" based on IndexedDB will take some time to be coded.
- JSON-LD isn't ready yet as we need the "Context branch" feature in order to enter the list of ontologies each document is based on.

@ -116,19 +116,24 @@
</div>
<script>
const supported = (() => {
if (RegExp().hasIndices === undefined) return false;
if (RegExp().hasIndices === undefined) {console.log("no RegExp().hasIndices");return false;}
try {
if (Worker === undefined) return false;
if (Worker === undefined) {console.log("no Worker");return false;}
new Worker(URL.createObjectURL(new Blob([';'], {type: 'application/javascript'})));
if (typeof WebAssembly === "object"
&& typeof WebAssembly.instantiate === "function") {
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
if (module instanceof WebAssembly.Module)
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
else {
console.log("no WebAssembly module");
}
}
} catch (e) {
{console.log(e);return false;}
}
console.log("no WebAssembly");
return false;
})();
if (!supported ) {

@ -551,6 +551,53 @@ async fn branch_history(session_id: u64, nuri: String) -> Result<AppHistoryJs, S
}
}
#[tauri::command(rename_all = "snake_case")]
async fn update_header(
session_id: u64,
nuri: String,
title: Option<String>,
about: Option<String>,
) -> Result<(), String> {
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_header(),
nuri,
payload: Some(AppRequestPayload::new_header(title, about)),
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
if let AppResponse::V0(AppResponseV0::Error(e)) = res {
Err(e)
} else {
Ok(())
}
}
#[tauri::command(rename_all = "snake_case")]
async fn fetch_header(session_id: u64, nuri: String) -> Result<AppHeader, String> {
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_fetch_header(),
nuri,
payload: None,
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
match res {
AppResponse::V0(AppResponseV0::Error(e)) => Err(e),
AppResponse::V0(AppResponseV0::Header(h)) => Ok(h),
_ => Err("invalid response".to_string()),
}
}
#[tauri::command(rename_all = "snake_case")]
async fn sparql_update(
session_id: u64,
@ -1023,6 +1070,8 @@ impl AppBuilder {
signature_status,
signature_request,
signed_snapshot_request,
update_header,
fetch_header,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

@ -53,6 +53,8 @@ const mapping = {
"signature_status": ["session_id", "nuri"],
"signed_snapshot_request": ["session_id", "nuri"],
"signature_request": ["session_id", "nuri"],
"update_header": ["session_id","nuri","title","about"],
"fetch_header": ["session_id", "nuri"]
}

@ -11,8 +11,7 @@
<script lang="ts">
import {
} from "../store";
import ng from "../api";
import { link } from "svelte-spa-router";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import{ PlusCircle } from "svelte-heros-v2";
@ -20,9 +19,16 @@
import {
in_memory_discrete, open_viewer, set_viewer, set_editor, set_view_or_edit, cur_tab_branch_class, cur_tab_doc_can_edit, cur_tab
} from "../tab";
import DataClassIcon from "../lib/icons/DataClassIcon.svelte";
import {
openModalCreate
openModalCreate,
sparql_query,
active_session
} from "../store";
import {
Clipboard
} from "svelte-heros-v2";
export let commits;
function contained(graph) {
@ -30,23 +36,41 @@
for (const g of graph) {
if (g.substring(57,90) === "http://www.w3.org/ns/ldp#contains") {
let nuri = g.substring(93,146);
let repo = nuri;
nuri = nuri + ":" + $cur_tab.store.overlay;
let hash = nuri.substring(9,16);
ret.push({nuri,hash});
ret.push({nuri,hash,repo});
}
}
ret.sort((a, b) => a.hash.localeCompare(b.hash));
return ret;
}
async function fetch_header(repo) {
try {
let res = await ng.fetch_header($active_session.session_id, repo);
return res;
}catch(e){
console.error(e);
return {};
}
}
const create = () => {
openModalCreate();
}
const config = {
class: "mr-2 w-6 h-6 shrink-0 focus:outline-none"
}
</script>
<div class="flex-col p-5">
{#each contained(commits.graph) as doc}
<div class="flex font-mono mb-3"> <a use:link href="/{doc.nuri}">{doc.hash}</a> </div>
{#await fetch_header(doc.repo)}
<div class="flex"> <Clipboard tabindex="-1" class="mr-2 w-6 h-6 shrink-0 focus:outline-none"/><div class="flex font-mono mb-3"> <a use:link href="/{doc.nuri}">{doc.hash}</a> </div> </div>
{:then header}
<div class="flex" title="{header.about || ''}"> {#if header.class}<DataClassIcon {config} dataClass={header.class}/>{:else}<Clipboard tabindex="-1" class="mr-2 w-6 h-6 shrink-0 focus:outline-none"/>{/if}<div class="flex font-mono mb-3"> <a use:link href="/{doc.nuri}">{header.title || doc.hash}</a> </div></div>
{/await}
{/each}
{#if commits.graph.length == 0 || contained(commits.graph).length == 0}
<p>{$t("doc.empty_container")}</p>

@ -55,7 +55,7 @@
await reset_toasts();
results = await sparql_query($in_memory_discrete, union);
} catch(e) {
console.log(e)
console.error(e)
toast_error(display_error(e));
}
}
@ -112,7 +112,7 @@
{#each results.results.bindings as row}
<TableBodyRow>
{#each results.head.vars as variable}
<TableBodyCell class="px-6 py-4 whitespace-break-spaces font-medium">{row[variable].value}</TableBodyCell>
<TableBodyCell class="px-6 py-4 whitespace-break-spaces font-medium">{#if row[variable]} {row[variable].value }{/if}</TableBodyCell>
{/each}
</TableBodyRow>
{/each}

@ -12,7 +12,7 @@
// "data:graph", "data:json", "data:array", "data:map", "data:xml", "data:table", "data:collection", "data:board", "data:grid", "data:geomap",
// "e:email", "e:web", "e:http://[url of class in ontology]", "e:rdf" (read-only cache of RDF fetched from web2.0)
// "mc:text", "mc:link", "mc:card", "mc:pad",
// "doc:diagram","doc:chart", "doc:pdf", "doc:odf", "doc:latex", "doc:ps", "doc:music", "doc:maths", "doc:chemistry", "doc:braille", "doc:ancientscript",
// "diagram","chart", "doc:pdf", "doc:odf", "doc:latex", "doc:ps", "doc:music", "doc:maths", "doc:chemistry", "doc:braille", "doc:ancientscript",
// "media:image", "media:reel", "media:album", "media:video", "media:audio", "media:song", "media:subtitle", "media:overlay",
// "social:channel", "social:stream", "social:contact", "social:event", "social:calendar", "social:scheduler", "social:reaction", "social:chatroom",
// "prod:task", "prod:project", "prod:issue", "prod:form", "prod:filling", "prod:cad", "prod:slides", "prod:question", "prod:answer", "prod:poll", "prod:vote"
@ -165,35 +165,24 @@ export const official_classes = {
},
"ng:compat": ["rdfs:Class"],
},
"schema:rdfs": {
"schema": { // display with https://github.com/VisualDataWeb/WebVOWL
"ng:crdt": "Graph",
"ng:n": "Schema - RDFS",
"ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with RDFS",
"ng:o": "n:g:z:json_ld_viewer", // default viewer
"ng:n": "Schema - RDFS/OWL",
"ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with RDFS and/or OWL",
"ng:o": "n:g:z:ontology_viewer", // default viewer
"ng:w": "n:g:z:ontology_editor", // default editor
"ng:x": {
"rdfs":true,
},
"ng:include": ["data:graph"],
"ng:compat": ["rdfs:*","class"],
},
"schema:owl": { // display with https://github.com/VisualDataWeb/WebVOWL
"ng:crdt": "Graph",
"ng:n": "Schema - OWL",
"ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with OWL",
"ng:o": "n:g:z:owl_viewer", // default viewer
"ng:w": "n:g:z:ontology_editor", // default editor
"ng:x": {
"owl":true,
},
"ng:include": ["data:graph"],
"ng:compat": ["owl:Ontology"],
"ng:compat": ["rdfs:*","class","owl:Ontology"],
},
"schema:shacl": {
"ng:crdt": "Graph",
"ng:n": "Schema - SHACL",
"ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with SHACL",
"ng:o": "n:g:z:json_ld_viewer", // default viewer
"ng:a": "Define the rules for your data with SHACL",
"ng:o": "n:g:z:ontology_viewer", // default viewer
"ng:w": "n:g:z:ontology_editor", // default editor
"ng:x": {
"sh":true,
@ -204,8 +193,8 @@ export const official_classes = {
"schema:shex": {
"ng:crdt": "Graph",
"ng:n": "Schema - SHEX",
"ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with SHEX",
"ng:o": "n:g:z:json_ld_viewer", // default viewer
"ng:a": "Define the rules for your data with SHEX",
"ng:o": "n:g:z:ontology_viewer", // default viewer
"ng:w": "n:g:z:ontology_editor", // default editor
"ng:x": {
"shex":true,
@ -426,13 +415,13 @@ export const official_classes = {
"ng:n": "Link",
"ng:a": "Link to a document. kept in Magic Carpet",
},
"plato/card": {
"plato:card": {
"ng:crdt": "Graph",
"ng:n": "Card",
"ng:a": "Card representation of a document",
"ng:o": "n:g:z:card",
},
"plato/pad": {
"plato:pad": {
"ng:crdt": "Graph",
"ng:n": "Pad",
"ng:a": "Pad representation of a document",
@ -445,62 +434,62 @@ export const official_classes = {
"ng:o": "n:g:z:compose:viewer",
"ng:w": "n:g:z:compose:editor",
},
"doc:diagram:mermaid" : {
"diagram:mermaid" : {
"ng:crdt": "YText",
"ng:n": "Diagram - Mermaid",
"ng:a": "Describe Diagrams with Mermaid",
"ng:compat": ["file:iana:application:vnd.mermaid"]
},
"doc:diagram:drawio" : {
"diagram:drawio" : {
"ng:crdt": "YXml",
"ng:n": "Diagram - DrawIo",
"ng:a": "Draw Diagrams with DrawIo",
"ng:compat": ["file:iana:application:vnd.jgraph.mxfile","file:iana:application:x-drawio"]
},
"doc:diagram:graphviz" : {
"diagram:graphviz" : {
"ng:crdt": "YText",
"ng:n": "Diagram - Graphviz",
"ng:a": "Describe Diagrams with Graphviz",
"ng:compat": ["file:iana:text:vnd.graphviz"]
},
"doc:diagram:excalidraw" : {
"diagram:excalidraw" : {
"ng:crdt": "Automerge",
"ng:n": "Diagram - Excalidraw",
"ng:a": "Collaborate on Diagrams with Excalidraw",
"ng:compat": ["file:iana:application:vnd.excalidraw+json"]
},
"doc:diagram:gantt" : { //https://github.com/frappe/gantt
"diagram:gantt" : { //https://github.com/frappe/gantt
"ng:crdt": "Automerge",
"ng:n": "Diagram - Gantt",
"ng:a": "Interactive gantt chart",
"ng:compat": []
},
"doc:diagram:flowchart" : { //https://github.com/adrai/flowchart.js
"diagram:flowchart" : { //https://github.com/adrai/flowchart.js
"ng:crdt": "YText",
"ng:n": "Diagram - Flowchart",
"ng:a": "flow chart diagrams",
"ng:compat": []
},
"doc:diagram:sequence" : { //https://github.com/bramp/js-sequence-diagrams
"diagram:sequence" : { //https://github.com/bramp/js-sequence-diagrams
"ng:crdt": "YText",
"ng:n": "Diagram - Sequence",
"ng:a": "sequence diagrams",
"ng:compat": []
},
// checkout https://www.mindmaps.app/ but it is AGPL
"doc:diagram:markmap" : { //https://github.com/markmap/markmap
"diagram:markmap" : { //https://github.com/markmap/markmap
"ng:crdt": "YText",
"ng:n": "Diagram - Markmap",
"ng:a": "mindmaps with markmap",
"ng:compat": []
},
"doc:diagram:mymind" : { //https://github.com/markmap/markmap
"diagram:mymind" : { //https://github.com/markmap/markmap
"ng:crdt": "YText", // see MyMind format, MindMup JSON, FreeMind XML and MindMap Architect XML
"ng:n": "Diagram - Mymind",
"ng:a": "mindmaps with mymind",
"ng:compat": [] // https://github.com/ondras/my-mind/wiki/Saving-and-loading#file-formats
},
"doc:diagram:jsmind" : { //https://github.com/hizzgdev/jsmind
"diagram:jsmind" : { //https://github.com/hizzgdev/jsmind
"ng:crdt": "Automerge",
"ng:n": "Diagram - jsmind",
"ng:a": "mindmaps with jsmind",
@ -514,69 +503,69 @@ export const official_classes = {
// https://github.com/Rich-Harris/pancake
// https://github.com/williamngan/pts
// https://visjs.org/
"doc:viz:cytoscape" : {
"viz:cytoscape" : {
"ng:crdt": "Automerge",
"ng:n": "Viz - Cytoscape",
"ng:a": "Graph theory (network) visualization",
"ng:compat": [] // https://github.com/cytoscape/cytoscape.js
},
"doc:viz:vega" : {
"viz:vega" : {
"ng:crdt": "Automerge",
"ng:n": "Viz - Vega",
"ng:a": "Grammar for interactive graphics",
"ng:compat": [] // https://vega.github.io/vega-lite/docs/ https://github.com/vega/editor
},
"doc:viz:vizzu" : {
"viz:vizzu" : {
"ng:crdt": "Automerge",
"ng:n": "Viz - Vizzu",
"ng:a": "Animated data visualizations and data stories",
"ng:compat": [] // https://github.com/vizzuhq/vizzu-lib
},
"doc:viz:plotly" : { //https://github.com/plotly/plotly.js
"viz:plotly" : { //https://github.com/plotly/plotly.js
"ng:crdt": "Automerge",
"ng:n": "Viz - Plotly",
"ng:a": "Declarative charts",
"ng:compat": [] // https://github.com/cytoscape/cytoscape.js
},
"doc:viz:avail" : {
"viz:avail" : {
"ng:crdt": "Automerge",
"ng:n": "Viz - Avail",
"ng:a": "Time Data Availability Visualization",
"ng:compat": [] // https://github.com/flrs/visavail
},
"doc:chart:frappecharts" : {
"chart:frappecharts" : {
"ng:crdt": "Automerge",
"ng:n": "Charts - Frappe",
"ng:a": "GitHub-inspired responsive charts",
"ng:compat": [] // https://github.com/frappe/charts
},
"doc:chart:financial" : {
"chart:financial" : {
"ng:crdt": "Automerge",
"ng:n": "Charts - Financial",
"ng:a": "Financial charts",
"ng:compat": [] //https://github.com/tradingview/lightweight-charts
},
// have a look at https://github.com/cube-js/cube and https://awesome.cube.dev/ and https://frappe.io/products
"doc:chart:apexcharts" : {
"chart:apexcharts" : {
"ng:crdt": "Automerge",
"ng:n": "Charts - ApexCharts",
"ng:a": "Interactive data visualizations",
"ng:compat": [] // https://github.com/apexcharts/apexcharts.js
},
//realtime data with https://github.com/square/cubism
"doc:chart:billboard" : {
"chart:billboard" : {
"ng:crdt": "Automerge",
"ng:n": "Charts - BillBoard",
"ng:a": "Interactive data visualizations based on D3",
"ng:compat": [] // https://github.com/naver/billboard.js
},
"doc:chart:echarts" : {
"chart:echarts" : {
"ng:crdt": "Automerge",
"ng:n": "Charts - ECharts",
"ng:a": "Interactive charting and data visualization with Apache ECharts",
"ng:compat": [] // https://github.com/apache/echarts
},
"doc:chart:chartjs" : {
"chart:chartjs" : {
"ng:crdt": "Automerge",
"ng:n": "Charts - Chart.js",
"ng:a": "Simple yet flexible charting for designers & developers with Chart.js",

@ -14,7 +14,7 @@
branch_subscribe,
active_session,
cannot_load_offline,
online,
open_doc_popup
} from "../store";
import {
@ -42,7 +42,7 @@
const inview_options = {};//{rootMargin: "-44px"};
function openEditHeader() {
//TODO
open_doc_popup("header");
}
</script>

@ -34,7 +34,6 @@
import BranchIcon from "./icons/BranchIcon.svelte";
import Message from "./components/Message.svelte";
import Signature from "./popups/Signature.svelte";
import {
get,
} from "svelte/store";
@ -47,11 +46,11 @@
has_editor_chat, all_files_count, all_comments_count, hideMenu, show_modal_menu, show_modal_create,
cur_tab_branch_nuri, cur_tab_doc_can_edit, cur_tab_doc_is_member, cur_tab_right_pane, cur_tab_folders_pane,
cur_tab_toc_pane, cur_tab_show_menu, cur_tab_branch_has_discrete, cur_tab_graph_or_discrete, cur_tab_view_or_edit, show_spinner,
in_private_store, show_doc_popup, cur_doc_popup, open_doc_popup } from "../tab";
in_private_store, show_doc_popup, cur_doc_popup } from "../tab";
import {
active_session, redirect_after_login, toasts, check_has_camera, toast_error,
reset_toasts, redirect_if_wallet_is, active_wallet,
display_error, openModalCreate
display_error, openModalCreate, open_doc_popup
} from "../store";
import ZeraIcon from "./icons/ZeraIcon.svelte";
import ng from "../api";
@ -300,33 +299,33 @@
];
const create_chart_items = [
"doc:chart:frappecharts",
"doc:chart:financial",
"doc:chart:apexcharts",
"doc:chart:billboard",
"doc:chart:echarts",
"doc:chart:chartjs",
"chart:frappecharts",
"chart:financial",
"chart:apexcharts",
"chart:billboard",
"chart:echarts",
"chart:chartjs",
];
const create_viz_items = [
"doc:viz:cytoscape",
"doc:viz:vega",
"doc:viz:vizzu",
"doc:viz:plotly",
"doc:viz:avail",
"viz:cytoscape",
"viz:vega",
"viz:vizzu",
"viz:plotly",
"viz:avail",
];
const create_diagram_items = [
"doc:diagram:mermaid",
"doc:diagram:drawio",
"doc:diagram:graphviz",
"doc:diagram:excalidraw",
"doc:diagram:gantt",
"doc:diagram:flowchart",
"doc:diagram:sequence",
"doc:diagram:markmap",
"doc:diagram:mymind",
"doc:diagram:jsmind",
"diagram:mermaid",
"diagram:drawio",
"diagram:graphviz",
"diagram:excalidraw",
"diagram:gantt",
"diagram:flowchart",
"diagram:sequence",
"diagram:markmap",
"diagram:mymind",
"diagram:jsmind",
];
const create_doc_items = [
@ -367,6 +366,19 @@
"app:n:xxx.xx.xx",
];
import Signature from "./popups/Signature.svelte";
import Header from "./popups/Header.svelte";
const doc_popups = {
"signature": Signature,
"header": Header,
}
const doc_popups_size = {
"signature": "xs",
"header": "md",
}
let top;
let shareMenu;
let toolsMenu;
@ -428,7 +440,7 @@
const openAction = (action:string) => {
hideMenu();
if (doc_popups[action]) open_doc_popup(action);
open_doc_popup(action);
}
const openPane = (pane:string) => {
@ -564,10 +576,6 @@
"mc":Sparkles,
};
const doc_popups = {
"signature": Signature,
}
let destination = "store";
$: destination = $cur_tab_branch_nuri === "" ? "mc" : destination == "mc" ? "store" : destination;
@ -755,7 +763,8 @@
{/if}
{#if $cur_tab_doc_can_edit}
<MenuItem title={$t("doc.menu.items.new_block.desc")} clickable={ ()=> openAction("new_block") }>
<!-- ()=> openAction("new_block") -->
<MenuItem title={$t("doc.menu.items.new_block.desc")} clickable={ undefined }>
<PlusCircle
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
@ -764,7 +773,8 @@
</MenuItem>
{/if}
{#if $has_editor_chat}
<MenuItem title={$t("doc.menu.items.editor_chat.desc")} selected={$cur_tab_right_pane == "chat"} clickable={ ()=> openPane("chat") }>
<!-- ()=> openPane("chat") -->
<MenuItem title={$t("doc.menu.items.editor_chat.desc")} selected={$cur_tab_right_pane == "chat"} clickable={ undefined }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["chat"]} />
<span class="ml-3">{$t("doc.menu.items.editor_chat.label")}</span>
</MenuItem>
@ -794,14 +804,16 @@
</MenuItem>
{#if open_share }
{#each share_items as share}
<MenuItem title={$t(`doc.menu.items.${share.n}.desc`)} extraClass="submenu" clickable={ () => openShare(share.n) }>
<!-- () => openShare(share.n) -->
<MenuItem title={$t(`doc.menu.items.${share.n}.desc`)} extraClass="submenu" clickable={ undefined }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white " variation="outline" color="currentColor" icon={share.i} />
<span class="ml-3">{$t(`doc.menu.items.${share.n}.label`)}</span>
</MenuItem>
{/each}
{/if}
{:else}
<MenuItem title={$t(`doc.menu.items.download.desc`)} clickable={ () => openShare("download") }>
<!-- () => openShare("download") -->
<MenuItem title={$t(`doc.menu.items.download.desc`)} clickable={ undefined }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white " variation="outline" color="currentColor" icon={DocumentArrowDown} />
<span class="ml-3">{$t(`doc.menu.items.download.label`)}</span>
</MenuItem>
@ -823,37 +835,37 @@
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["history"]} />
<span class="ml-3">{$t("doc.menu.items.history.label")}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.find.desc")} clickable={ find }>
<!-- find -->
<MenuItem title={$t("doc.menu.items.find.desc")} clickable={ undefined }>
<MagnifyingGlass
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.find.label")}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.bookmark.desc")} clickable={ bookmark }>
<!-- bookmark -->
<MenuItem title={$t("doc.menu.items.bookmark.desc")} clickable={ undefined }>
<Bookmark
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.bookmark.label")}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.annotate.desc")} clickable={ annotate }>
<!-- annotate -->
<MenuItem title={$t("doc.menu.items.annotate.desc")} clickable={ undefined }>
<ChatBubbleLeftEllipsis
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.annotate.label")}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.info.desc")} selected={$cur_tab_right_pane == "info"} clickable={ ()=> openPane("info") }>
<!-- ()=> openPane("info") -->
<MenuItem title={$t("doc.menu.items.info.desc")} selected={$cur_tab_right_pane == "info"} clickable={ undefined }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["info"]} />
<span class="ml-3">{$t("doc.menu.items.info.label")}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.notifs.desc")} clickable={ ()=> openAction("notifs") }>
<!-- ()=> openAction("notifs") -->
<MenuItem title={$t("doc.menu.items.notifs.desc")} clickable={ undefined }>
<Bell
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
@ -861,7 +873,8 @@
<span class="ml-3">{$t("doc.menu.items.notifs.label")}</span>
</MenuItem>
{#if $cur_tab_doc_is_member && !$in_private_store}
<MenuItem title={$t("doc.menu.items.permissions.desc")} clickable={ ()=> openAction("permissions") }>
<!-- ()=> openAction("permissions") -->
<MenuItem title={$t("doc.menu.items.permissions.desc")} clickable={ undefined }>
<LockOpen
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
@ -869,7 +882,8 @@
<span class="ml-3">{$t("doc.menu.items.permissions.label")}</span>
</MenuItem>
{/if}
<MenuItem title={$t("doc.menu.items.settings.desc")} clickable={ ()=> openAction("settings") }>
<!-- ()=> openAction("settings") -->
<MenuItem title={$t("doc.menu.items.settings.desc")} clickable={ undefined }>
<Cog6Tooth
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
@ -887,7 +901,8 @@
{#if open_tools }
{#each tools_items as tool}
{#if !$in_private_store || tool.n !== "signature" }
<MenuItem title={$t(`doc.menu.items.${tool.n}.desc`)} extraClass="submenu" clickable={ () => openAction(tool.n) }>
<!-- () => openAction(tool.n) -->
<MenuItem title={$t(`doc.menu.items.${tool.n}.desc`)} extraClass="submenu" clickable={ tool.n === "signature" ? () => openAction(tool.n) : undefined }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white " variation="outline" color="currentColor" icon={tool.i} />
<span class="ml-3">{$t(`doc.menu.items.${tool.n}.label`)}</span>
</MenuItem>
@ -895,11 +910,13 @@
{/each}
{/if}
{/if}
<MenuItem title={$t("doc.menu.items.mc.desc")} selected={$cur_tab_right_pane == "mc"} clickable={ ()=> openPane("mc") }>
<!-- ()=> openPane("mc") -->
<MenuItem title={$t("doc.menu.items.mc.desc")} selected={$cur_tab_right_pane == "mc"} clickable={ undefined }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["mc"]} />
<span class="ml-3">{$t("doc.menu.items.mc.label")}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.archive.desc")} clickable={ ()=> openArchive() }>
<!-- ()=> openArchive() -->
<MenuItem title={$t("doc.menu.items.archive.desc")} clickable={ undefined }>
<ArchiveBox
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
@ -945,12 +962,12 @@
<Modal class="document-popup"
outsideclose
bind:open={$show_doc_popup}
size = 'xs'
size = {doc_popups_size[$cur_doc_popup]}
placement = 'center'
defaultClass="bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-400 rounded-lg border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md relative flex flex-col mx-auto w-full"
backdropClass="bg-gray-900 bg-opacity-50 dark:bg-opacity-80 popup-bg-modal"
>
<svelte:component this={doc_popups[$cur_doc_popup]}/>
{#if doc_popups[$cur_doc_popup]}<svelte:component this={doc_popups[$cur_doc_popup]}/>{/if}
</Modal>
<Modal class="menu-modal"
outsideclose
@ -1121,7 +1138,7 @@
<div style="padding:0;" bind:this={createMenu.chart}></div>
<MenuItem title={$t("doc.chart")} dropdown={createMenuOpened.chart} clickable={ () => { createMenuOpened.chart = !createMenuOpened.chart; scrollToCreateMenu("chart"); } }>
<DataClassIcon dataClass="doc:chart" {config}/>
<DataClassIcon dataClass="chart" {config}/>
<span class="ml-3">{$t("doc.chart")}</span>
</MenuItem>
{#if createMenuOpened.chart }
@ -1136,7 +1153,7 @@
<div style="padding:0;" bind:this={createMenu.viz}></div>
<MenuItem title={$t("doc.viz")} dropdown={createMenuOpened.viz} clickable={ () => { createMenuOpened.viz = !createMenuOpened.viz; scrollToCreateMenu("viz"); } }>
<DataClassIcon dataClass="doc:viz" {config}/>
<DataClassIcon dataClass="viz" {config}/>
<span class="ml-3">{$t("doc.viz")}</span>
</MenuItem>
{#if createMenuOpened.viz }
@ -1151,7 +1168,7 @@
<div style="padding:0;" bind:this={createMenu.diagram}></div>
<MenuItem title={$t("doc.diagram")} dropdown={createMenuOpened.diagram} clickable={ () => { createMenuOpened.diagram = !createMenuOpened.diagram; scrollToCreateMenu("diagram"); } }>
<DataClassIcon dataClass="doc:diagram" {config}/>
<DataClassIcon dataClass="diagram" {config}/>
<span class="ml-3">{$t("doc.diagram")}</span>
</MenuItem>
{#if createMenuOpened.diagram }

@ -116,8 +116,8 @@
"e:link": Link,
"mc:text": Bars3BottomLeft,
"mc:link": Link,
"plato/card": Clipboard,
"plato/pad": Square2Stack,
"plato:card": Clipboard,
"plato:pad": Square2Stack,
"media:image": Photo,
"media:reel": VideoCamera,
"media:video": Film,
@ -171,9 +171,9 @@
"app:": StopCircle,
"query:": RocketLaunch,
"data:": CircleStack,
"doc:diagram": DocumentChartBar,
"doc:chart": ChartPie,
"doc:viz": ArrowTrendingUp,
"diagram": DocumentChartBar,
"chart": ChartPie,
"viz": ArrowTrendingUp,
"doc:": ClipboardDocumentList,
file: Document,
};

@ -0,0 +1,70 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
branch_subscribe,
active_session,
toast_error,
toast_success,
display_error,
online,
change_header
} from "../../store";
import {
cur_tab,
show_doc_popup
} from "../../tab";
import { get } from "svelte/store";
import { onMount, onDestroy, tick } from "svelte";
import ng from "../../api";
import { t } from "svelte-i18n";
import {
CheckCircle
} from "svelte-heros-v2";
import {
} from "flowbite-svelte";
let is_tauri = import.meta.env.TAURI_PLATFORM;
onMount(()=>{
title = $cur_tab.doc.title;
about = $cur_tab.doc.description;
});
async function update_header() {
await change_header(title,about);
$show_doc_popup = false;
}
let title;
let about;
</script>
<div class="flex flex-col">
<span class="font-bold text-xl mb-3">{$t("doc.header.buttons.edit_intro")}</span>
{$t("doc.header.doc.title")} :
<input placeholder="Enter the title of the document" bind:value={title} class="mb-3"/>
{$t("doc.header.doc.about")} :
<textarea rows=6 class="my-4 col-span-6 pr-11 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Enter the introduction" bind:value={about}/>
<button
style="width:120px;"
on:click|once={update_header}
class="mt-4 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-3 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<CheckCircle class="w-8 h-8 mr-3"/>
{$t("doc.header.buttons.save")}
</button>
</div>

@ -21,7 +21,7 @@
"chart": "Chart",
"viz": "Visualization",
"diagram": "Diagram",
"other": "Other file formats",
"other": "Other formats",
"data": "Data",
"code": "Code",
"apps": "Apps",
@ -71,7 +71,16 @@
"groups": "Groups",
"channels": "Channels",
"inbox": "Inbox",
"chat": "Chat"
"chat": "Chat",
"save": "Save"
},
"doc":{
"title": "Title",
"about": "Introduction"
},
"profile": {
"title": "Name",
"about": "Bio"
}
},
"signature": {

@ -357,7 +357,7 @@
{/if}
<!-- Remove Wallet -->
<li
<!-- <li
tabindex="0"
role="menuitem"
class="text-left flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
@ -372,7 +372,7 @@
</div>
<span class="ml-3">{$t("pages.wallet_info.remove_wallet")}</span
>
</li>
</li> -->
<!-- Confirm Remove Wallet Modal -->
<Modal
autoclose

@ -147,6 +147,9 @@
async function gotWallet(event) {
try {
if (importing) {
step = "loggedin";
$redirect_after_login=undefined;
$redirect_if_wallet_is=undefined;
let in_memory = !event.detail.trusted;
//console.log("IMPORTING", in_memory, event.detail.wallet, wallet);
let client = await ng.wallet_import(
@ -188,6 +191,7 @@
if (importing) {wallet = undefined;}
importing = false;
error = e;
step = "open";
return;
}
//await tick();
@ -252,6 +256,25 @@
{$t("buttons.start_over")}
</button>
</div>
{:else if step == "loggedin"}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-green-800">
{@html $t("pages.wallet_login.logged_in")}...
<svg
class="my-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z"
/>
</svg>
</div>
{:else if $wallet_from_import}
<!-- Imported a wallet -->
@ -437,25 +460,6 @@
</a>
</div>
</div>
{:else if step == "loggedin"}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-green-800">
{@html $t("pages.wallet_login.logged_in")}...
<svg
class="my-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z"
/>
</svg>
</div>
{/if}
</CenteredLayout>
</div>

@ -17,7 +17,7 @@ import {
} from "svelte/store";
import { register, init, locale, format } from "svelte-i18n";
import ng from "./api";
import { persistent_error, update_class, update_branch_display, open_branch, tab_update, change_nav_bar, cur_branch, cur_tab, show_modal_create, save, nav_bar,in_memory_save } from "./tab";
import { persistent_error, update_class, update_branch_display, open_branch, tab_update, change_nav_bar, cur_branch, cur_tab, show_modal_create, save, nav_bar,in_memory_save, cur_doc_popup, show_doc_popup } from "./tab";
import { encode } from "./base64url";
let all_branches = {};
@ -423,6 +423,43 @@ export const discrete_update = async (update, crdt, heads) => {
// the editor then process those updates and calls live_discrete_update
}
export const open_doc_popup = async (popup_name) => {
await reset_toasts();
cur_doc_popup.set(popup_name);
show_doc_popup.set(true);
}
export const change_header = async (title_, about_) => {
let session = get(active_session);
if (!session) {
persistent_error(get(cur_branch), {
title: get(format)("doc.errors.no_session"),
desc: get(format)("doc.errors_details.no_session")
});
throw new Error("no session");
}
let nuri = "did:ng:"+get(cur_tab).doc.nuri+":"+get(cur_tab).store.overlay;
let title = undefined;
let about = undefined;
if ( get(cur_tab).doc.title != title_ ) {
title = title_;
}
if ( get(cur_tab).doc.description != about_ ) {
about = about_;
}
if (title === undefined && about === undefined) {
//console.log("identical");
return;
}
try {
await ng.update_header(session.session_id, nuri, title, about);
}
catch (e) {
toast_error(display_error(e));
}
}
export const live_discrete_update = async (update, crdt, heads) => {
// send directly to verifier with AppRequest Update
let session = get(active_session);
@ -552,7 +589,7 @@ export const branch_subscribe = function(nuri:string, in_tab:boolean) {
req.V0.session_id = session.session_id;
unsub = await ng.app_request_stream(req,
async (response) => {
//console.log("GOT APP RESPONSE", response);
console.log("GOT APP RESPONSE", response);
if (response.V0.TabInfo) {
tab_update(nuri, ($cur_tab) => {
if (response.V0.TabInfo.branch?.id) {
@ -568,15 +605,21 @@ export const branch_subscribe = function(nuri:string, in_tab:boolean) {
if (response.V0.TabInfo.doc?.nuri) {
$cur_tab.doc.nuri = response.V0.TabInfo.doc.nuri;
}
if (response.V0.TabInfo.doc?.can_edit) {
if (typeof response.V0.TabInfo.doc?.can_edit === "boolean" ) {
$cur_tab.doc.can_edit = response.V0.TabInfo.doc.can_edit;
}
if (response.V0.TabInfo.doc?.is_store) {
if (typeof response.V0.TabInfo.doc?.is_store === "boolean") {
$cur_tab.doc.is_store = response.V0.TabInfo.doc.is_store;
}
if (response.V0.TabInfo.doc?.is_member) {
$cur_tab.doc.is_member = response.V0.TabInfo.doc.is_member;
}
if (response.V0.TabInfo.doc?.title !== undefined && response.V0.TabInfo.doc?.title !== null) {
$cur_tab.doc.title = response.V0.TabInfo.doc.title;
}
if (response.V0.TabInfo.doc?.description !== undefined && response.V0.TabInfo.doc?.description !== null) {
$cur_tab.doc.description = response.V0.TabInfo.doc.description;
}
if (response.V0.TabInfo.store?.overlay) {
$cur_tab.store.overlay = response.V0.TabInfo.store.overlay;
}
@ -643,13 +686,17 @@ export const branch_subscribe = function(nuri:string, in_tab:boolean) {
onUpdate(response.V0.Patch.discrete);
}
if (response.V0.Patch.graph) {
//console.log(response.V0.Patch.graph)
let duplicates = [];
for (let i = 0; i < old.graph.length; i++) {
if (response.V0.Patch.graph.inserts.includes(old.graph[i])) {
duplicates.push(old.graph[i])
} else
if (response.V0.Patch.graph.removes.includes(old.graph[i])) {//TODO: optimization: remove this triple from the removes list.
old.graph.splice(i, 1);
} else {
//console.log("remove?", i, old.graph[i], JSON.stringify(old.graph))
if (response.V0.Patch.graph.removes.includes(old.graph[i])) {//TODO: optimization: remove this triple from the removes list.
old.graph.splice(i, 1);
//console.log("yes",JSON.stringify(old.graph))
}
}
}
for (const insert of response.V0.Patch.graph.inserts){

@ -220,13 +220,17 @@ td.hljs {
.container3 {
margin: 0;
min-width: 280px;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}
.container3 aside {
width: 20rem !important;
}
div[role="alert"] div {
display: block;
}

@ -208,11 +208,6 @@ export const show_doc_popup = writable(false);
export const cur_doc_popup = writable("");
export const show_modal_create = writable(false);
export const open_doc_popup = (popup_name) => {
cur_doc_popup.set(popup_name);
show_doc_popup.set(true);
}
export const in_memory_graph = writable("");
export const in_memory_discrete = writable("");
@ -351,7 +346,7 @@ export const cur_branch = writable("");
export const cur_tab = derived([cur_branch, all_tabs], ([cb, all]) => {return all[cb];});
export const can_have_header = derived(cur_tab, ($cur_tab) => {
return !($cur_tab.doc.is_store && ( $cur_tab.store.store_type === "private" || $cur_tab.store.store_type === "dialog"));
return !($cur_tab.doc.is_store); // && ( $cur_tab.store.store_type === "private" || $cur_tab.store.store_type === "dialog"));
});
export const cur_tab_branch_nuri = derived(cur_tab, ($cur_tab) => {
return $cur_tab.branch.nuri;

@ -210,16 +210,16 @@ export const official_apps = {
"ng:g": "n:g:z:ontology_editor",
"ng:b": "JsonLdEditor",
"ng:o": [],
"ng:w": ["schema:*"],
"ng:w": ["schema*"],
},
"n:g:z:owl_viewer": {
"ng:n": "OWL Ontology",
"n:g:z:ontology_viewer": {
"ng:n": "Ontology",
"ng:a": "View the OWL Ontology",
"ng:c": "app",
"ng:u": "ontology_viewer",//favicon. can be a did:ng:j
"ng:g": "n:g:z:owl_viewer",
"ng:b": "OwlViewer", // display with https://github.com/VisualDataWeb/WebVOWL
"ng:o": ["schema:owl"],
"ng:g": "n:g:z:ontology_viewer",
"ng:b": "OntologyViewer", // display with https://github.com/VisualDataWeb/WebVOWL
"ng:o": ["schema*"],
"ng:w": [],
},
"n:g:z:sparql:invoke": { // displayed with highlight.js https://github.com/highlightjs/highlightjs-turtle/tree/master
@ -313,7 +313,7 @@ export const official_apps = {
"ng:u": "source",//favicon. can be a did:ng:j
"ng:g": "n:g:z:crdt_source_viewer:xml",
"ng:b": "XmlSource", // displayed with highlight.js , with option to download
"ng:o": ["post:rich","post:md","post:html","page","data:xml", "doc:diagram:drawio"],
"ng:o": ["post:rich","post:md","post:html","page","data:xml", "diagram:drawio"],
"ng:w": [],
implemented: true,
},
@ -335,7 +335,7 @@ export const official_apps = {
"ng:u": "source",//favicon. can be a did:ng:j
"ng:g": "n:g:z:crdt_source_viewer:json",
"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:o": ["data:json", "data:table", "diagram:jsmind", "diagram:gantt", "diagram:excalidraw", "viz:*", "chart:*", "prod:cad"],
"ng:w": [],
"full_width": true,
implemented: true,
@ -369,8 +369,8 @@ export const official_apps = {
"ng:u": "source",//favicon. can be a did:ng:j
"ng:g": "n:g:z:crdt_source_viewer:text",
"ng:b": "TextViewer", // displayed with highlight.js , with option to download and copy paste
"ng:o": ["post:asciidoc", "service*", "contract", "query:sparql*","query:graphql","doc:diagram:mermaid","doc:diagram:graphviz","doc:diagram:flowchart",
"doc:diagram:sequence","doc:diagram:markmap","doc:diagram:mymind","doc:music*", "doc:maths", "doc:chemistry", "doc:ancientscript", "doc:braille", "media:subtitle"],
"ng:o": ["post:asciidoc", "service*", "contract", "query:sparql*","query:graphql","diagram:mermaid","diagram:graphviz","diagram:flowchart",
"diagram:sequence","diagram:markmap","diagram:mymind","doc:music*", "doc:maths", "doc:chemistry", "doc:ancientscript", "doc:braille", "media:subtitle"],
"ng:w": [],
implemented: true,
},
@ -454,7 +454,7 @@ export const official_apps = {
"ng:u": "pad",//favicon. can be a did:ng:j
"ng:g": "n:g:z:pad",
"ng:b": "Pad",
"ng:o": ["plato/pad"],
"ng:o": ["plato:pad"],
"ng:w": [],
},
"n:g:z:card": {
@ -464,7 +464,7 @@ export const official_apps = {
"ng:u": "card",//favicon. can be a did:ng:j
"ng:g": "n:g:z:card",
"ng:b": "Card",
"ng:o": ["plato/card"],
"ng:o": ["plato:card"],
"ng:w": [],
},
"n:g:z:gallery": {
@ -673,7 +673,7 @@ export const official_services = {
"ng:c": "service",
"ng:u": "data",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_json",
"ng:o": ["data:json", "data:map", "data:array", "data:table", "doc:diagram:jsmind", "doc:diagram:gantt", "doc:diagram:excalidraw", "doc:viz:*", "doc:chart:*", "prod:cad"],
"ng:o": ["data:json", "data:map", "data:array", "data:table", "diagram:jsmind", "diagram:gantt", "diagram:excalidraw", "viz:*", "chart:*", "prod:cad"],
"ng:w": [],
"ng:result": ["file:iana:application:json"],
},
@ -683,7 +683,7 @@ export const official_services = {
"ng:c": "service",
"ng:u": "data",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_xml",
"ng:o": ["post:rich","post:md","post:html","page","data:xml", "doc:diagram:drawio"],
"ng:o": ["post:rich","post:md","post:html","page","data:xml", "diagram:drawio"],
"ng:w": [],
"ng:result": ["file:iana:text:xml"],
},
@ -693,8 +693,8 @@ export const official_services = {
"ng:c": "service",
"ng:u": "dump",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_text",
"ng:o": ["post:text", "post:asciidoc", "code*", "service*", "contract", "query:sparql*","query:graphql","doc:diagram:mermaid","doc:diagram:graphviz","doc:diagram:flowchart",
"doc:diagram:sequence","doc:diagram:markmap","doc:diagram:mymind","doc:music*", "doc:maths", "doc:chemistry", "doc:ancientscript", "doc:braille", "media:subtitle"],
"ng:o": ["post:text", "post:asciidoc", "code*", "service*", "contract", "query:sparql*","query:graphql","diagram:mermaid","diagram:graphviz","diagram:flowchart",
"diagram:sequence","diagram:markmap","diagram:mymind","doc:music*", "doc:maths", "doc:chemistry", "doc:ancientscript", "doc:braille", "media:subtitle"],
"ng:w": [],
"ng:result": ["file:iana:text:plain"],
},

@ -60,6 +60,7 @@ pub enum AppFetchContentV0 {
SignatureStatus,
SignatureRequest,
SignedSnapshotRequest,
Header,
//Invoke,
}
@ -261,6 +262,14 @@ impl NuriV0 {
format!("{DID_PREFIX}:o:{repo_id}:v:{overlay_id}")
}
pub fn branch_repo_graph_name(
branch_id: &BranchId,
repo_id: &RepoId,
overlay_id: &OverlayId,
) -> String {
format!("{DID_PREFIX}:o:{repo_id}:v:{overlay_id}:b:{branch_id}")
}
pub fn repo_skolem(
repo_id: &RepoId,
peer_id: &Vec<u8>,
@ -576,6 +585,7 @@ pub enum AppRequestCommandV0 {
Create,
FileGet, // needs the Nuri of branch/doc/store AND ObjectId
FilePut, // needs the Nuri of branch/doc/store
Header,
}
impl AppRequestCommandV0 {
@ -587,6 +597,7 @@ impl AppRequestCommandV0 {
| Self::Delete
| Self::UnPin
| Self::Pin
| Self::Header
| Self::Fetch(_) => false,
}
}
@ -617,6 +628,12 @@ impl AppRequestCommandV0 {
pub fn new_create() -> Self {
AppRequestCommandV0::Create
}
pub fn new_header() -> Self {
AppRequestCommandV0::Header
}
pub fn new_fetch_header() -> Self {
AppRequestCommandV0::Fetch(AppFetchContentV0::Header)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -793,6 +810,12 @@ pub struct DocAddFile {
pub object: ObjectRef,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DocHeader {
pub title: Option<String>,
pub about: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DocCreateDestination {
Store,
@ -836,8 +859,10 @@ pub enum AppRequestPayloadV0 {
SmallFilePut(SmallFile),
RandomAccessFilePut(String), // content_type (iana media type)
RandomAccessFilePutChunk((u32, serde_bytes::ByteBuf)), // end the upload with an empty vec
//RemoveFile
//Invoke(InvokeArguments),
Header(DocHeader),
//RemoveFile
//Invoke(InvokeArguments),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -849,6 +874,9 @@ impl AppRequestPayload {
pub fn new_sparql_query(sparql: String, base: Option<String>) -> Self {
AppRequestPayload::V0(AppRequestPayloadV0::Query(DocQuery::V0 { sparql, base }))
}
pub fn new_header(title: Option<String>, about: Option<String>) -> Self {
AppRequestPayload::V0(AppRequestPayloadV0::Header(DocHeader { title, about }))
}
pub fn new_discrete_update(
head_strings: Vec<String>,
crdt: String,
@ -1019,6 +1047,22 @@ pub struct AppTabDocInfo {
//TODO branches
}
impl AppTabDocInfo {
pub fn new() -> Self {
AppTabDocInfo {
nuri: None,
is_store: None,
is_member: None,
title: None,
icon: None,
description: None,
authors: None,
inbox: None,
can_edit: None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AppTabBranchInfo {
pub id: Option<String>, //+
@ -1034,6 +1078,13 @@ pub struct AppTabInfo {
pub store: Option<AppTabStoreInfo>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AppHeader {
pub about: Option<String>,
pub title: Option<String>,
pub class: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AppResponseV0 {
SessionStart(AppSessionStartResponse),
@ -1059,6 +1110,7 @@ pub enum AppResponseV0 {
Error(String),
EndOfStream,
Nuri(String),
Header(AppHeader),
}
#[derive(Clone, Debug, Serialize, Deserialize)]

@ -16,7 +16,7 @@ use crate::oxigraph::storage::numeric_encoder::{
};
use crate::oxigraph::storage::{MatchBy, StorageError, StorageReader};
use crate::oxigraph::store::CorruptionError;
use crate::oxrdf::GraphName;
use crate::oxrdf::{GraphName, NamedNodeRef};
use crate::sparopt::algebra::NamedNode;
use std::cell::RefCell;
@ -43,30 +43,44 @@ impl Iterator for ErrorIterator {
impl DatasetView {
pub fn new(
reader: StorageReader,
dataset: &QueryDataset,
query_dataset: &QueryDataset,
default_graph: &Option<String>,
) -> Self {
let dataset = EncodedDatasetSpec {
default: if dataset.has_no_default_dataset() && default_graph.is_some() {
default: if query_dataset.has_no_default_dataset() && default_graph.is_some() {
Some(vec![GraphName::NamedNode(NamedNode::new_unchecked(
default_graph.to_owned().unwrap(),
))
.as_ref()
.into()])
} else {
dataset
query_dataset
.default_graph_graphs()
.map(|graphs| graphs.iter().map(|g| g.as_ref().into()).collect::<Vec<_>>())
},
named: dataset
named: query_dataset
.available_named_graphs()
.map(|graphs| graphs.iter().map(|g| g.as_ref().into()).collect::<Vec<_>>()),
};
Self {
let res = Self {
reader,
extra: RefCell::new(HashMap::default()),
dataset,
};
if let Some(default_graph) = default_graph {
res.encode_term(NamedNodeRef::new_unchecked(default_graph));
}
if !query_dataset.has_no_default_dataset() {
query_dataset.default_graph_graphs().map(|graphs| {
graphs.iter().for_each(|g| match g {
GraphName::NamedNode(nn) => {
let _a = res.encode_term(nn);
}
_ => {}
})
});
}
res
}
fn parse_graph_name(&self, graph_name: &EncodedTerm) -> Result<MatchBy, StorageError> {

@ -2185,12 +2185,14 @@ impl<'a> StorageWriter<'a> {
let value = [value];
self.buffer.clear();
write_spog_quad(&mut self.buffer, encoded);
let result = if self
.transaction
.contains_key_for_update(&self.storage.spog_cf, &self.buffer)?
let result =
// if self
// .transaction
// .contains_key_for_update(&self.storage.spog_cf, &self.buffer)?
// {
// false
// } else
{
false
} else {
self.transaction
.insert(&self.storage.spog_cf, &self.buffer, &value)?;

@ -87,7 +87,7 @@ impl UserInfo {
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct BranchInfo {
pub id: BranchId,
@ -641,6 +641,15 @@ impl Repo {
None
}
pub fn header_branch(&self) -> Option<&BranchInfo> {
for (_, branch) in self.branches.iter() {
if branch.branch_type == BranchType::Header {
return Some(branch);
}
}
None
}
pub fn root_branch(&self) -> Option<&BranchInfo> {
for (_, branch) in self.branches.iter() {
if branch.branch_type == BranchType::Root {

@ -220,8 +220,6 @@ impl Store {
)?;
let branch_read_cap = branch_commit.reference().unwrap();
//log_debug!("{:?} BRANCH COMMIT {}", branch_type, branch_commit);
// creating the AddBranch commit (on root_branch), deps to the RootBranch commit
// author is the owner
@ -247,12 +245,6 @@ impl Store {
self,
)?;
// log_debug!(
// "ADD_BRANCH {:?} BRANCH COMMIT {}",
// &branch_type,
// add_branch_commit
// );
let branch_info = BranchInfo {
id: branch_pub_key,
branch_type,
@ -369,7 +361,6 @@ impl Store {
&self,
)?;
//log_debug!("ROOT_BRANCH COMMIT {}", root_branch_commit);
let root_branch_readcap = root_branch_commit.reference().unwrap();
let root_branch_readcap_id = root_branch_readcap.id;
// adding the 2 events for the Repository and Rootbranch commits
@ -378,6 +369,32 @@ impl Store {
events.push((root_branch_commit, vec![]));
// creating the header branch
let (header_add_branch_commit, header_branch_info, next_dep) = if !is_private_store {
let (header_branch_commit, header_add_branch_commit, header_branch_info) =
self.as_ref().create_branch(
BranchType::Header,
BranchCrdt::Graph(branch_crdt.as_ref().unwrap().class().clone()),
creator,
creator_priv_key,
repo_pub_key,
repository_commit_ref.clone(),
root_branch_readcap_id,
&repo_write_cap_secret,
vec![root_branch_readcap.clone()],
vec![],
)?;
let header_add_branch_readcap = header_add_branch_commit.reference().unwrap();
events_postponed.push((header_branch_commit, vec![]));
(
Some(header_add_branch_commit),
Some(header_branch_info),
header_add_branch_readcap,
)
} else {
(None, None, root_branch_readcap.clone())
};
// creating the main branch
let (main_branch_commit, main_add_branch_commit, main_branch_info) =
@ -390,10 +407,9 @@ impl Store {
repository_commit_ref.clone(),
root_branch_readcap_id,
&repo_write_cap_secret,
vec![root_branch_readcap.clone()],
vec![next_dep],
vec![],
)?;
events_postponed.push((main_branch_commit, vec![]));
// TODO: optional AddMember and AddPermission, that should be added as deps to the SynSignature below (and to the commits of the SignatureContent)
@ -414,7 +430,6 @@ impl Store {
vec![main_add_branch_commit.reference().unwrap()],
vec![],
)?;
events_postponed.push((store_branch_commit, vec![]));
// creating the overlay or user branch
@ -467,7 +482,20 @@ impl Store {
// creating signature for RootBranch, AddBranch and Branch commits
// signed with owner threshold signature (threshold = 0)
let mut signed_commits = vec![main_branch_info.read_cap.as_ref().unwrap().id];
let mut signed_commits = if header_branch_info.is_some() {
vec![
header_branch_info
.as_ref()
.unwrap()
.read_cap
.as_ref()
.unwrap()
.id,
main_branch_info.read_cap.as_ref().unwrap().id,
]
} else {
vec![main_branch_info.read_cap.as_ref().unwrap().id]
};
if let Some((_, store_branch, oou_add_branch, oou_branch)) = &extra_branches {
signed_commits.append(&mut vec![
@ -563,16 +591,32 @@ impl Store {
&self,
)?;
let mut branches = vec![(main_branch_info.id, main_branch_info)];
let mut branches = if header_branch_info.is_some() {
vec![
(
header_branch_info.as_ref().unwrap().id,
header_branch_info.unwrap(),
),
(main_branch_info.id, main_branch_info),
]
} else {
vec![(main_branch_info.id, main_branch_info)]
};
// adding the event for the sync_sig_on_root_branch_commit
let mut additional_blocks = Vec::with_capacity(
cert_obj_blocks.len() + sig_obj_blocks.len() + main_add_branch_commit.blocks().len(),
);
let mut capacity =
cert_obj_blocks.len() + sig_obj_blocks.len() + main_add_branch_commit.blocks().len();
if header_add_branch_commit.is_some() {
capacity += header_add_branch_commit.as_ref().unwrap().blocks().len()
}
let mut additional_blocks = Vec::with_capacity(capacity);
additional_blocks.extend(cert_obj_blocks.iter());
additional_blocks.extend(sig_obj_blocks.iter());
additional_blocks.extend(main_add_branch_commit.blocks().iter());
if header_add_branch_commit.is_some() {
additional_blocks.extend(header_add_branch_commit.unwrap().blocks().iter());
}
if let Some((store_add_branch, store_branch_info, oou_add_branch, oou_branch_info)) =
extra_branches
{
@ -582,7 +626,7 @@ impl Store {
branches.push((oou_branch_info.id, oou_branch_info));
}
// creating the SyncSignature for all 3 branches with deps to the Branch commit and acks also to this commit as it is its direct causal future.
// creating the SyncSignature for all (2+ optional 2) branches with deps to the Branch commit and acks also to this commit as it is its direct causal future.
for (branch_id, branch_info) in &mut branches {
let sync_sig_on_branch_commit = Commit::new_with_body_acks_deps_and_save(
@ -600,12 +644,10 @@ impl Store {
// adding the event for the sync_sig_on_branch_commit
let mut additional_blocks =
Vec::with_capacity(cert_obj_blocks.len() + sig_obj_blocks.len());
additional_blocks.extend(cert_obj_blocks.iter());
additional_blocks.extend(sig_obj_blocks.iter());
events_postponed.push((sync_sig_on_branch_commit, additional_blocks));
let mut additional = Vec::with_capacity(cert_obj_blocks.len() + sig_obj_blocks.len());
additional.extend(cert_obj_blocks.iter());
additional.extend(sig_obj_blocks.iter());
events_postponed.push((sync_sig_on_branch_commit, additional));
branch_info.current_heads = vec![sync_sig_on_branch_commit_ref];

@ -810,6 +810,17 @@ impl fmt::Display for StoreRepo {
}
impl StoreRepo {
pub fn from_type_and_repo(store_type: &String, repo_id_str: &String) -> Result<Self, NgError> {
let repo_id: RepoId = repo_id_str.as_str().try_into()?;
Ok(StoreRepo::V0(match store_type.as_str() {
"public" => StoreRepoV0::PublicStore(repo_id),
"protected" => StoreRepoV0::ProtectedStore(repo_id),
"private" => StoreRepoV0::PrivateStore(repo_id),
"group" => StoreRepoV0::Group(repo_id),
"dialog" | _ => unimplemented!(),
}))
}
pub fn store_type_for_app(&self) -> String {
match self {
Self::V0(v0) => match v0 {
@ -1533,6 +1544,12 @@ impl BranchType {
_ => false,
}
}
pub fn is_header(&self) -> bool {
match self {
Self::Header => true,
_ => false,
}
}
}
impl fmt::Display for BranchType {

@ -25,10 +25,10 @@ ng.init_headless(config).then( async() => {
//let user_id = await ng.admin_create_user(config);
//console.log("user created: ",user_id);
let user_id = "NnAJWxO-KapuWyCm7RGwO5VszZwaJARGit-i3i1mXbkA";
let user_id = "tPY8WDkgXdqJsQMXI6U5ztj_SK54djy9XtHJhKlzyGQA";
let base = "did:ng:o:8mqfhoSprneBjkAASinRk0OYvFpbiyhjMBVHKQIarDEA";
//let base;
let session = await ng.session_headless_start(user_id);
session_id = session.session_id;
console.log(session);
@ -36,13 +36,23 @@ ng.init_headless(config).then( async() => {
let dump = await ng.rdf_dump(session.session_id);
console.log(dump);
//let nuri = await ng.doc_create(session.session_id, "Graph", "data:graph", "protected", "RMKIXeT9RvGT5wc2uJQaRqEFjaJpC19haeaSx4iRenIA", "store");
let nuri = "did:ng:o:b70vk7Bj4eInXgG8pLysrFpEL-YSOiRYEmihPGiM1EsA:v:_0hm2qIpq443C7rMEdCGnhPDhsaWR2XruTIaF-9LKbkA";
console.log("nuri=",nuri);
let base = "did:ng:o:b70vk7Bj4eInXgG8pLysrFpEL-YSOiRYEmihPGiM1EsA";
console.log("******** SELECT")
let sparql_result = await ng.sparql_query(session.session_id, "SELECT ?s ?p ?o WHERE { ?s ?p ?o }", base);
console.log(sparql_result);
for (const q of sparql_result.results.bindings) {
console.log(q);
}
let header_branch = "did:ng:o:b70vk7Bj4eInXgG8pLysrFpEL-YSOiRYEmihPGiM1EsA:v:_0hm2qIpq443C7rMEdCGnhPDhsaWR2XruTIaF-9LKbkA:b:TokczMya9WDpQ-_FYFi7QJVbHmllWS3lD-vjtzHHQa0A";
// let sparql_result = await ng.sparql_query(session.session_id, "SELECT ?s ?p ?o WHERE { ?s ?p ?o }", base, header_branch);
// console.log(sparql_result);
// for (const q of sparql_result.results.bindings) {
// console.log(q);
// }
await ng.sparql_update(session.session_id, "WITH <"+header_branch+"> \
DELETE { <> <did:ng:x:ng#n> ?n. } INSERT {<> <did:ng:x:ng#n> \"ddd6\". } WHERE {OPTIONAL { <> <did:ng:x:ng#n> ?n } }",nuri);
// let history = await ng.branch_history(session.session_id);
// for (const h of history.history) {
@ -50,6 +60,12 @@ ng.init_headless(config).then( async() => {
// }
// console.log(history.swimlane_state);
sparql_result = await ng.sparql_query(session.session_id, "CONSTRUCT { ?s ?p ?o } WHERE { GRAPH <"+header_branch+"> { ?s ?p ?o } }", base);
console.log("******** CONSTRUCT")
for (const r of sparql_result) console.log(r.subject.value, r.predicate.value,r.object.value);
//await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:o:8mqfhoSprneBjkAASinRk0OYvFpbiyhjMBVHKQIarDEA> <did:ng:i> <did:ng:j> }");
// await ng.sparql_update(session.session_id, "DELETE DATA { <did:ng:t:AJQ5gCLoXXjalC9diTDCvxxWu5ZQUcYWEE821nhVRMcE> <did:ng:i> <did:ng:j> }");
@ -65,29 +81,29 @@ ng.init_headless(config).then( async() => {
//await ng.sparql_update(session.session_id, "INSERT { _:_ <did:ng:ok> <did:ng:v> . } WHERE { _:_ <did:ng:m> <did:ng:n> } ");
//await ng.sparql_update(session.session_id, "INSERT DATA { _:_ <abc:a> <d:a> . _:_a <abceee:a> <d:a> . }");
await ng.sparql_update(session.session_id, "INSERT DATA { <> <a:selftest> <a:selftest> . }",base);
//await ng.sparql_update(session.session_id, "INSERT DATA { <> <a:selftest> <a:selftest> . }",base);
//await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:TEST4> <did:ng:j> _:_ . _:_ <did:ng:m> <did:ng:n> . }");
//await ng.sparql_update(session.session_id, "INSERT DATA { <did:ng:TEST5> <did:ng:j> [ <did:ng:m> <did:ng:n> ]. }");
sparql_result = await ng.sparql_query(session.session_id, "SELECT ?a WHERE { ?a <did:ng:j> _:abc. _:abc <did:ng:m> <did:ng:n> }", base);
console.log(sparql_result);
for (const q of sparql_result.results.bindings) {
console.log(q);
}
// sparql_result = await ng.sparql_query(session.session_id, "SELECT ?a WHERE { ?a <did:ng:j> _:abc. _:abc <did:ng:m> <did:ng:n> }", base);
// console.log(sparql_result);
// for (const q of sparql_result.results.bindings) {
// console.log(q);
// }
sparql_result = await ng.sparql_query(session.session_id, "SELECT ?s ?a WHERE { ?s <did:ng:j> ?a }", base);
console.log(sparql_result);
for (const q of sparql_result.results.bindings) {
console.log(q);
}
// sparql_result = await ng.sparql_query(session.session_id, "SELECT ?s ?a WHERE { ?s <did:ng:j> ?a }", base);
// console.log(sparql_result);
// for (const q of sparql_result.results.bindings) {
// console.log(q);
// }
console.log("******** CONSTRUCT")
// console.log("******** CONSTRUCT2")
let quads = await ng.sparql_query(session.session_id, "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }",base);
for (const q of quads) {
console.log(q.subject.toString(), q.predicate.toString(), q.object.toString(), q.graph.toString())
}
// let quads = await ng.sparql_query(session.session_id, "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }",base);
// for (const q of quads) {
// console.log(q.subject.toString(), q.predicate.toString(), q.object.toString(), q.graph.toString())
// }
// let file_nuri = await ng.file_put_to_private_store(session.session_id,"LICENSE-MIT","text/plain");
// console.log(file_nuri);

@ -399,6 +399,71 @@ pub async fn sparql_update(
}
}
#[wasm_bindgen]
pub async fn update_header(
session_id: JsValue,
nuri: String,
title: JsValue,
about: JsValue,
) -> Result<(), String> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Invalid session_id".to_string())?;
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
let title = if title.is_string() {
Some(title.as_string().unwrap())
} else {
None
};
let about = if about.is_string() {
Some(about.as_string().unwrap())
} else {
None
};
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_header(),
nuri,
payload: Some(AppRequestPayload::new_header(title, about)),
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
if let AppResponse::V0(AppResponseV0::Error(e)) = res {
Err(e)
} else {
Ok(())
}
}
#[wasm_bindgen]
pub async fn fetch_header(session_id: JsValue, nuri: String) -> Result<JsValue, String> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Invalid session_id".to_string())?;
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_fetch_header(),
nuri,
payload: None,
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
match res {
AppResponse::V0(AppResponseV0::Error(e)) => Err(e),
AppResponse::V0(AppResponseV0::Header(h)) => Ok(serde_wasm_bindgen::to_value(&h).unwrap()),
_ => Err("invalid response".to_string()),
}
}
#[cfg(not(wasmpack_target = "nodejs"))]
#[wasm_bindgen]
pub async fn sparql_query(
@ -1211,6 +1276,7 @@ pub async fn app_request_with_nuri_command(
Ok(serde_wasm_bindgen::to_value(&response).unwrap())
}
#[cfg(not(wasmpack_target = "nodejs"))]
#[wasm_bindgen]
pub async fn doc_create(
session_id: JsValue,
@ -1253,6 +1319,50 @@ pub async fn doc_create(
}
}
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen]
pub async fn doc_create(
session_id: JsValue,
crdt: String,
class_name: String,
store_type: String,
store_repo: String,
destination: String,
) -> Result<JsValue, String> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Deserialization error of session_id".to_string())?;
let class = BranchCrdt::from(crdt, class_name).map_err(|e| e.to_string())?;
let store = StoreRepo::from_type_and_repo(&store_type, &store_repo)
.map_err(|_| "invalid store_repo".to_string())?;
let destination = DocCreateDestination::from(destination).map_err(|e| e.to_string())?;
let request = AppRequest::V0(AppRequestV0 {
session_id,
command: AppRequestCommandV0::new_create(),
nuri: NuriV0::new_empty(),
payload: Some(AppRequestPayload::V0(AppRequestPayloadV0::Create(
DocCreate {
store,
class,
destination,
},
))),
});
let response = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
if let AppResponse::V0(AppResponseV0::Nuri(nuri)) = response {
Ok(serde_wasm_bindgen::to_value(&nuri).unwrap())
} else {
Err("invalid response".to_string())
}
}
#[wasm_bindgen]
pub async fn file_get_from_private_store(
session_id: JsValue,

@ -27,6 +27,7 @@ rand = { version = "0.7", features = ["getrandom"] }
web-time = "0.2.0"
either = "1.8.1"
futures = "0.3.24"
lazy_static = "1.4.0"
async-trait = "0.1.64"
async-std = { version = "1.12.0", features = [ "attributes", "unstable" ] }
automerge = "0.5.11"

@ -33,7 +33,7 @@ use crate::verifier::Verifier;
struct BranchUpdateInfo {
branch_id: BranchId,
branch_is_main: bool,
branch_type: BranchType,
repo_id: RepoId,
topic_id: TopicId,
token: Digest,
@ -268,7 +268,7 @@ impl Verifier {
if body.graph.is_some() {
let info = BranchUpdateInfo {
branch_id: *branch_id,
branch_is_main: branch.branch_type.is_main(),
branch_type: branch.branch_type.clone(),
repo_id: *repo_id,
topic_id: branch.topic.clone().unwrap(),
token: branch.read_cap.as_ref().unwrap().tokenize(),
@ -330,7 +330,10 @@ impl Verifier {
fn find_branch_and_repo_for_quad(
&self,
quad: &Quad,
branches: &mut HashMap<BranchId, (StoreRepo, RepoId, bool, TopicId, Digest, OverlayId)>,
branches: &mut HashMap<
BranchId,
(StoreRepo, RepoId, BranchType, TopicId, Digest, OverlayId),
>,
nuri_branches: &mut HashMap<String, (RepoId, BranchId, bool)>,
) -> Result<(RepoId, BranchId, bool), VerifierError> {
match &quad.graph_name {
@ -348,13 +351,13 @@ impl Verifier {
nuri.overlay.unwrap().outer().to_slice(),
))?;
let repo = self.get_repo(nuri.target.repo_id(), store.get_store_repo())?;
let (branch_id, is_publisher, is_main, topic_id, token) = match nuri.branch {
let (branch_id, is_publisher, branch_type, topic_id, token) = match nuri.branch {
None => {
let b = repo.main_branch().ok_or(VerifierError::BranchNotFound)?;
(
b.id,
b.topic_priv_key.is_some(),
true,
b.branch_type.clone(),
b.topic.clone().unwrap(),
b.read_cap.as_ref().unwrap().tokenize(),
)
@ -365,7 +368,7 @@ impl Verifier {
(
id,
b.topic_priv_key.is_some(),
false,
b.branch_type.clone(),
b.topic.clone().unwrap(),
b.read_cap.as_ref().unwrap().tokenize(),
)
@ -376,7 +379,7 @@ impl Verifier {
let _ = branches.entry(branch_id).or_insert((
store.get_store_repo().clone(),
repo.id,
is_main,
branch_type,
topic_id,
token,
store.overlay_id,
@ -392,7 +395,7 @@ impl Verifier {
}
}
async fn prepare_sparql_update(
pub(crate) async fn prepare_sparql_update(
&mut self,
inserts: Vec<Quad>,
removes: Vec<Quad>,
@ -405,8 +408,10 @@ impl Verifier {
// for now we just do skip, without giving option to user
let mut inserts_map: HashMap<BranchId, HashSet<Triple>> = HashMap::with_capacity(1);
let mut removes_map: HashMap<BranchId, HashSet<Triple>> = HashMap::with_capacity(1);
let mut branches: HashMap<BranchId, (StoreRepo, RepoId, bool, TopicId, Digest, OverlayId)> =
HashMap::with_capacity(1);
let mut branches: HashMap<
BranchId,
(StoreRepo, RepoId, BranchType, TopicId, Digest, OverlayId),
> = HashMap::with_capacity(1);
let mut nuri_branches: HashMap<String, (RepoId, BranchId, bool)> =
HashMap::with_capacity(1);
let mut inserts_len = inserts.len();
@ -462,8 +467,7 @@ impl Verifier {
let mut updates = Vec::with_capacity(branches.len());
for (branch_id, (store_repo, repo_id, branch_is_main, topic_id, token, overlay_id)) in
branches
for (branch_id, (store_repo, repo_id, branch_type, topic_id, token, overlay_id)) in branches
{
let graph_transac = GraphTransaction {
inserts: Vec::from_iter(inserts_map.remove(&branch_id).unwrap_or(HashSet::new())),
@ -497,7 +501,7 @@ impl Verifier {
let info = BranchUpdateInfo {
branch_id,
branch_is_main,
branch_type,
repo_id,
topic_id,
token,
@ -526,13 +530,15 @@ impl Verifier {
let reader = transaction.ng_get_reader();
for update in updates_ref.iter_mut() {
let branch_is_main = update.branch_type.is_main();
let commit_name =
NuriV0::commit_graph_name(&update.commit_id, &update.overlay_id);
let commit_encoded = numeric_encoder::StrHash::new(&commit_name);
let cv_graphname = NamedNode::new_unchecked(commit_name);
let cv_graphname_ref = GraphNameRef::NamedNode((&cv_graphname).into());
let ov_main = if update.branch_is_main {
let ov_main = if branch_is_main {
let ov_graphname = NamedNode::new_unchecked(NuriV0::repo_graph_name(
&update.repo_id,
&update.overlay_id,
@ -541,7 +547,7 @@ impl Verifier {
} else {
None
};
let value = if update.branch_is_main {
let value = if branch_is_main {
ADDED_IN_MAIN
} else {
ADDED_IN_OTHER
@ -611,7 +617,7 @@ impl Verifier {
let at_current_heads = current_heads == direct_causal_past_encoded;
// if not, we need to base ourselves on the materialized state of the direct_causal_past of the commit
let value = if update.branch_is_main {
let value = if branch_is_main {
REMOVED_IN_MAIN
} else {
REMOVED_IN_OTHER
@ -678,18 +684,53 @@ impl Verifier {
.map_err(|e| VerifierError::OxigraphError(e.to_string()));
if res.is_ok() {
for update in updates {
let graph_patch = update.transaction.as_patch();
self.push_app_response(
&update.branch_id,
AppResponse::V0(AppResponseV0::Patch(AppPatch {
commit_id: update.commit_id.to_string(),
commit_info: update.commit_info,
graph: Some(graph_patch),
discrete: None,
other: None,
})),
)
.await;
if update.branch_type.is_header() {
let mut tab_doc_info = AppTabDocInfo::new();
for removed in update.transaction.removes {
match removed.predicate.as_str() {
NG_ONTOLOGY_ABOUT => tab_doc_info.description = Some("".to_string()),
NG_ONTOLOGY_TITLE => tab_doc_info.title = Some("".to_string()),
_ => {}
}
}
for inserted in update.transaction.inserts {
match inserted.predicate.as_str() {
NG_ONTOLOGY_ABOUT => {
if let Term::Literal(l) = inserted.object {
tab_doc_info.description = Some(l.value().to_string())
}
}
NG_ONTOLOGY_TITLE => {
if let Term::Literal(l) = inserted.object {
tab_doc_info.title = Some(l.value().to_string())
}
}
_ => {}
}
}
self.push_app_response(
&update.branch_id,
AppResponse::V0(AppResponseV0::TabInfo(AppTabInfo {
branch: None,
doc: Some(tab_doc_info),
store: None,
})),
)
.await;
} else {
let graph_patch = update.transaction.as_patch();
self.push_app_response(
&update.branch_id,
AppResponse::V0(AppResponseV0::Patch(AppPatch {
commit_id: update.commit_id.to_string(),
commit_info: update.commit_info,
graph: Some(graph_patch),
discrete: None,
other: None,
})),
)
.await;
}
}
}
res
@ -714,7 +755,7 @@ impl Verifier {
);
match res {
Err(e) => Err(e.to_string()),
Ok((mut inserts, removes)) => {
Ok((inserts, removes)) => {
if inserts.is_empty() && removes.is_empty() {
Ok(())
} else {

@ -16,6 +16,7 @@ use futures::channel::mpsc;
use futures::SinkExt;
use futures::StreamExt;
use ng_oxigraph::oxigraph::sparql::{results::*, Query, QueryResults};
use ng_oxigraph::oxrdf::{Literal, NamedNode, Quad, Term};
use ng_repo::errors::*;
use ng_repo::file::{RandomAccessFile, ReadFile};
@ -146,6 +147,23 @@ impl Verifier {
}
}
fn resolve_header_branch(
&self,
target: &NuriTargetV0,
) -> Result<(RepoId, BranchId, StoreRepo), NgError> {
Ok(match target {
NuriTargetV0::Repo(repo_id) => {
let (branch, store_repo) = {
let repo = self.repos.get(repo_id).ok_or(NgError::RepoNotFound)?;
let branch = repo.header_branch().ok_or(NgError::BranchNotFound)?;
(branch.id, repo.store.get_store_repo().clone())
};
(*repo_id, branch, store_repo)
}
_ => return Err(NgError::NotImplemented),
})
}
pub(crate) fn resolve_target_for_sparql(
&self,
target: &NuriTargetV0,
@ -532,6 +550,80 @@ impl Verifier {
payload: Option<AppRequestPayload>,
) -> Result<AppResponse, NgError> {
match command {
AppRequestCommandV0::Header => {
if let Some(AppRequestPayload::V0(AppRequestPayloadV0::Header(doc_header))) =
payload
{
let (repo_id, branch_id, store_repo) =
match self.resolve_header_branch(&nuri.target) {
Err(e) => return Ok(AppResponse::error(e.to_string())),
Ok(a) => a,
};
let graph_name = NuriV0::branch_repo_graph_name(
&branch_id,
&repo_id,
&store_repo.overlay_id_for_storage_purpose(),
);
let base = NuriV0::repo_id(&repo_id);
let mut deletes = String::new();
let mut wheres = String::new();
let mut inserts = String::new();
if let Some(about) = doc_header.about {
deletes += &format!("<> <{NG_ONTOLOGY_ABOUT}> ?a. ");
wheres += &format!("OPTIONAL {{ <> <{NG_ONTOLOGY_ABOUT}> ?a }} ");
if about.len() > 0 {
inserts += &format!(
"<> <{NG_ONTOLOGY_ABOUT}> \"{}\". ",
about.replace("\\", "\\\\").replace("\"", "\\\"")
);
}
}
if let Some(title) = doc_header.title {
deletes += &format!("<> <{NG_ONTOLOGY_TITLE}> ?n. ");
wheres += &format!("OPTIONAL {{ <> <{NG_ONTOLOGY_TITLE}> ?n }} ");
if title.len() > 0 {
inserts += &format!(
"<> <{NG_ONTOLOGY_TITLE}> \"{}\". ",
title.replace("\\", "\\\\").replace("\"", "\\\"")
);
}
}
let query = format!(
"DELETE {{ {deletes} }} INSERT {{ {inserts} }} WHERE {{ {wheres} }}"
);
let oxistore = self.graph_dataset.as_ref().unwrap();
let update = ng_oxigraph::oxigraph::sparql::Update::parse(&query, Some(&base))
.map_err(|e| NgError::InternalError)?;
let res = oxistore.ng_update(update, Some(graph_name));
return Ok(match res {
Err(e) => AppResponse::error(NgError::InternalError.to_string()),
Ok((inserts, removes)) => {
if inserts.is_empty() && removes.is_empty() {
AppResponse::ok()
} else {
match self
.prepare_sparql_update(
Vec::from_iter(inserts),
Vec::from_iter(removes),
self.get_peer_id_for_skolem(),
)
.await
{
Err(e) => AppResponse::error(e.to_string()),
Ok(_) => AppResponse::ok(),
}
}
}
});
} else {
return Err(NgError::InvalidPayload);
}
}
AppRequestCommandV0::Create => {
if let Some(AppRequestPayload::V0(AppRequestPayloadV0::Create(doc_create))) =
payload
@ -540,6 +632,7 @@ impl Verifier {
let user_id = self.user_id().clone();
let user_priv_key = self.user_privkey().clone();
let primary_class = doc_create.class.class().clone();
let repo_id = self
.new_repo_default(
&user_id,
@ -549,6 +642,11 @@ impl Verifier {
)
.await?;
let header_branch_id = {
let repo = self.get_repo(&repo_id, &doc_create.store)?;
repo.header_branch().ok_or(NgError::BranchNotFound)?.id
};
// adding an AddRepo commit to the Store branch of store.
self.send_add_repo_to_store(&repo_id, &doc_create.store)
.await?;
@ -570,12 +668,73 @@ impl Verifier {
self.add_doc(&repo_id, &overlay_id)?;
// adding the class triple to the header branch
let header_branch_nuri = format!("{nuri_result}:b:{}", header_branch_id);
let quad = Quad {
subject: NamedNode::new_unchecked(&nuri).into(),
predicate: NG_ONTOLOGY_CLASS_NAME.clone().into(),
object: Literal::new_simple_literal(primary_class).into(),
graph_name: NamedNode::new_unchecked(&header_branch_nuri).into(),
};
let ret = self.prepare_sparql_update(vec![quad], vec![], vec![]).await;
if let Err(e) = ret {
return Ok(AppResponse::error(e.to_string()));
}
return Ok(AppResponse::V0(AppResponseV0::Nuri(nuri_result)));
} else {
return Err(NgError::InvalidPayload);
}
}
AppRequestCommandV0::Fetch(fetch) => match fetch {
AppFetchContentV0::Header => {
let (repo_id, branch_id, store_repo) =
match self.resolve_header_branch(&nuri.target) {
Err(e) => return Ok(AppResponse::error(e.to_string())),
Ok(a) => a,
};
self.open_branch(&repo_id, &branch_id, true).await?;
let graph_name = NuriV0::branch_repo_graph_name(
&branch_id,
&repo_id,
&store_repo.overlay_id_for_storage_purpose(),
);
let base = NuriV0::repo_id(&repo_id);
let oxistore = self.graph_dataset.as_ref().unwrap();
let parsed = Query::parse(
&format!("SELECT ?class ?title ?about WHERE {{ OPTIONAL {{ <> <{NG_ONTOLOGY_CLASS}> ?class }} OPTIONAL {{ <> <{NG_ONTOLOGY_ABOUT}> ?about }} OPTIONAL {{ <> <{NG_ONTOLOGY_TITLE}> ?title }} }}"), Some(&base));
if parsed.is_err() {
return Ok(AppResponse::error(parsed.unwrap_err().to_string()));
}
let results = oxistore.query(parsed.unwrap(), Some(graph_name));
match results {
Err(e) => return Ok(AppResponse::error(e.to_string())),
Ok(QueryResults::Solutions(mut sol)) => {
let mut title = None;
let mut about = None;
let mut class = None;
if let Some(Ok(s)) = sol.next() {
if let Some(Term::Literal(l)) = s.get("title") {
title = Some(l.value().to_string());
}
if let Some(Term::Literal(l)) = s.get("about") {
about = Some(l.value().to_string());
}
if let Some(Term::Literal(l)) = s.get("class") {
class = Some(l.value().to_string());
}
}
return Ok(AppResponse::V0(AppResponseV0::Header(AppHeader {
about,
title,
class,
})));
}
_ => return Err(NgError::InvalidResponse),
};
}
AppFetchContentV0::ReadQuery => {
if let Some(AppRequestPayload::V0(AppRequestPayloadV0::Query(DocQuery::V0 {
sparql,

@ -106,7 +106,7 @@ impl SiteV0 {
let protected_store = Self::site_store_to_store_repo(&protected);
let private_store = Self::site_store_to_store_repo(&private);
verifier.reserve_more(33)?;
verifier.reserve_more(37)?;
let mut signer_caps = Vec::with_capacity(3);

@ -17,11 +17,24 @@ use serde::{Deserialize, Serialize};
//use oxigraph::store::Store;
//use oxigraph::model::GroundQuad;
//use yrs::{StateVector, Update};
use lazy_static::lazy_static;
use ng_net::{app_protocol::*, types::*};
use ng_oxigraph::oxrdf::{GraphName, GraphNameRef, NamedNode, Quad, Triple, TripleRef};
use ng_repo::{errors::*, types::*};
pub const NG_ONTOLOGY: &str = "did:ng:x:ng#";
pub const NG_ONTOLOGY_ABOUT: &str = "did:ng:x:ng#a";
pub const NG_ONTOLOGY_TITLE: &str = "did:ng:x:ng#n";
pub const NG_ONTOLOGY_CLASS: &str = "did:ng:x:ng#c";
lazy_static! {
pub static ref NG_ONTOLOGY_ABOUT_NAME: NamedNode = NamedNode::new_unchecked(NG_ONTOLOGY_ABOUT);
pub static ref NG_ONTOLOGY_TITLE_NAME: NamedNode = NamedNode::new_unchecked(NG_ONTOLOGY_TITLE);
pub static ref NG_ONTOLOGY_CLASS_NAME: NamedNode = NamedNode::new_unchecked(NG_ONTOLOGY_CLASS);
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GraphTransaction {
pub inserts: Vec<Triple>,

@ -25,6 +25,9 @@ use async_std::stream::StreamExt;
use async_std::sync::{Mutex, RwLockReadGuard};
use futures::channel::mpsc;
use futures::SinkExt;
use ng_oxigraph::oxigraph::sparql::Query;
use ng_oxigraph::oxigraph::sparql::QueryResults;
use ng_oxigraph::oxrdf::Term;
use ng_repo::utils::derive_key;
use sbbf_rs_safe::Filter;
use serde::{Deserialize, Serialize};
@ -253,10 +256,11 @@ impl Verifier {
}
fn branch_get_tab_info(
&self,
repo: &Repo,
branch: &BranchId,
outer: String,
) -> Result<AppTabInfo, NgError> {
) -> Result<(AppTabInfo, Option<BranchId>), NgError> {
let branch_info = repo.branch(branch)?;
let branch_tab_info = AppTabBranchInfo {
@ -266,6 +270,40 @@ impl Verifier {
comment_branch: None, //TODO
};
// Retrieve Header branch info (title and about)
let header_branch_info = repo.header_branch();
let mut about = None;
let mut title = None;
if let Some(header_branch_info) = header_branch_info {
let oxistore = self.graph_dataset.as_ref().unwrap();
let header_graph = NuriV0::branch_repo_graph_name(
&header_branch_info.id,
&repo.id,
&repo.store.overlay_id,
);
let base = NuriV0::repo_id(&repo.id);
let parsed = Query::parse(&format!("SELECT ?title ?about WHERE {{ OPTIONAL {{ <> <{NG_ONTOLOGY_ABOUT}> ?about }} OPTIONAL {{ <> <{NG_ONTOLOGY_TITLE}> ?title }} }}"),
Some(&base)).map_err(|e| NgError::OxiGraphError(e.to_string()))?;
let results = oxistore
.query(parsed, Some(header_graph))
.map_err(|e| NgError::OxiGraphError(e.to_string()))?;
match results {
QueryResults::Solutions(mut sol) => {
if let Some(Ok(s)) = sol.next() {
if let Some(Term::Literal(l)) = s.get("title") {
title = Some(l.value().to_string());
}
if let Some(Term::Literal(l)) = s.get("about") {
about = Some(l.value().to_string());
}
}
}
_ => return Err(NgError::InvalidResponse),
}
}
let root_branch_info = repo.branch(&repo.id)?;
let doc_tab_info = AppTabDocInfo {
@ -275,9 +313,9 @@ impl Verifier {
authors: None, // TODO
inbox: None, // TODO
can_edit: Some(true),
title: None,
title,
icon: None,
description: None,
description: about,
};
let store_tab_info = AppTabStoreInfo {
@ -293,11 +331,14 @@ impl Verifier {
description: None,
};
Ok(AppTabInfo {
branch: Some(branch_tab_info),
doc: Some(doc_tab_info),
store: Some(store_tab_info),
})
Ok((
AppTabInfo {
branch: Some(branch_tab_info),
doc: Some(doc_tab_info),
store: Some(store_tab_info),
},
header_branch_info.map(|i| i.id),
))
}
pub(crate) async fn create_branch_subscription(
@ -317,19 +358,45 @@ impl Verifier {
//return Err(VerifierError::DoubleBranchSubscription);
}
}
let (heads, head_keys, tab_info, header_branch_id, crdt) = {
let repo = self.get_repo(&repo_id, &store_repo)?;
let branch = repo.branch(&branch_id)?;
let repo = self.get_repo(&repo_id, &store_repo)?;
let branch = repo.branch(&branch_id)?;
let heads: Vec<ObjectId> = branch.current_heads.iter().map(|h| h.id.clone()).collect();
let head_keys: Vec<ObjectKey> =
branch.current_heads.iter().map(|h| h.key.clone()).collect();
//let tx = self.branch_subscriptions.entry(branch).or_insert_with(|| {});
let (tab_info, header_branch_id) =
self.branch_get_tab_info(repo, &branch_id, self.outer.clone())?;
(
heads,
head_keys,
tab_info,
header_branch_id,
branch.crdt.clone(),
)
};
if let Some(header_branch_id) = header_branch_id {
if let Some(returned) = self
.branch_subscriptions
.insert(header_branch_id, tx.clone())
{
if !returned.is_closed() {
returned.close_channel();
}
}
}
//let tx = self.branch_subscriptions.entry(branch).or_insert_with(|| {});
let files = self
.user_storage
.as_ref()
.unwrap()
.branch_get_all_files(&branch_id)?;
let tab_info = Self::branch_get_tab_info(repo, &branch_id, self.outer.clone())?;
// let tab_info = self.user_storage.as_ref().unwrap().branch_get_tab_info(
// &branch_id,
// &repo_id,
@ -356,7 +423,6 @@ impl Verifier {
}
}
let crdt = &repo.branch(&branch_id)?.crdt;
let discrete = if crdt.is_graph() {
None
} else {
@ -366,7 +432,7 @@ impl Verifier {
.unwrap()
.branch_get_discrete_state(&branch_id)
{
Ok(state) => Some(match repo.branch(&branch_id)?.crdt {
Ok(state) => Some(match crdt {
BranchCrdt::Automerge(_) => DiscreteState::Automerge(state),
BranchCrdt::YArray(_) => DiscreteState::YArray(state),
BranchCrdt::YMap(_) => DiscreteState::YMap(state),
@ -380,8 +446,8 @@ impl Verifier {
};
let state = AppState {
heads: branch.current_heads.iter().map(|h| h.id.clone()).collect(),
head_keys: branch.current_heads.iter().map(|h| h.key.clone()).collect(),
heads,
head_keys,
graph: if results.is_empty() {
None
} else {

Loading…
Cancel
Save