From 771dd8ad0cee0aee0b0a65c978a3d3313be482a5 Mon Sep 17 00:00:00 2001 From: Niko PLP <niko@nextgraph.org> Date: Mon, 2 Sep 2024 05:32:07 +0300 Subject: [PATCH] edit title and intro --- Cargo.lock | 1 + RELEASE-NOTE.md | 1 - ng-app/index-web.html | 9 +- ng-app/src-tauri/src/lib.rs | 49 +++++++ ng-app/src/api.ts | 2 + ng-app/src/apps/ContainerView.svelte | 34 ++++- ng-app/src/apps/SparqlQueryEditor.svelte | 4 +- ng-app/src/classes.ts | 77 +++++----- ng-app/src/lib/Document.svelte | 4 +- ng-app/src/lib/FullLayout.svelte | 123 +++++++++------- ng-app/src/lib/icons/DataClassIcon.svelte | 10 +- ng-app/src/lib/popups/Header.svelte | 70 +++++++++ ng-app/src/locales/en.json | 13 +- ng-app/src/routes/WalletInfo.svelte | 4 +- ng-app/src/routes/WalletLogin.svelte | 42 +++--- ng-app/src/store.ts | 61 +++++++- ng-app/src/styles.css | 6 +- ng-app/src/tab.ts | 7 +- ng-app/src/zeras.ts | 32 ++--- ng-net/src/app_protocol.rs | 56 +++++++- ng-oxigraph/src/oxigraph/sparql/dataset.rs | 26 +++- ng-oxigraph/src/oxigraph/storage/mod.rs | 12 +- ng-repo/src/repo.rs | 11 +- ng-repo/src/store.rs | 90 ++++++++---- ng-repo/src/types.rs | 17 +++ ng-sdk-js/app-node/index.js | 64 +++++---- ng-sdk-js/src/lib.rs | 110 ++++++++++++++ ng-verifier/Cargo.toml | 1 + ng-verifier/src/commits/transaction.rs | 99 +++++++++---- ng-verifier/src/request_processor.rs | 159 +++++++++++++++++++++ ng-verifier/src/site.rs | 2 +- ng-verifier/src/types.rs | 13 ++ ng-verifier/src/verifier.rs | 100 ++++++++++--- 33 files changed, 1033 insertions(+), 276 deletions(-) create mode 100644 ng-app/src/lib/popups/Header.svelte diff --git a/Cargo.lock b/Cargo.lock index 0b66a14..50f07c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3507,6 +3507,7 @@ dependencies = [ "either", "futures", "getrandom 0.2.10", + "lazy_static", "ng-net", "ng-oxigraph", "ng-repo", diff --git a/RELEASE-NOTE.md b/RELEASE-NOTE.md index 48ec1c4..c5e774c 100644 --- a/RELEASE-NOTE.md +++ b/RELEASE-NOTE.md @@ -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. diff --git a/ng-app/index-web.html b/ng-app/index-web.html index 12b3a4f..729114b 100644 --- a/ng-app/index-web.html +++ b/ng-app/index-web.html @@ -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 ) { diff --git a/ng-app/src-tauri/src/lib.rs b/ng-app/src-tauri/src/lib.rs index b066de1..e8adbbe 100644 --- a/ng-app/src-tauri/src/lib.rs +++ b/ng-app/src-tauri/src/lib.rs @@ -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"); diff --git a/ng-app/src/api.ts b/ng-app/src/api.ts index 30269a1..0918351 100644 --- a/ng-app/src/api.ts +++ b/ng-app/src/api.ts @@ -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"] } diff --git a/ng-app/src/apps/ContainerView.svelte b/ng-app/src/apps/ContainerView.svelte index 21e1b9f..5f187a6 100644 --- a/ng-app/src/apps/ContainerView.svelte +++ b/ng-app/src/apps/ContainerView.svelte @@ -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> diff --git a/ng-app/src/apps/SparqlQueryEditor.svelte b/ng-app/src/apps/SparqlQueryEditor.svelte index dc8bcb8..76fbf34 100644 --- a/ng-app/src/apps/SparqlQueryEditor.svelte +++ b/ng-app/src/apps/SparqlQueryEditor.svelte @@ -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} diff --git a/ng-app/src/classes.ts b/ng-app/src/classes.ts index 6499364..5929e0e 100644 --- a/ng-app/src/classes.ts +++ b/ng-app/src/classes.ts @@ -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", diff --git a/ng-app/src/lib/Document.svelte b/ng-app/src/lib/Document.svelte index 939124d..457e642 100644 --- a/ng-app/src/lib/Document.svelte +++ b/ng-app/src/lib/Document.svelte @@ -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> diff --git a/ng-app/src/lib/FullLayout.svelte b/ng-app/src/lib/FullLayout.svelte index 381ccf5..15c88f9 100644 --- a/ng-app/src/lib/FullLayout.svelte +++ b/ng-app/src/lib/FullLayout.svelte @@ -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 } diff --git a/ng-app/src/lib/icons/DataClassIcon.svelte b/ng-app/src/lib/icons/DataClassIcon.svelte index 58cbfb8..54a4284 100644 --- a/ng-app/src/lib/icons/DataClassIcon.svelte +++ b/ng-app/src/lib/icons/DataClassIcon.svelte @@ -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, }; diff --git a/ng-app/src/lib/popups/Header.svelte b/ng-app/src/lib/popups/Header.svelte new file mode 100644 index 0000000..03c5d5c --- /dev/null +++ b/ng-app/src/lib/popups/Header.svelte @@ -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> + + diff --git a/ng-app/src/locales/en.json b/ng-app/src/locales/en.json index 2f4b3c9..4d3150e 100644 --- a/ng-app/src/locales/en.json +++ b/ng-app/src/locales/en.json @@ -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": { diff --git a/ng-app/src/routes/WalletInfo.svelte b/ng-app/src/routes/WalletInfo.svelte index 885e9e9..4bbd6d4 100644 --- a/ng-app/src/routes/WalletInfo.svelte +++ b/ng-app/src/routes/WalletInfo.svelte @@ -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 diff --git a/ng-app/src/routes/WalletLogin.svelte b/ng-app/src/routes/WalletLogin.svelte index ea31bfd..1e8f978 100644 --- a/ng-app/src/routes/WalletLogin.svelte +++ b/ng-app/src/routes/WalletLogin.svelte @@ -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> diff --git a/ng-app/src/store.ts b/ng-app/src/store.ts index e220f55..0a3289a 100644 --- a/ng-app/src/store.ts +++ b/ng-app/src/store.ts @@ -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){ diff --git a/ng-app/src/styles.css b/ng-app/src/styles.css index 05b0d9b..13bc864 100644 --- a/ng-app/src/styles.css +++ b/ng-app/src/styles.css @@ -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; } diff --git a/ng-app/src/tab.ts b/ng-app/src/tab.ts index 65939a5..1efb15f 100644 --- a/ng-app/src/tab.ts +++ b/ng-app/src/tab.ts @@ -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; diff --git a/ng-app/src/zeras.ts b/ng-app/src/zeras.ts index 6385e6d..9c0cc03 100644 --- a/ng-app/src/zeras.ts +++ b/ng-app/src/zeras.ts @@ -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"], }, diff --git a/ng-net/src/app_protocol.rs b/ng-net/src/app_protocol.rs index 3275d5b..8f7ff38 100644 --- a/ng-net/src/app_protocol.rs +++ b/ng-net/src/app_protocol.rs @@ -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)] diff --git a/ng-oxigraph/src/oxigraph/sparql/dataset.rs b/ng-oxigraph/src/oxigraph/sparql/dataset.rs index 3551f5d..c8bbbb9 100644 --- a/ng-oxigraph/src/oxigraph/sparql/dataset.rs +++ b/ng-oxigraph/src/oxigraph/sparql/dataset.rs @@ -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> { diff --git a/ng-oxigraph/src/oxigraph/storage/mod.rs b/ng-oxigraph/src/oxigraph/storage/mod.rs index f478f81..d46262c 100644 --- a/ng-oxigraph/src/oxigraph/storage/mod.rs +++ b/ng-oxigraph/src/oxigraph/storage/mod.rs @@ -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)?; diff --git a/ng-repo/src/repo.rs b/ng-repo/src/repo.rs index a898eb1..4614483 100644 --- a/ng-repo/src/repo.rs +++ b/ng-repo/src/repo.rs @@ -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 { diff --git a/ng-repo/src/store.rs b/ng-repo/src/store.rs index ab4663b..c600c33 100644 --- a/ng-repo/src/store.rs +++ b/ng-repo/src/store.rs @@ -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]; diff --git a/ng-repo/src/types.rs b/ng-repo/src/types.rs index 02251b8..897b577 100644 --- a/ng-repo/src/types.rs +++ b/ng-repo/src/types.rs @@ -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 { diff --git a/ng-sdk-js/app-node/index.js b/ng-sdk-js/app-node/index.js index bc599af..2c7c855 100644 --- a/ng-sdk-js/app-node/index.js +++ b/ng-sdk-js/app-node/index.js @@ -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 base = "did:ng:o:8mqfhoSprneBjkAASinRk0OYvFpbiyhjMBVHKQIarDEA"; + let user_id = "tPY8WDkgXdqJsQMXI6U5ztj_SK54djy9XtHJhKlzyGQA"; + + //let base; let session = await ng.session_headless_start(user_id); session_id = session.session_id; console.log(session); @@ -36,19 +36,35 @@ 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) { // console.log(h[0], h[1]); // } // 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); diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs index 22970ad..ba256f8 100644 --- a/ng-sdk-js/src/lib.rs +++ b/ng-sdk-js/src/lib.rs @@ -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, diff --git a/ng-verifier/Cargo.toml b/ng-verifier/Cargo.toml index de0da5a..8373cf3 100644 --- a/ng-verifier/Cargo.toml +++ b/ng-verifier/Cargo.toml @@ -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" diff --git a/ng-verifier/src/commits/transaction.rs b/ng-verifier/src/commits/transaction.rs index c17552d..6895f01 100644 --- a/ng-verifier/src/commits/transaction.rs +++ b/ng-verifier/src/commits/transaction.rs @@ -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 { diff --git a/ng-verifier/src/request_processor.rs b/ng-verifier/src/request_processor.rs index e85da60..81c61d1 100644 --- a/ng-verifier/src/request_processor.rs +++ b/ng-verifier/src/request_processor.rs @@ -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, diff --git a/ng-verifier/src/site.rs b/ng-verifier/src/site.rs index 7e3db5c..6a5b188 100644 --- a/ng-verifier/src/site.rs +++ b/ng-verifier/src/site.rs @@ -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); diff --git a/ng-verifier/src/types.rs b/ng-verifier/src/types.rs index 0116b4e..a14d761 100644 --- a/ng-verifier/src/types.rs +++ b/ng-verifier/src/types.rs @@ -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>, diff --git a/ng-verifier/src/verifier.rs b/ng-verifier/src/verifier.rs index 8ab7dcd..c61216a 100644 --- a/ng-verifier/src/verifier.rs +++ b/ng-verifier/src/verifier.rs @@ -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 {