From 029276e205bf5524a98e6d9135540c5d5fc61b79 Mon Sep 17 00:00:00 2001 From: Niko PLP Date: Sat, 27 Jul 2024 17:51:36 +0300 Subject: [PATCH] SPARQL Query viewer --- nextgraph/src/lib.rs | 1 + ng-app/package.json | 2 + ng-app/src/apps/SparqlQueryEditor.svelte | 119 ++++++++++++++++++ ng-app/src/apps/SparqlUpdateEditor.svelte | 22 ++-- ng-app/src/apps/TurtleViewer.svelte | 81 +++++++++++++ ng-app/src/classes.ts | 4 +- ng-app/src/locales/en.json | 4 +- ng-app/src/routes/WalletInfo.svelte | 2 +- ng-app/src/store.ts | 17 ++- ng-app/src/styles.css | 5 + ng-app/src/turtle.js | 139 ++++++++++++++++++++++ ng-app/src/zeras.ts | 8 +- ng-sdk-js/src/lib.rs | 70 ++++++++++- ng-verifier/src/lib.rs | 10 +- ng-verifier/src/request_processor.rs | 3 +- pnpm-lock.yaml | 15 +++ 16 files changed, 474 insertions(+), 28 deletions(-) create mode 100644 ng-app/src/apps/SparqlQueryEditor.svelte create mode 100644 ng-app/src/apps/TurtleViewer.svelte create mode 100644 ng-app/src/turtle.js diff --git a/nextgraph/src/lib.rs b/nextgraph/src/lib.rs index c0e9511..94df7cf 100644 --- a/nextgraph/src/lib.rs +++ b/nextgraph/src/lib.rs @@ -94,6 +94,7 @@ pub mod verifier { pub use ng_net::app_protocol::*; } pub use ng_verifier::prepare_app_response_for_js; + pub use ng_verifier::triples_ser_to_json_string; } pub mod wallet { diff --git a/ng-app/package.json b/ng-app/package.json index cd5c96d..a932bb0 100644 --- a/ng-app/package.json +++ b/ng-app/package.json @@ -44,6 +44,7 @@ "autoprefixer": "^10.4.14", "cross-env": "^7.0.3", "dayjs": "^1.11.10", + "highlight.js": "^11.10.0", "internal-ip": "^7.0.0", "node-gzip": "^1.1.2", "postcss": "^8.4.23", @@ -52,6 +53,7 @@ "svelte": "^3.54.0", "svelte-check": "^3.0.0", "svelte-heros-v2": "^0.10.12", + "svelte-highlight": "^7.7.0", "svelte-preprocess": "^5.0.3", "svelte-time": "^0.8.0", "tailwindcss": "^3.3.1", diff --git a/ng-app/src/apps/SparqlQueryEditor.svelte b/ng-app/src/apps/SparqlQueryEditor.svelte new file mode 100644 index 0000000..f0e5440 --- /dev/null +++ b/ng-app/src/apps/SparqlQueryEditor.svelte @@ -0,0 +1,119 @@ + + + +
+ + + Query all docs + + + {#if results!==undefined} +
+ Results:
+ {#if Array.isArray(results)} + + + + {:else if results?.head} + + + {#each results.head.vars as variable} + {variable} + {/each} + + + {#each results.results.bindings as row} + + {#each results.head.vars as variable} + {row[variable].value} + {/each} + + {/each} + +
+ {:else} + {results} + {/if} +
+ {/if} +
\ No newline at end of file diff --git a/ng-app/src/apps/SparqlUpdateEditor.svelte b/ng-app/src/apps/SparqlUpdateEditor.svelte index 6210dfa..b062982 100644 --- a/ng-app/src/apps/SparqlUpdateEditor.svelte +++ b/ng-app/src/apps/SparqlUpdateEditor.svelte @@ -14,7 +14,8 @@ import { sparql_update, toast_error, - toast_success + toast_success, + reset_toasts, } from "../store"; import { in_memory_discrete, open_viewer @@ -29,22 +30,23 @@ import { sparql } from "@codemirror/legacy-modes/mode/sparql"; import {basicSetup} from "codemirror" onMount(()=>{ - if (!$in_memory_discrete){ - $in_memory_discrete = "INSERT DATA { \n \"An example value\".\r}"; - -// "SELECT * WHERE {\r\ -// ?subject ?predicate ?object .\r\ -// } LIMIT 10"; - } + if (!$in_memory_discrete){ + $in_memory_discrete = "INSERT DATA { \n \"An example value\".\r}"; + } }); const run = async () => { try{ + reset_toasts(); await sparql_update($in_memory_discrete); toast_success($t("app.sparql_update_editor.success")); } catch(e) { toast_error(e); } } + const openViewer = () => { + reset_toasts(); + open_viewer(); + }
@@ -63,8 +65,8 @@ Run Update + {#if $cur_tab.doc.can_edit} + + {/if} + + + + + + +
+ \ No newline at end of file diff --git a/ng-app/src/classes.ts b/ng-app/src/classes.ts index acb3bfc..115b96e 100644 --- a/ng-app/src/classes.ts +++ b/ng-app/src/classes.ts @@ -200,7 +200,7 @@ export const official_classes = { "ng:n": "SPARQL Query", // edited with YASGUI or Sparnatural, displayed with highlight.js https://github.com/highlightjs/highlightjs-turtle/tree/master "ng:a": "Saved SPARQL Query that can be invoked", "ng:o": "n:g:z:sparql:invoke", - "ng:w": "n:g:z:sparql_query:yasgui", + "ng:w": "n:g:z:sparql_query", "ng:compat": ["code:sparql", "file:iana:application:sparql-query","file:iana:application:x-sparql-query"], }, "query:sparql_update": { @@ -208,7 +208,7 @@ export const official_classes = { "ng:n": "SPARQL Update", // edited with YASGUI, displayed with highlight.js https://github.com/highlightjs/highlightjs-turtle/tree/master "ng:a": "Saved SPARQL Update that can be invoked", "ng:o": "n:g:z:sparql:invoke", - "ng:w": "n:g:z:sparql_update:yasgui", + "ng:w": "n:g:z:sparql_update", "ng:compat": ["code:sparql", "file:iana:application:sparql-update"], }, "query:graphql": { diff --git a/ng-app/src/locales/en.json b/ng-app/src/locales/en.json index 9095456..b10a5a0 100644 --- a/ng-app/src/locales/en.json +++ b/ng-app/src/locales/en.json @@ -218,7 +218,7 @@ "remove_wallet": "Remove wallet from Device", "remove_wallet_modal.title": "Remove wallet?", "remove_wallet_modal.confirm": "Are you sure you want to remove this wallet from your device?", - "create_text_code": "Generate TextCode to export", + "create_text_code": "Export by generating a TextCode", "scan_qr.title": "Export by scanning a QR-Code", "scan_qr.no_camera": "If to the contrary, the other device does not have a camera, ", "scan_qr.other_has_camera": "If the other device where you want to import the Wallet, has a camera, then you can just click on the Back button and select Generate QR to export", @@ -459,7 +459,7 @@ }, "wallet_login_textcode": { "title": "Import Wallet from TextCode", - "description": "To generate a TextCode, open a logged in device and go to
User Panel > Wallet > Generate TextCode to export.", + "description": "To generate a TextCode, open a logged in device and go to
User Panel > Wallet > Export by generating a TextCode.", "import_btn": "Import with TextCode", "enter_here": "Enter your TextCode here below and click on Import button" }, diff --git a/ng-app/src/routes/WalletInfo.svelte b/ng-app/src/routes/WalletInfo.svelte index f944e5e..08241ea 100644 --- a/ng-app/src/routes/WalletInfo.svelte +++ b/ng-app/src/routes/WalletInfo.svelte @@ -195,7 +195,7 @@ - +
  • {$t("pages.wallet_info.title")}

  • diff --git a/ng-app/src/store.ts b/ng-app/src/store.ts index c3043fa..72cd530 100644 --- a/ng-app/src/store.ts +++ b/ng-app/src/store.ts @@ -111,6 +111,12 @@ export const toast = function(level, text) { }); } +export const reset_toasts = function() { + toasts.update((old)=>{ + return []; + }); +} + export const toast_error = (text) => { toast("error", text); } @@ -377,13 +383,22 @@ export const digest_to_string = function(digest) { return encode(buffer.buffer); }; +export const sparql_query = async function(sparql:string, union:boolean) { + let session = get(active_session); + if (!session) { + throw new Error("no session"); + } + let nuri = union ? undefined : "did:ng:"+get(cur_tab).branch.nuri; + return await ng.sparql_query(session.session_id, sparql, nuri); +} + export const sparql_update = async function(sparql:string) { let session = get(active_session); if (!session) { throw new Error("no session"); } let nuri = "did:ng:"+get(cur_tab).branch.nuri; - await ng.sparql_update(session.session_id, nuri, sparql); + await ng.sparql_update(session.session_id, sparql, nuri); } export const branch_subscribe = function(nuri:string, in_tab:boolean) { diff --git a/ng-app/src/styles.css b/ng-app/src/styles.css index 95f2b59..d4b0f39 100644 --- a/ng-app/src/styles.css +++ b/ng-app/src/styles.css @@ -9,6 +9,11 @@ // according to those terms. */ +td.hljs { + padding-left: 0 !important; + padding-right: 0 !important; +} + /** To format paths, like Settings > Wallet > Generate Wallet QR */ .path { font-family: monospace; diff --git a/ng-app/src/turtle.js b/ng-app/src/turtle.js new file mode 100644 index 0000000..d623c21 --- /dev/null +++ b/ng-app/src/turtle.js @@ -0,0 +1,139 @@ +/* +Language: Turtle +Author: Redmer KRONEMEIJER +Contributors: Mark ELLIS , Vladimir ALEXIEV +*/ + + +function hljsDefineTurtle(hljs) { + // export default function (hljs) { + var KEYWORDS = { + keyword: "BASE|2 PREFIX|5 @base|10 @prefix|10", + literal: "true false", + built_in: "a", + }; + + var IRI_LITERAL = { + // https://www.w3.org/TR/turtle/#grammar-production-IRIREF + className: "literal", + relevance: 1, // XML tags look also like relative IRIs + begin: //, + // illegal: /[^\x00-\x20"{}|^`\\]/, // TODO: https://www.w3.org/TR/turtle/#grammar-production-UCHAR + }; + + // https://www.w3.org/TR/turtle/#terminals + var PN_CHARS_BASE = + "A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u10000-\uEFFFF"; + var PN_CHARS_U = PN_CHARS_BASE + "_"; + var PN_CHARS = "-" + PN_CHARS_U + "0-9\u00B7\u0300-\u036F\u203F-\u2040"; + var BLANK_NODE_LABEL = + "_:[" + PN_CHARS_U + "0-9]([" + PN_CHARS + ".]*[" + PN_CHARS + "])?"; + var PN_PREFIX = + "[" + PN_CHARS_BASE + "]([" + PN_CHARS + ".]*[" + PN_CHARS + "])?"; + var PERCENT = "%[0-9A-Fa-f][0-9A-Fa-f]"; + var PN_LOCAL_ESC = "\\\\[_~.!$&'()*+,;=/?#@%-]"; + var PLX = PERCENT + "|" + PN_LOCAL_ESC; + var PNAME_NS = "(" + PN_PREFIX + ")?:"; + var PN_LOCAL = + "([" + + PN_CHARS_U + + ":0-9]|" + + PLX + + ")([" + + PN_CHARS + + ".:]|" + + PLX + + ")*([" + + PN_CHARS + + ":]|" + + PLX + + ")?"; + var PNAME_LN = PNAME_NS + PN_LOCAL; + var PNAME_NS_or_LN = PNAME_NS + "(" + PN_LOCAL + ")?"; + + var PNAME = { + begin: PNAME_NS_or_LN, + relevance: 0, + className: "symbol", + }; + + var BLANK_NODE = { + begin: BLANK_NODE_LABEL, + relevance: 10, + className: "template-variable", + }; + + var LANGTAG = { + begin: /@[a-zA-Z]+([a-zA-Z0-9-]+)*/, + className: "type", + relevance: 0, // also catches objectivec keywords like: @protocol, @optional + }; + + var DATATYPE = { + begin: "\\^\\^" + PNAME_LN, + className: "type", + relevance: 10, + }; + + var TRIPLE_APOS_STRING = { + begin: /'''/, + end: /'''/, + className: "string", + relevance: 0, + }; + + var TRIPLE_QUOTE_STRING = { + begin: /"""/, + end: /"""/, + className: "string", + relevance: 0, + }; + + var APOS_STRING_LITERAL = JSON.parse(JSON.stringify(hljs.APOS_STRING_MODE)); + APOS_STRING_LITERAL.relevance = 0; + + var QUOTE_STRING_LITERAL = JSON.parse(JSON.stringify(hljs.QUOTE_STRING_MODE)); + QUOTE_STRING_LITERAL.relevance = 0; + + return { + name: "Turtle", + case_insensitive: true, // however `true` and `@prefix` are oblig. cased thus + keywords: KEYWORDS, + aliases: ["turtle", "ttl", "n3"], + contains: [ + LANGTAG, + DATATYPE, + IRI_LITERAL, + BLANK_NODE, + PNAME, + TRIPLE_APOS_STRING, + TRIPLE_QUOTE_STRING, // order matters + APOS_STRING_LITERAL, + QUOTE_STRING_LITERAL, + hljs.C_NUMBER_MODE, + hljs.HASH_COMMENT_MODE, + ], + exports: { + LANGTAG: LANGTAG, + DATATYPE: DATATYPE, + IRI_LITERAL: IRI_LITERAL, + BLANK_NODE: BLANK_NODE, + PNAME: PNAME, + TRIPLE_APOS_STRING: TRIPLE_APOS_STRING, + TRIPLE_QUOTE_STRING: TRIPLE_QUOTE_STRING, + APOS_STRING_LITERAL: APOS_STRING_LITERAL, + QUOTE_STRING_LITERAL: QUOTE_STRING_LITERAL, + NUMBER: hljs.C_NUMBER_MODE, + KEYWORDS: KEYWORDS, + }, + }; +} + +// module.exports = function (hljs) { +// hljs.registerLanguage("turtle", hljsDefineTurtle); +// }; + +// module.exports.definer = hljsDefineTurtle; + +export const definer = hljsDefineTurtle; diff --git a/ng-app/src/zeras.ts b/ng-app/src/zeras.ts index f837085..d4001a4 100644 --- a/ng-app/src/zeras.ts +++ b/ng-app/src/zeras.ts @@ -85,12 +85,12 @@ export const official_apps = { "ng:b": "TripleViewer", "ng:o": ["data:graph"], }, - "n:g:z:sparql_query:yasgui": { + "n:g:z:sparql_query": { "ng:n": "SPARQL Query", "ng:a": "View, edit and invoke a Graph SPARQL query", "ng:c": "app", "ng:u": "sparql_query",//favicon. can be a did:ng:j - "ng:g": "n:g:z:sparql_query:yasgui", + "ng:g": "n:g:z:sparql_query", "ng:b": "SparqlQueryEditor", // YASGUI of Zazuko https://github.com/zazuko/trifid/tree/main/packages/yasgui "ng:o": ["data:graph"], "ng:w": ["query:sparql"], @@ -115,12 +115,12 @@ export const official_apps = { "ng:o": ["data:graph"], "ng:w": ["query:graphql"], }, - "n:g:z:sparql_update:yasgui": { + "n:g:z:sparql_update": { "ng:n": "SPARQL Update", "ng:a": "View, edit and invoke a Graph SPARQL Update", "ng:c": "app", "ng:u": "sparql_query",//favicon. can be a did:ng:j - "ng:g": "n:g:z:sparql_update:yasgui", + "ng:g": "n:g:z:sparql_update", "ng:b": "SparqlUpdateEditor", // YASGUI of Zazuko https://github.com/zazuko/trifid/tree/main/packages/yasgui "ng:o": [], "ng:w": ["query:sparql_update","data:graph"], diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs index 2aa3cdb..273ea5e 100644 --- a/ng-sdk-js/src/lib.rs +++ b/ng-sdk-js/src/lib.rs @@ -259,16 +259,26 @@ pub async fn session_headless_stop(session_id: JsValue, force_close: bool) -> Re Ok(()) } - +/*let app_response = nextgraph::verifier::prepare_app_response_for_js(app_response)?; */ #[cfg(wasmpack_target = "nodejs")] #[wasm_bindgen] -pub async fn sparql_query(session_id: JsValue, sparql: String) -> Result { +pub async fn sparql_query( + session_id: JsValue, + sparql: String, + nuri: JsValue, +) -> Result { let session_id: u64 = serde_wasm_bindgen::from_value::(session_id) .map_err(|_| "Invalid session_id".to_string())?; + let nuri = if nuri.is_string() { + NuriV0::new_from(&nuri.as_string().unwrap()) + .map_err(|_| "Deserialization error of Nuri".to_string())? + } else { + NuriV0::new_entire_user_site() + }; let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_read_query(), - nuri: NuriV0::new_entire_user_site(), + nuri, payload: Some(AppRequestPayload::new_sparql_query(sparql)), session_id, }); @@ -305,8 +315,8 @@ pub async fn sparql_query(session_id: JsValue, sparql: String) -> Result Result<(), String> { let session_id: u64 = serde_wasm_bindgen::from_value::(session_id) .map_err(|_| "Invalid session_id".to_string())?; @@ -329,6 +339,58 @@ pub async fn sparql_update( } } +#[cfg(not(wasmpack_target = "nodejs"))] +#[wasm_bindgen] +pub async fn sparql_query( + session_id: JsValue, + sparql: String, + nuri: JsValue, +) -> Result { + let session_id: u64 = serde_wasm_bindgen::from_value::(session_id) + .map_err(|_| "Invalid session_id".to_string())?; + + let nuri = if nuri.is_string() { + NuriV0::new_from(&nuri.as_string().unwrap()) + .map_err(|_| "Deserialization error of Nuri".to_string())? + } else { + NuriV0::new_entire_user_site() + }; + let request = AppRequest::V0(AppRequestV0 { + command: AppRequestCommandV0::new_read_query(), + nuri, + payload: Some(AppRequestPayload::new_sparql_query(sparql)), + session_id, + }); + + let response = nextgraph::local_broker::app_request(request) + .await + .map_err(|e: NgError| e.to_string())?; + + let AppResponse::V0(res) = response; + match res { + AppResponseV0::False => return Ok(JsValue::FALSE), + AppResponseV0::True => return Ok(JsValue::TRUE), + AppResponseV0::Graph(graph) => { + let triples: Vec = serde_bare::from_slice(&graph) + .map_err(|_| "Deserialization error of Vec".to_string())?; + + Ok(JsValue::from( + triples + .into_iter() + .map(|x| JsValue::from_str(&x.to_string())) + .collect::(), + )) + } + AppResponseV0::QueryResult(buf) => { + let string = String::from_utf8(buf) + .map_err(|_| "Deserialization error of JSON QueryResult String".to_string())?; + js_sys::JSON::parse(&string) + } + AppResponseV0::Error(e) => Err(e.to_string().into()), + _ => Err("invalid AppResponse".to_string().into()), + } +} + #[cfg(wasmpack_target = "nodejs")] #[wasm_bindgen] pub async fn rdf_dump(session_id: JsValue) -> Result { diff --git a/ng-verifier/src/lib.rs b/ng-verifier/src/lib.rs index fcb8564..1dbe491 100644 --- a/ng-verifier/src/lib.rs +++ b/ng-verifier/src/lib.rs @@ -17,7 +17,7 @@ mod rocksdb_user_storage; use ng_net::app_protocol::*; use ng_oxigraph::oxrdf::Triple; -fn triples_ser_to_json_ser(ser: &Vec) -> Result, String> { +pub fn triples_ser_to_json_string(ser: &Vec) -> Result { let triples: Vec = serde_bare::from_slice(ser) .map_err(|_| "Deserialization error of Vec".to_string())?; @@ -26,8 +26,12 @@ fn triples_ser_to_json_ser(ser: &Vec) -> Result, String> { triples_json.push(serde_json::Value::String(insert.to_string())); } let triples_json = serde_json::Value::Array(triples_json); - let json = serde_json::to_string(&triples_json) - .map_err(|_| "Cannot serialize Vec to JSON".to_string())?; + serde_json::to_string(&triples_json) + .map_err(|_| "Cannot serialize Vec to JSON".to_string()) +} + +fn triples_ser_to_json_ser(ser: &Vec) -> Result, String> { + let json = triples_ser_to_json_string(ser)?; Ok(json.as_bytes().to_vec()) } diff --git a/ng-verifier/src/request_processor.rs b/ng-verifier/src/request_processor.rs index 87035af..56bbade 100644 --- a/ng-verifier/src/request_processor.rs +++ b/ng-verifier/src/request_processor.rs @@ -169,6 +169,7 @@ impl Verifier { if update { return Err(NgError::InvalidTarget); } else { + //log_info!("QUERYING UNION GRAPH"); return Ok(None); } } @@ -248,7 +249,7 @@ impl Verifier { query, )))) = payload { - log_debug!("query={}", query); + //log_debug!("query={}", query); let store = self.graph_dataset.as_ref().unwrap(); let parsed = Query::parse(&query, None); if parsed.is_err() { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af5ffa8..1c0e313 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,7 @@ importers: dayjs: ^1.11.10 flowbite: ^1.6.5 flowbite-svelte: ^0.43.3 + highlight.js: ^11.10.0 html5-qrcode: ^2.3.8 internal-ip: ^7.0.0 ng-sdk-js: workspace:^0.1.0-preview.1 @@ -42,6 +43,7 @@ importers: svelte-check: ^3.0.0 svelte-codemirror-editor: ^1.4.0 svelte-heros-v2: ^0.10.12 + svelte-highlight: ^7.7.0 svelte-i18n: ^4.0.0 svelte-inview: ^4.0.2 svelte-preprocess: ^5.0.3 @@ -83,6 +85,7 @@ importers: autoprefixer: 10.4.14_postcss@8.4.24 cross-env: 7.0.3 dayjs: 1.11.10 + highlight.js: 11.10.0 internal-ip: 7.0.0 node-gzip: 1.1.2 postcss: 8.4.24 @@ -91,6 +94,7 @@ importers: svelte: 3.59.1 svelte-check: 3.4.3_sxhny56dlbcmwov4vk7qwrzshi svelte-heros-v2: 0.10.12_svelte@3.59.1 + svelte-highlight: 7.7.0 svelte-preprocess: 5.0.4_vmz4xia4c7tzh4ii3qac2x3tom svelte-time: 0.8.0 tailwindcss: 3.3.2 @@ -1604,6 +1608,11 @@ packages: function-bind: 1.1.1 dev: true + /highlight.js/11.10.0: + resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==} + engines: {node: '>=12.0.0'} + dev: true + /html5-qrcode/2.3.8: resolution: {integrity: sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ==} dev: false @@ -2263,6 +2272,12 @@ packages: svelte: 3.59.1 dev: true + /svelte-highlight/7.7.0: + resolution: {integrity: sha512-umBiTz3fLbbNCA+wGlRhJOb54iC6ap8S/dxjauQ+g6a8oFGTOGQeOP2rJVoh/K1ssF7IjP4P0T3Yuiu+vtaG5Q==} + dependencies: + highlight.js: 11.10.0 + dev: true + /svelte-hmr/0.15.2_svelte@3.59.1: resolution: {integrity: sha512-q/bAruCvFLwvNbeE1x3n37TYFb3mTBJ6TrCq6p2CoFbSTNhDE9oAtEfpy+wmc9So8AG0Tja+X0/mJzX9tSfvIg==} engines: {node: ^12.20 || ^14.13.1 || >= 16}