navigation between docs and tabs, sparql update editor

pull/37/head
Niko PLP 1 month ago
parent a8ec95a583
commit 7c621509d2
  1. 4
      Cargo.lock
  2. 1
      nextgraph/src/lib.rs
  3. 363
      nextgraph/src/local_broker.rs
  4. 6
      ng-app/package.json
  5. 1
      ng-app/src-tauri/Cargo.toml
  6. 5
      ng-app/src-tauri/src/lib.rs
  7. 8
      ng-app/src/App.svelte
  8. 36
      ng-app/src/api.ts
  9. 49
      ng-app/src/apps/ListView.svelte
  10. 61
      ng-app/src/apps/SparqlUpdateEditor.svelte
  11. 59
      ng-app/src/base64url.js
  12. 371
      ng-app/src/classes.ts
  13. 116
      ng-app/src/lib/DataClassIcon.svelte
  14. 111
      ng-app/src/lib/Document.svelte
  15. 350
      ng-app/src/lib/FullLayout.svelte
  16. 44
      ng-app/src/lib/Home.svelte
  17. 4
      ng-app/src/lib/Login.svelte
  18. 24
      ng-app/src/lib/Test.svelte
  19. 2
      ng-app/src/lib/components/Logo.svelte
  20. 36
      ng-app/src/lib/components/NavBar.svelte
  21. 10
      ng-app/src/lib/components/NavIcon.svelte
  22. 19
      ng-app/src/lib/components/PaneHeader.svelte
  23. 2
      ng-app/src/locales/de.json
  24. 50
      ng-app/src/locales/en.json
  25. 1
      ng-app/src/routes/Home.svelte
  26. 52
      ng-app/src/routes/NURI.svelte
  27. 1
      ng-app/src/routes/NotFound.svelte
  28. 54
      ng-app/src/routes/Shared.svelte
  29. 46
      ng-app/src/routes/Site.svelte
  30. 12
      ng-app/src/routes/WalletCreate.svelte
  31. 15
      ng-app/src/routes/WalletLogin.svelte
  32. 226
      ng-app/src/store.ts
  33. 12
      ng-app/src/styles.css
  34. 361
      ng-app/src/tab.ts
  35. 168
      ng-app/src/zeras.ts
  36. 3
      ng-app/vite.config.ts
  37. 4
      ng-net/src/actors/client/pin_repo.rs
  38. 4
      ng-net/src/actors/client/topic_sub.rs
  39. 141
      ng-net/src/app_protocol.rs
  40. 42
      ng-net/src/broker.rs
  41. 11
      ng-net/src/connection.rs
  42. 1
      ng-oxigraph/Cargo.toml
  43. 14
      ng-oxigraph/src/oxigraph/storage/mod.rs
  44. 69
      ng-oxigraph/src/oxigraph/store.rs
  45. 2
      ng-repo/src/branch.rs
  46. 11
      ng-repo/src/event.rs
  47. 12
      ng-repo/src/repo.rs
  48. 37
      ng-repo/src/store.rs
  49. 173
      ng-repo/src/types.rs
  50. 1
      ng-sdk-js/Cargo.toml
  51. 62
      ng-sdk-js/src/lib.rs
  52. 1
      ng-verifier/Cargo.toml
  53. 18
      ng-verifier/src/commits/mod.rs
  54. 84
      ng-verifier/src/commits/transaction.rs
  55. 38
      ng-verifier/src/lib.rs
  56. 55
      ng-verifier/src/rocksdb_user_storage.rs
  57. 62
      ng-verifier/src/user_storage/branch.rs
  58. 6
      ng-verifier/src/user_storage/repo.rs
  59. 18
      ng-verifier/src/user_storage/storage.rs
  60. 138
      ng-verifier/src/verifier.rs
  61. 6
      ng-wallet/src/types.rs
  62. 32
      ngaccount/web/src/routes/Create.svelte
  63. 1
      ngaccount/web/src/routes/NotFound.svelte
  64. 135
      pnpm-lock.yaml

4
Cargo.lock generated

@ -3327,6 +3327,7 @@ dependencies = [
"ng-repo",
"ng-wallet",
"serde",
"serde_bare",
"serde_bytes",
"serde_json",
"sys-locale",
@ -3440,6 +3441,7 @@ dependencies = [
"libc",
"md-5",
"memchr",
"ng-repo",
"ng-rocksdb",
"oxilangtag",
"oxiri",
@ -3523,7 +3525,6 @@ dependencies = [
"serde-wasm-bindgen",
"serde_bare",
"serde_bytes",
"serde_json",
"sys-locale",
"wasm-bindgen",
"wasm-bindgen-futures",
@ -3577,6 +3578,7 @@ dependencies = [
"serde",
"serde_bare",
"serde_bytes",
"serde_json",
"web-time",
"yrs",
]

@ -93,6 +93,7 @@ pub mod verifier {
pub mod protocol {
pub use ng_net::app_protocol::*;
}
pub use ng_verifier::prepare_app_response_for_js;
}
pub mod wallet {

@ -13,9 +13,9 @@ use std::fs::{read, remove_file, write};
use std::path::PathBuf;
use async_once_cell::OnceCell;
use async_std::sync::{Arc, Mutex, RwLock};
use async_std::sync::{Arc, Condvar, Mutex, RwLock};
use futures::channel::mpsc;
use futures::SinkExt;
use futures::{SinkExt, StreamExt};
use lazy_static::lazy_static;
use once_cell::sync::Lazy;
use pdf_writer::{Content, Finish, Name, Pdf, Rect, Ref, Str};
@ -37,7 +37,7 @@ use ng_net::app_protocol::*;
use ng_net::broker::*;
use ng_net::connection::{AppConfig, ClientConfig, IConnect, NoiseFSM, StartConfig};
use ng_net::types::*;
use ng_net::utils::{Receiver, Sender};
use ng_net::utils::{spawn_and_log_error, Receiver, ResultSend, Sender};
use ng_net::{actor::*, actors::admin::*};
use ng_verifier::types::*;
@ -552,6 +552,7 @@ struct LocalBroker {
disconnections_sender: Sender<String>,
disconnections_receiver: Option<Receiver<String>>,
pump_cond: Option<Arc<(Mutex<bool>, Condvar)>>,
}
impl fmt::Debug for LocalBroker {
@ -565,6 +566,14 @@ impl fmt::Debug for LocalBroker {
}
}
#[doc(hidden)]
#[async_trait::async_trait]
pub trait ILocalBroker: Send + Sync + EActor {
async fn deliver(&mut self, event: Event, overlay: OverlayId, user: UserId);
async fn user_disconnected(&mut self, user_id: UserId);
}
// used to deliver events to the verifier on Clients, or Core that have Verifiers attached.
#[async_trait::async_trait]
impl ILocalBroker for LocalBroker {
@ -599,7 +608,56 @@ impl EActor for LocalBroker {
}
}
async fn pump(
mut reader: Receiver<LocalBrokerMessage>,
pair: Arc<(Mutex<bool>, Condvar)>,
) -> ResultSend<()> {
while let Some(message) = reader.next().await {
let (lock, cvar) = &*pair;
let mut running = lock.lock().await;
while !*running {
running = cvar.wait(running).await;
}
let mut broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(Box::new(NgError::LocalBrokerNotInitialized)),
Some(Ok(broker)) => broker.write().await,
};
match message {
LocalBrokerMessage::Deliver {
event,
overlay,
user,
} => broker.deliver(event, overlay, user).await,
LocalBrokerMessage::Disconnected { user_id } => broker.user_disconnected(user_id).await,
}
}
log_debug!("END OF PUMP");
Ok(())
}
impl LocalBroker {
async fn stop_pump(&self) {
let (lock, cvar) = self.pump_cond.as_deref().as_ref().unwrap();
let mut running = lock.lock().await;
*running = false;
cvar.notify_one();
}
async fn start_pump(&self) {
let (lock, cvar) = self.pump_cond.as_deref().as_ref().unwrap();
let mut running = lock.lock().await;
*running = true;
cvar.notify_one();
}
fn init_pump(&mut self, broker_pump_receiver: Receiver<LocalBrokerMessage>) {
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = Arc::clone(&pair);
self.pump_cond = Some(pair);
spawn_and_log_error(pump(broker_pump_receiver, pair2));
}
// fn storage_path_for_user(&self, user_id: &UserId) -> Option<PathBuf> {
// match &self.config {
// LocalBrokerConfig::InMemory | LocalBrokerConfig::JsStorage(_) => None,
@ -914,12 +972,12 @@ impl LocalBroker {
}
fn get_wallet_and_session(
&mut self,
&self,
user_id: &UserId,
) -> Result<(&SensitiveWallet, &mut Session), NgError> {
) -> Result<(&SensitiveWallet, &Session), NgError> {
let session_idx = self.user_to_local_session_id_for_mut(user_id)?;
let session = self.opened_sessions_list[session_idx]
.as_mut()
.as_ref()
.ok_or(NgError::SessionNotFound)?;
let wallet = &match &session.config {
SessionConfig::WithCredentialsV0(_) | SessionConfig::HeadlessV0(_) => {
@ -934,6 +992,14 @@ impl LocalBroker {
Ok((wallet, session))
}
fn get_session_mut(&mut self, user_id: &UserId) -> Result<&mut Session, NgError> {
let session_idx = self.user_to_local_session_id_for_mut(user_id)?;
self.opened_sessions_list[session_idx]
.as_mut()
.ok_or(NgError::SessionNotFound)
}
async fn disconnect_session(&mut self, user_id: &PubKey) -> Result<(), NgError> {
match self.opened_sessions.get(user_id) {
Some(session) => {
@ -1022,6 +1088,12 @@ impl LocalBroker {
let private_store_id = self
.get_site_store_of_session(&session, SiteStoreType::Private)?
.to_string();
let protected_store_id = self
.get_site_store_of_session(&session, SiteStoreType::Protected)?
.to_string();
let public_store_id = self
.get_site_store_of_session(&session, SiteStoreType::Public)?
.to_string();
let user_id = session.config.user_id();
@ -1035,6 +1107,8 @@ impl LocalBroker {
session_id: idx as u64,
user: user_id,
private_store_id,
protected_store_id,
public_store_id,
})
}
@ -1059,7 +1133,9 @@ impl LocalBroker {
Ok(SessionInfo {
session_id: first_available,
user: user_id,
private_store_id: String::new(), // will be updated when the AppSessionStart replies arrive from broker
private_store_id: String::new(), // will be updated when the AppSessionStart reply arrives from broker
protected_store_id: String::new(),
public_store_id: String::new(),
})
}
@ -1360,7 +1436,10 @@ async fn init_(config: LocalBrokerConfig) -> Result<Arc<RwLock<LocalBroker>>, Ng
}
};
let (disconnections_sender, disconnections_receiver) = mpsc::unbounded::<String>();
let local_broker = LocalBroker {
let (localbroker_pump_sender, broker_pump_receiver) = mpsc::unbounded::<LocalBrokerMessage>();
let mut local_broker = LocalBroker {
config,
wallets,
opened_wallets: HashMap::new(),
@ -1373,7 +1452,10 @@ async fn init_(config: LocalBrokerConfig) -> Result<Arc<RwLock<LocalBroker>>, Ng
disconnections_sender,
disconnections_receiver: Some(disconnections_receiver),
headless_connected_to_remote_broker: false,
pump_cond: None,
};
local_broker.init_pump(broker_pump_receiver);
//log_debug!("{:?}", &local_broker);
let broker = Arc::new(RwLock::new(local_broker));
@ -1381,7 +1463,7 @@ async fn init_(config: LocalBrokerConfig) -> Result<Arc<RwLock<LocalBroker>>, Ng
BROKER
.write()
.await
.set_local_broker(Arc::clone(&broker) as Arc<RwLock<dyn ILocalBroker>>);
.set_local_broker(localbroker_pump_sender);
Ok(broker)
}
@ -2135,11 +2217,13 @@ pub async fn session_start(config: SessionConfig) -> Result<SessionInfo, NgError
if let Ok(AppResponse::V0(AppResponseV0::SessionStart(AppSessionStartResponse::V0(response)))) = res {
session_info.private_store_id = response.private_store.to_string();
session_info.protected_store_id = response.protected_store.to_string();
session_info.public_store_id = response.public_store.to_string();
}
Ok(session_info)
},
_ => panic!("don't call session_start with a SessionConfig different from HeadlessV0 and a LocalBroker configured for Headless")
_ => panic!("don't call session_start with a SessionConfig different from HeadlessV0 when the LocalBroker is configured for Headless")
}
}
// TODO: implement SessionConfig::WithCredentials . VerifierType::Remote => it needs to establish a connection to remote here, then send the AppSessionStart in it.
@ -2164,6 +2248,12 @@ pub async fn session_start(config: SessionConfig) -> Result<SessionInfo, NgError
private_store_id: broker
.get_site_store_of_session(sess, SiteStoreType::Private)?
.to_string(),
protected_store_id: broker
.get_site_store_of_session(sess, SiteStoreType::Protected)?
.to_string(),
public_store_id: broker
.get_site_store_of_session(sess, SiteStoreType::Public)?
.to_string(),
});
}
}
@ -2241,7 +2331,7 @@ fn get_client_info(client_type: ClientType) -> ClientInfo {
#[doc(hidden)]
pub async fn user_connect_with_device_info(
info: ClientInfo,
user_id: &UserId,
original_user_id: &UserId,
location: Option<String>,
) -> Result<Vec<(String, String, String, Option<String>, f64)>, NgError> {
//FIXME: release this write lock much sooner than at the end of the loop of all tries to connect to some servers ?
@ -2253,139 +2343,146 @@ pub async fn user_connect_with_device_info(
local_broker.err_if_headless()?;
let (wallet, session) = local_broker.get_wallet_and_session(user_id)?;
let (client, sites, brokers, peer_key) = {
let (wallet, session) = local_broker.get_wallet_and_session(original_user_id)?;
match wallet {
SensitiveWallet::V0(wallet) => (
wallet.client.clone().unwrap(),
wallet.sites.clone(),
wallet.brokers.clone(),
session.peer_key.clone(),
),
}
};
let mut result: Vec<(String, String, String, Option<String>, f64)> = Vec::new();
let arc_cnx: Arc<Box<dyn IConnect>> = Arc::new(Box::new(ConnectionWebSocket {}));
match wallet {
SensitiveWallet::V0(wallet) => {
let client = wallet.client.as_ref().unwrap();
let client_priv = &client.sensitive_client_storage.priv_key;
let client_name = &client.name;
let auto_open = &client.auto_open;
// log_info!(
// "XXXX {} name={:?} auto_open={:?} {:?}",
// client_id.to_string(),
// client_name,
// auto_open,
// wallet
// );
for user in auto_open {
let user_id = user.to_string();
let peer_key = &session.peer_key;
let peer_id = peer_key.to_pub();
log_info!(
"connecting with local peer_id {} for user {}",
peer_id,
user_id
);
let site = wallet.sites.get(&user_id);
if site.is_none() {
result.push((
user_id,
"".into(),
"".into(),
Some("Site is missing".into()),
get_unix_time(),
));
continue;
}
let site = site.unwrap();
let user_priv = site.get_individual_user_priv_key().unwrap();
let core = site.cores[0]; //TODO: cycle the other cores if failure to connect (failover)
let server_key = core.0;
let broker = wallet.brokers.get(&core.0.to_string());
if broker.is_none() {
result.push((
user_id,
core.0.to_string(),
"".into(),
Some("Broker is missing".into()),
get_unix_time(),
));
continue;
}
let brokers = broker.unwrap();
let mut tried: Option<(String, String, String, Option<String>, f64)> = None;
//TODO: on tauri (or forward in local broker, or CLI), prefer a Public to a Domain. Domain always comes first though, so we need to reorder the list
//TODO: use site.bootstraps to order the list of brokerInfo.
for broker_info in brokers {
match broker_info {
BrokerInfoV0::ServerV0(server) => {
let url = server.get_ws_url(&location).await;
log_debug!("URL {:?}", url);
//Option<(String, Vec<BindAddress>)>
if url.is_some() {
let url = url.unwrap();
if url.1.is_empty() {
// TODO deal with Box(Dyn)Public -> tunnel, and on tauri/forward/CLIs, deal with all Box -> direct connections (when url.1.len is > 0)
let res = BROKER
.write()
.await
.connect(
arc_cnx.clone(),
peer_key.clone(),
peer_id,
server_key,
StartConfig::Client(ClientConfig {
url: url.0.clone(),
name: client_name.clone(),
user_priv: user_priv.clone(),
client_priv: client_priv.clone(),
info: info.clone(),
registration: Some(core.1),
}),
)
.await;
log_debug!("broker.connect : {:?}", res);
tried = Some((
user_id.clone(),
core.0.to_string(),
url.0.into(),
match &res {
Ok(_) => None,
Err(e) => Some(e.to_string()),
},
get_unix_time(),
));
}
if tried.is_some() && tried.as_ref().unwrap().3.is_none() {
if let Err(e) =
session.verifier.connection_opened(server_key).await
{
log_err!(
"got error while processing opened connection {:?}",
e
);
Broker::close_all_connections().await;
tried.as_mut().unwrap().3 = Some(e.to_string());
}
break;
} else {
log_debug!("Failed connection {:?}", tried);
}
let client_priv = &client.sensitive_client_storage.priv_key;
let client_name = &client.name;
let auto_open = &client.auto_open;
// log_info!(
// "XXXX {} name={:?} auto_open={:?} {:?}",
// client_id.to_string(),
// client_name,
// auto_open,
// wallet
// );
for user in auto_open {
let user_id = user.to_string();
let peer_id = peer_key.to_pub();
log_info!(
"connecting with local peer_id {} for user {}",
peer_id,
user_id
);
let site = sites.get(&user_id);
if site.is_none() {
result.push((
user_id,
"".into(),
"".into(),
Some("Site is missing".into()),
get_unix_time(),
));
continue;
}
let site = site.unwrap();
let user_priv = site.get_individual_user_priv_key().unwrap();
let core = site.cores[0]; //TODO: cycle the other cores if failure to connect (failover)
let server_key = core.0;
let broker = brokers.get(&core.0.to_string());
if broker.is_none() {
result.push((
user_id,
core.0.to_string(),
"".into(),
Some("Broker is missing".into()),
get_unix_time(),
));
continue;
}
let brokers = broker.unwrap();
let mut tried: Option<(String, String, String, Option<String>, f64)> = None;
//TODO: on tauri (or forward in local broker, or CLI), prefer a Public to a Domain. Domain always comes first though, so we need to reorder the list
//TODO: use site.bootstraps to order the list of brokerInfo.
local_broker.stop_pump().await;
for broker_info in brokers {
match broker_info {
BrokerInfoV0::ServerV0(server) => {
let url = server.get_ws_url(&location).await;
log_debug!("URL {:?}", url);
//Option<(String, Vec<BindAddress>)>
if url.is_some() {
let url = url.unwrap();
if url.1.is_empty() {
// TODO deal with Box(Dyn)Public -> tunnel, and on tauri/forward/CLIs, deal with all Box -> direct connections (when url.1.len is > 0)
let res = BROKER
.write()
.await
.connect(
arc_cnx.clone(),
peer_key.clone(),
peer_id,
server_key,
StartConfig::Client(ClientConfig {
url: url.0.clone(),
name: client_name.clone(),
user_priv: user_priv.clone(),
client_priv: client_priv.clone(),
info: info.clone(),
registration: Some(core.1),
}),
)
.await;
log_debug!("broker.connect : {:?}", res);
tried = Some((
user_id.clone(),
core.0.to_string(),
url.0.into(),
match &res {
Ok(_) => None,
Err(e) => Some(e.to_string()),
},
get_unix_time(),
));
}
if tried.is_some() && tried.as_ref().unwrap().3.is_none() {
let res = {
let session = local_broker.get_session_mut(original_user_id)?;
session.verifier.connection_opened(server_key).await
};
if res.is_err() {
let e = res.unwrap_err();
log_err!("got error while processing opened connection {:?}", e);
Broker::close_all_connections().await;
tried.as_mut().unwrap().3 = Some(e.to_string());
} else {
local_broker.start_pump().await;
}
break;
} else {
log_debug!("Failed connection {:?}", tried);
}
// Core information is discarded
_ => {}
}
}
if tried.is_none() {
tried = Some((
user_id,
core.0.to_string(),
"".into(),
Some("No broker found".into()),
get_unix_time(),
));
}
result.push(tried.unwrap());
// Core information is discarded
_ => {}
}
}
if tried.is_none() {
tried = Some((
user_id,
core.0.to_string(),
"".into(),
Some("No broker found".into()),
get_unix_time(),
));
}
result.push(tried.unwrap());
}
Ok(result)
}

@ -16,17 +16,23 @@
"tauri": "tauri"
},
"dependencies": {
"@codemirror/language": "^6.10.2",
"@codemirror/legacy-modes": "^6.4.0",
"@popperjs/core": "^2.11.8",
"@tauri-apps/api": "2.0.0-alpha.8",
"@tauri-apps/plugin-barcode-scanner": "2.0.0-alpha.0",
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
"async-proxy": "^0.4.1",
"classnames": "^2.3.2",
"codemirror": "^6.0.0",
"flowbite": "^1.6.5",
"flowbite-svelte": "^0.43.3",
"html5-qrcode": "^2.3.8",
"ng-sdk-js": "workspace:^0.1.0-preview.1",
"sparql": "link:@codemirror/legacy-modes/mode/sparql",
"svelte-codemirror-editor": "^1.4.0",
"svelte-i18n": "^4.0.0",
"svelte-inview": "^4.0.2",
"svelte-spa-router": "^3.3.0",
"vite-plugin-top-level-await": "^1.3.1"
},

@ -25,6 +25,7 @@ tauri-utils = { version = "=2.0.0-alpha.7" }
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0"
serde_json = "1.0"
serde_bytes = "0.11.7"
async-std = { version = "1.12.0", features = ["attributes", "unstable"] }

@ -364,6 +364,7 @@ async fn app_request_stream(
main_window: tauri::Window,
) -> ResultSend<()> {
while let Some(app_response) = reader.next().await {
let app_response = nextgraph::verifier::prepare_app_response_for_js(app_response)?;
main_window.emit(&stream_id, app_response).unwrap();
}
@ -391,10 +392,10 @@ async fn doc_fetch_private_subscribe() -> Result<AppRequest, String> {
}
#[tauri::command(rename_all = "snake_case")]
async fn doc_fetch_repo_subscribe(repo_id: String) -> Result<AppRequest, String> {
async fn doc_fetch_repo_subscribe(repo_o: String) -> Result<AppRequest, String> {
let request = AppRequest::new(
AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)),
NuriV0::new_repo_target_from_string(repo_id).map_err(|e| e.to_string())?,
NuriV0::new_from(&repo_o).map_err(|e| e.to_string())?,
None,
);
Ok(request)

@ -36,6 +36,8 @@
import UserRegistered from "./routes/UserRegistered.svelte";
import Install from "./routes/Install.svelte";
import ScanQR from "./routes/ScanQR.svelte";
import Shared from "./routes/Shared.svelte";
import Site from "./routes/Site.svelte";
import ng from "./api";
import AccountInfo from "./routes/AccountInfo.svelte";
@ -56,7 +58,9 @@
routes.set("/user/accounts", AccountInfo);
routes.set("/wallet/scanqr", ScanQR);
if (import.meta.env.NG_APP_WEB) routes.set("/install", Install);
routes.set(/^\/did:ng(.*)/i, NURI);
routes.set("/shared", Shared);
routes.set("/site", Site);
routes.set(/^\/did:ng:(.*)/i, NURI);
routes.set("*", NotFound);
let unsubscribe = () => {};
@ -74,7 +78,7 @@
await disconnections_subscribe();
await select_default_lang();
} catch (e) {
console.error(e);
console.warn(e);
//console.log("called disconnections_subscribe twice");
}
let tauri_platform = import.meta.env.TAURI_PLATFORM;

@ -42,7 +42,7 @@ const mapping = {
"test": [ ],
"get_device_name": [],
"doc_fetch_private_subscribe": [],
"doc_fetch_repo_subscribe": ["repo_id"],
"doc_fetch_repo_subscribe": ["repo_o"],
}
@ -67,6 +67,22 @@ const handler = {
// } else if (path[0] === "wallet_create") {
// let res = await Reflect.apply(sdk[path], caller, args);
// return res;
} else if (path[0] === "app_request_stream") {
let callback = args[1];
let new_callback = (event) => {
if (event.V0.State?.graph?.triples) {
let json_str = new TextDecoder().decode(event.V0.State.graph.triples);
event.V0.State.graph.triples = JSON.parse(json_str);
} else if (event.V0.Patch?.graph) {
let inserts_json_str = new TextDecoder().decode(event.V0.Patch.graph.inserts);
event.V0.Patch.graph.inserts = JSON.parse(inserts_json_str);
let removes_json_str = new TextDecoder().decode(event.V0.Patch.graph.removes);
event.V0.Patch.graph.removes = JSON.parse(removes_json_str);
}
callback(event).then(()=> {})
};
args[1] = new_callback;
return Reflect.apply(sdk[path], caller, args)
} else {
return Reflect.apply(sdk[path], caller, args)
}
@ -156,10 +172,24 @@ const handler = {
if (event.payload.V0.FileBinary) {
event.payload.V0.FileBinary = Uint8Array.from(event.payload.V0.FileBinary);
}
if (event.payload.V0.State?.graph?.triples) {
let json_str = new TextDecoder().decode(Uint8Array.from(event.payload.V0.State.graph.triples));
event.payload.V0.State.graph.triples = JSON.parse(json_str);
} else if (event.payload.V0.Patch?.graph) {
let inserts_json_str = new TextDecoder().decode(Uint8Array.from(event.payload.V0.Patch.graph.inserts));
event.payload.V0.Patch.graph.inserts = JSON.parse(inserts_json_str);
let removes_json_str = new TextDecoder().decode(Uint8Array.from(event.payload.V0.Patch.graph.removes));
event.payload.V0.Patch.graph.removes = JSON.parse(removes_json_str);
}
callback(event.payload).then(()=> {})
})
await tauri.invoke("app_request_stream",{stream_id, request});
try {
await tauri.invoke("app_request_stream",{stream_id, request});
} catch (e) {
unlisten();
tauri.invoke("cancel_stream", {stream_id});
throw e;
}
return () => {
unlisten();
tauri.invoke("cancel_stream", {stream_id});

@ -0,0 +1,49 @@
<!--
// 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 {
get_blob,
} from "../store";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
export let commits;
</script>
<div class="flex-col">
<h2>ListView</h2>
<div class="flex">
HEADS: {#each commits.heads as head} {head} , {/each}
</div>
TRIPLES:
{#each commits.graph as triple}
<div class="flex"> {triple}</div>
{/each}
{#each commits.files as file}
<div class="flex">
{file.name}
{#await get_blob(file)}
<div class="row">
<Spinner />
</div>
{:then url}
{#if url}
<img src={url} title={file.nuri} alt={file.name} />
{/if}
{/await}
</div>
{/each}
</div>

@ -0,0 +1,61 @@
<!--
// 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 { onMount, tick, onDestroy } from "svelte";
import {
sparql_update,
} from "../store";
import {
in_memory_discrete, cur_tab
} from "../tab";
import{ RocketLaunch } from "svelte-heros-v2";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import CodeMirror from "svelte-codemirror-editor";
import {StreamLanguage} from "@codemirror/language"
import { sparql } from "@codemirror/legacy-modes/mode/sparql";
import {basicSetup} from "codemirror"
onMount(()=>{
if (!$in_memory_discrete){
$in_memory_discrete = "INSERT DATA { \n <did:ng:test> <test:predicate> \"An example value\".\r}";
// "SELECT * WHERE {\r\
// ?subject ?predicate ?object .\r\
// } LIMIT 10";
}
});
const run = async () => {
await sparql_update($in_memory_discrete);
}
</script>
<div class="flex-col">
<CodeMirror bind:value={$in_memory_discrete} lineWrapping extensions={[basicSetup,StreamLanguage.define(sparql)]} styles={{
"&": {
maxWidth: "100%",
},
}}/>
<button
on:click={run}
on:keypress={run}
class="select-none ml-2 mt-2 mb-10 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<RocketLaunch class="mr-2 focus:outline-none" />
Run Update
</button>
</div>

@ -0,0 +1,59 @@
/*
* Base64URL-ArrayBuffer
* https://github.com/herrjemand/Base64URL-ArrayBuffer
*
* Copyright (c) 2017 Yuriy Ackermann <ackermann.yuriy@gmail.com>
* Copyright (c) 2012 Niklas von Hertzen
* Licensed under the MIT license.
*
*/
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
// Use a lookup table to find the index.
var lookup = new Uint8Array(256);
for (var i = 0; i < chars.length; i++) {
lookup[chars.charCodeAt(i)] = i;
}
export const encode = function(arraybuffer) {
var bytes = new Uint8Array(arraybuffer),
i, len = bytes.length, base64 = "";
for (i = 0; i < len; i+=3) {
base64 += chars[bytes[i] >> 2];
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
base64 += chars[bytes[i + 2] & 63];
}
if ((len % 3) === 2) {
base64 = base64.substring(0, base64.length - 1);
} else if (len % 3 === 1) {
base64 = base64.substring(0, base64.length - 2);
}
return base64;
};
export const decode = function(base64) {
var bufferLength = base64.length * 0.75,
len = base64.length, i, p = 0,
encoded1, encoded2, encoded3, encoded4;
var arraybuffer = new ArrayBuffer(bufferLength),
bytes = new Uint8Array(arraybuffer);
for (i = 0; i < len; i+=4) {
encoded1 = lookup[base64.charCodeAt(i)];
encoded2 = lookup[base64.charCodeAt(i+1)];
encoded3 = lookup[base64.charCodeAt(i+2)];
encoded4 = lookup[base64.charCodeAt(i+3)];
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
}
return arraybuffer;
};

@ -7,16 +7,16 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
// "post/rich", "post/md", "post/text", "post/html", "post/asciidoc", "page", "code", "code/*", "app", "app/z", "class", "schema", "schema/owl|rdfs|shacl|shex", "service", "service/rust", "service/deno", "contract", "app/n:xxx.xx.xx:", "app/o:",
// "query/sparql", "query/graphql", "query/text", "query/web",
// "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",
// "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"
// "prod/task", "prod/project", "prod/issue", "prod/form", "prod/filling", "prod/cad", "prod/slides", "prod/question", "prod/answer", "prod/poll", "prod/vote"
// "file", "file/iana/*", "file/gimp", "file/inkscape", "file/kdenlive", "file/blender", "file/openscad", "file/lyx", "file/scribus", "file/libreoffice", "file/audacity"
// "post:rich", "post:md", "post:text", "post:html", "post:asciidoc", "page", "code", "code:*", "app", "app:z", "class", "schema", "schema:owl|rdfs|shacl|shex", "service", "service:rust", "service:deno", "contract", "app:n:xxx.xx.xx:", "app:o:",
// "query:sparql", "query:graphql", "query:text", "query:web",
// "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",
// "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"
// "file", "file:iana:*", "file:gimp", "file:inkscape", "file:kdenlive", "file:blender", "file:openscad", "file:lyx", "file:scribus", "file:libreoffice", "file:audacity", "file:godot"
// application/vnd.api+json
@ -27,12 +27,12 @@
// animation: snap, lottie, smil editor: https://github.com/HaikuTeam/animator/
export const has_toc = (class_name) => {
return class_name === "post/rich" || class_name === "post/md" || class_name === "post/html" || class_name === "post/asciidoc" || class_name === "app/z" || class_name === "class"
|| class_name.startsWith("schema") || class_name === "doc/pdf" || class_name === "doc/odf" || class_name === "doc/latex" || class_name === "doc/ps" || class_name === "prod/project" || class_name === "prod/slides"
return class_name === "post:rich" || class_name === "post:md" || class_name === "post:html" || class_name === "post:asciidoc" || class_name === "app:z" || class_name === "class"
|| class_name.startsWith("schema") || class_name === "doc:pdf" || class_name === "doc:odf" || class_name === "doc:latex" || class_name === "doc:ps" || class_name === "prod:project" || class_name === "prod:slides"
};
export const official_classes = {
"post/rich": {
"post:rich": {
"ng:crdt": "YXml",
"ng:n": "Post - Rich Text", // editor: y-ProseMirror, viewer: https://www.npmjs.com/package/prosemirror-to-html-js or https://prosemirror.net/docs/ref/version/0.4.0.html#toDOM https://prosemirror.net/docs/ref/version/0.4.0.html#toHTML
"ng:a": "A Post with Rich Text, including images, links, formatted text, and embeds of other content",
@ -43,7 +43,7 @@ export const official_classes = {
},
"ng:compat": ["as:Article"],
},
"post/md": {
"post:md": {
"ng:crdt": "YXml",
"ng:n": "Post - MarkDown", // editor y-MilkDown, viewer: https://github.com/wooorm/markdown-rs
"ng:a": "A Post with MarkDown, including images, links, formatted text, and embeds of other content",
@ -52,9 +52,9 @@ export const official_classes = {
"ng:x": {
"as":true,
},
"ng:compat": ["file/iana/text/markdown", "code/markdown","as:Article"],
"ng:compat": ["file:iana:text:markdown", "code:markdown","as:Article"],
},
"post/text": {
"post:text": {
"ng:crdt": "YText",
"ng:n": "Post - Plain Text",
"ng:a": "A Post with Plain Text",
@ -63,9 +63,9 @@ export const official_classes = {
"ng:x": {
"as":true,
},
"ng:compat": ["file/iana/text/plain", "code/plaintext","as:Article"],
"ng:compat": ["file:iana:text:plain", "code:plaintext","as:Article"],
},
"post/html": {
"post:html": {
"ng:crdt": "YXml",
"ng:n": "Post - TinyMCE",
"ng:x": {
@ -74,7 +74,7 @@ export const official_classes = {
"ng:a": "A Post based on TinyMCE, including images, links, formatted text, and embeds of other content",
"ng:compat": ["as:Article"],
},
"post/asciidoc": { // display with https://github.com/asciidoctor/asciidoctor.js/
"post:asciidoc": { // display with https://github.com/asciidoctor/asciidoctor.js/
"ng:crdt": "YText",
"ng:n": "Post - AsciiDoc",
"ng:a": "A Post based on AsciiDoc format",
@ -95,21 +95,21 @@ export const official_classes = {
"ng:a": "A Source Code file. many languages supported",
"ng:o": "n:g:z:pre",
"ng:w": "n:g:z:code_editor",
"ng:compat": ["code/*","file/iana/text/javascript","file/iana/text/css","file/iana/text/html","file/iana/text/markdown", "file/iana/application/xml",
"file/iana/application/yaml", "file/iana/text/xml", "file/iana/application/xhtml+xml"],
"ng:compat": ["code:*","file:iana:text:javascript","file:iana:text:css","file:iana:text:html","file:iana:text:markdown", "file:iana:application:xml",
"file:iana:application:yaml", "file:iana:text:xml", "file:iana:application:xhtml+xml"],
},
"app": {
"ng:n": "Official App",
"ng:a": "App provided by NextGraph platform",
},
"app/z": {
"app:z": {
"ng:crdt": "Elmer",
"ng:n": "Application", // Editor: Monaco
"ng:a": "Create an Application based on NextGraph Framework",
"ng:o": "n:g:z:app_store",
"ng:w": "n:g:z:app_editor",
"ng:include": ["schema/*","service/*","code","file"],
"ng:compat": ["code/svelte"],
"ng:include": ["schema:*","service:*","code","file"],
"ng:compat": ["code:svelte"],
},
"class": {
"ng:crdt": "Graph",
@ -120,7 +120,7 @@ export const official_classes = {
},
"ng:compat": ["rdfs:Class"],
},
"schema/rdfs": {
"schema:rdfs": {
"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",
@ -129,10 +129,10 @@ export const official_classes = {
"ng:x": {
"rdfs":true,
},
"ng:include": ["data/graph"],
"ng:include": ["data:graph"],
"ng:compat": ["rdfs:*","class"],
},
"schema/owl": { // display with https://github.com/VisualDataWeb/WebVOWL
"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",
@ -141,10 +141,10 @@ export const official_classes = {
"ng:x": {
"owl":true,
},
"ng:include": ["data/graph"],
"ng:include": ["data:graph"],
"ng:compat": ["owl:Ontology"],
},
"schema/shacl": {
"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",
@ -153,10 +153,10 @@ export const official_classes = {
"ng:x": {
"sh":true,
},
"ng:include": ["data/graph"],
"ng:compat": ["sh:Shape", "file/iana/text/shaclc" ],
"ng:include": ["data:graph"],
"ng:compat": ["sh:Shape", "file:iana:text:shaclc" ],
},
"schema/shex": {
"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",
@ -165,73 +165,73 @@ export const official_classes = {
"ng:x": {
"shex":true,
},
"ng:include": ["data/graph"],
"ng:compat": ["shex:*", "file/iana/text/shex", "code/shexc" ],
"ng:include": ["data:graph"],
"ng:compat": ["shex:*", "file:iana:text:shex", "code:shexc" ],
},
"service": {
"ng:n": "Internal Service",
"ng:a": "Service provided by NextGraph framework",
"ng:o": "n:g:z:service_invoke", // default viewer
},
"service/rust": {
"service:rust": {
"ng:crdt": "YText",
"ng:n": "Service - Rust", // edited with CodeMirror, displayed with highlight.js
"ng:a": "Service written in Rust and compiled to WASM",
"ng:o": "external_service_invoke", // default viewer
"ng:w": "n:g:z:service_editor", // default editor
"ng:compat": ["code/rust", "file/iana/application/wasm"],
"ng:compat": ["code:rust", "file:iana:application:wasm"],
},
"service/deno": {
"service:deno": {
"ng:crdt": "YText",
"ng:n": "Service - Deno/JS", // edited with CodeMirror, displayed with highlight.js
"ng:a": "Service written in JS/TS for Deno or NodeJS",
"ng:o": "external_service_invoke", // default viewer
"ng:w": "n:g:z:service_editor", // default editor
"ng:compat": ["code/javascript", "code/typescript", "file/iana/text/javascript", "file/iana/application/node"],
"ng:compat": ["code:javascript", "code:typescript", "file:iana:text:javascript", "file:iana:application:node"],
},
"contract": {
"ng:crdt": "YText",
"ng:n": "Contract", // edited with CodeMirror, displayed with highlight.js
"ng:a": "Smart Contract with Rust or JS code",
"ng:compat": ["code/rust", "file/iana/application/wasm", "code/javascript", "code/typescript", "file/iana/text/javascript", "file/iana/application/node"],
"ng:compat": ["code:rust", "file:iana:application:wasm", "code:javascript", "code:typescript", "file:iana:text:javascript", "file:iana:application:node"],
},
"query/sparql": {
"query:sparql": {
"ng:crdt": "YText",// uses ng:default_graph and ng:named_graph predicates
"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:compat": ["code/sparql", "file/iana/application/sparql-query"],
"ng:compat": ["code:sparql", "file:iana:application:sparql-query","file:iana:application:x-sparql-query"],
},
"query/sparql_update": {
"query:sparql_update": {
"ng:crdt": "YText",// uses ng:default_graph and ng:named_graph predicates
"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:compat": ["code/sparql", "file/iana/application/sparql-update"],
"ng:compat": ["code:sparql", "file:iana:application:sparql-update"],
},
"query/graphql": {
"query:graphql": {
"ng:crdt": "YText", // uses ng:default_graph predicate
"ng:n": "GraphQL Query", // edited with https://github.com/graphql/graphiql or https://github.com/graphql-editor/graphql-editor, displayed with highlight.js
"ng:a": "Saved GraphQL Query that can be invoked",
"ng:o": "n:g:z:graphql:invoke",
"ng:w": "n:g:z:graphql_query",
"ng:compat": ["code/graphql", "file/iana/application/graphql+json"],
"ng:compat": ["code:graphql", "file:iana:application:graphql+json"],
},
"query/text": {
"query:text": {
"ng:crdt": "Graph",
"ng:n": "Text Search",
"ng:a": "Saved Text Search and its results",
"ng:compat": [],
},
"query/web": {
"query:web": {
"ng:crdt": "Graph",
"ng:n": "Web Search",
"ng:a": "Saved Web Search and its results",
"ng:compat": [],
},
"data/graph": {
"data:graph": {
"ng:crdt": "Graph", // https://github.com/highlightjs/highlightjs-turtle/tree/master
"ng:n": "Graph",
"ng:a": "Define the Graph of your data with Semantic Web / Linked Data",
@ -241,47 +241,47 @@ export const official_classes = {
"rdf":true,
"xsd":true,
},
"ng:compat": [ "rdf:*", "xsd:*", "file/iana/text/n3", "file/iana/text/rdf+n3", "file/iana/text/turtle", "file/iana/application/n-quads", "file/iana/application/trig", "file/iana/application/n-triples",
"file/iana/application/rdf+xml", "file/iana/application/ld+json"],
"ng:compat": [ "rdf:*", "xsd:*", "file:iana:text:n3", "file:iana:text:rdf+n3", "file:iana:text:turtle", "file:iana:application:n-quads", "file:iana:application:trig", "file:iana:application:n-triples",
"file:iana:application:rdf+xml", "file:iana:application:ld+json"],
},
"data/json": {
"data:json": {
"ng:crdt": "Automerge",
"ng:n": "JSON",
"ng:a": "JSON Data CRDT",
"ng:o": "n:g:z:json_viewer", // default viewer
"ng:w": "n:g:z:json_editor", // default editor
"ng:compat": ["file/iana/application/json", "code:json"],
"ng:compat": ["file:iana:application:json", "code:json"],
},
"data/array": {
"data:array": {
"ng:crdt": "YArray",
"ng:n": "JSON Array",
"ng:a": "JSON Array CRDT",
"ng:o": "n:g:z:json_viewer", // default viewer
"ng:w": "n:g:z:json_editor", // default editor
"ng:compat": ["file/iana/application/json", "code:json"],
"ng:compat": ["file:iana:application:json", "code:json"],
},
"data/map": {
"data:map": {
"ng:crdt": "YMap",
"ng:n": "JSON Map",
"ng:a": "JSON Map CRDT",
"ng:o": "n:g:z:json_viewer", // default viewer
"ng:w": "n:g:z:json_editor", // default editor
"ng:compat": ["file/iana/application/json", "code:json"],
"ng:compat": ["file:iana:application:json", "code:json"],
},
"data/xml": {
"data:xml": {
"ng:crdt": "YXml",
"ng:n": "XML",
"ng:a": "XML Data CRDT",
"ng:compat": ["file/iana/text/xml","file/iana/application/xml", "code:xml"],
"ng:compat": ["file:iana:text:xml","file:iana:application:xml", "code:xml"],
},
"data/table": {
"data:table": {
"ng:crdt": "Automerge", // see https://github.com/frappe/datatable
"ng:n": "Table", // see https://specs.frictionlessdata.io/table-schema displayed with pivot table see https://activetable.io/docs/data https://www.npmjs.com/package/pivottable https://socket.dev/npm/package/svelte-pivottable/alerts/0.2.0?tab=dependencies
"ng:a": "Data in a Table (columns and rows)",
"ng:o": "n:g:z:pivot",
"ng:compat": ["file/iana/application/sparql-results+json","file/iana/application/sparql-results+xml","file/iana/text/csv"],
"ng:compat": ["file:iana:application:sparql-results+json","file:iana:application:sparql-results+xml","file:iana:text:csv"],
},
"data/collection": {
"data:collection": {
"ng:crdt": "Graph",
"ng:n": "Collection",
"ng:a": "An ordered list of items",
@ -292,10 +292,10 @@ export const official_classes = {
},
"ng:compat": ["as:Collection","rdf:List","rdf:Seq"],
},
"data/container": {
"data:container": {
"ng:crdt": "Graph",
"ng:n": "Container",
"ng:a": "An unordered list of items",
"ng:a": "An unordered set of items",
"ng:o": "n:g:z:list",
"ng:x": {
"rdf": true,
@ -304,14 +304,14 @@ export const official_classes = {
},
"ng:compat": ["rdfs:member","ldp:contains","rdf:Bag","rdf:Alt"],
},
"data/plato": {
"data:plato": {
"ng:crdt": "Graph",
"ng:n": "Plato",
"ng:a": "A tree of files and folders",
"ng:o": "n:g:z:tree",
"ng:compat": ["ng:plato","ng:has_plato"],
},
"data/board": {
"data:board": {
"ng:crdt": "Graph",
"ng:n": "Board",
"ng:a": "Whiteboard, infinite canvas to arrange your content in 2D",
@ -319,15 +319,15 @@ export const official_classes = {
"ng:include": [],
"ng:compat": [], //https://jsoncanvas.org/ https://www.canvasprotocol.org/ https://github.com/orgs/ocwg/discussions/25 https://infinitecanvas.tools/gallery/
},
"data/grid": {
"data:grid": {
"ng:crdt": "Graph",
"ng:n": "Grid",
"ng:a": "Grid representation of a collection or container",
"ng:o": "n:g:z:grid",
"ng:include": ["data/container","data/collection","data/table","media/album"],
"ng:include": ["data:container","data:collection","data:table","media:album"],
"ng:compat": [],
},
"data/geomap": { // https://github.com/leaflet/leaflet
"data:geomap": { // https://github.com/leaflet/leaflet
"ng:crdt": "Graph",
"ng:n": "Geo Map",
"ng:a": "Geographical Map",
@ -336,36 +336,36 @@ export const official_classes = {
"gn": true,
"as": true,
},
"ng:compat": ["as:Place","wgs:*","gn:*", "file/iana/application/geo+json", "file/iana/application/vnd.mapbox-vector-tile"], // see also https://github.com/topojson/topojson
"ng:compat": ["as:Place","wgs:*","gn:*", "file:iana:application:geo+json", "file:iana:application:vnd.mapbox-vector-tile"], // see also https://github.com/topojson/topojson
},
"e/email": {
"e:email": {
"ng:crdt": "Graph",
"ng:n": "Email",
"ng:a": "Email content and headers",
"ng:x": {
"email": "http://www.invincea.com/ontologies/icas/1.0/email#" //https://raw.githubusercontent.com/twosixlabs/icas-ontology/master/ontology/email.ttl
},
"ng:compat": ["file/iana/message/rfc822","file/iana/multipart/related"],
"ng:compat": ["file:iana:message:rfc822","file:iana:multipart:related"],
},
"e/web": {
"e:web": {
"ng:crdt": "Graph",
//https://www.npmjs.com/package/warcio https://github.com/N0taN3rd/node-warc
"ng:n": "Web Archive",
"ng:a": "Archive the content of a web page",
"ng:compat": ["file/iana/application/warc","file/iana/multipart/related"],
"ng:compat": ["file:iana:application:warc","file:iana:multipart:related"],
},
"e/rdf": {
"e:rdf": {
"ng:crdt": "Graph",
"ng:n": "RDF Archive",
"ng:a": "Archive the triples of an RDF resource dereferenced with HTTP",
"ng:include": ["data/graph"],
"ng:include": ["data:graph"],
},
"mc/text": {
"mc:text": {
"ng:crdt": "Graph",
"ng:n": "Text Selection",
"ng:a": "Text Selection copied into Magic Carpet",
},
"mc/link": {
"mc:link": {
"ng:crdt": "Graph",
"ng:n": "Link",
"ng:a": "Link to a document kept in Magic Carpet",
@ -382,68 +382,68 @@ export const official_classes = {
"ng:a": "Pad representation of a document",
"ng:o": "n:g:z:pad",
},
"doc/compose" : {
"doc:compose" : {
"ng:crdt": "YArray",
"ng:n": "Composition",
"ng:a": "Compose several blocks into a single document",
"ng:o": "n:g:z:compose",
},
"doc/diagram/mermaid" : {
"doc:diagram:mermaid" : {
"ng:crdt": "YText",
"ng:n": "Diagram - Mermaid",
"ng:a": "Describe Diagrams with Mermaid",
"ng:compat": ["file/iana/application/vnd.mermaid"]
"ng:compat": ["file:iana:application:vnd.mermaid"]
},
"doc/diagram/drawio" : {
"doc: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"]
"ng:compat": ["file:iana:application:vnd.jgraph.mxfile","file:iana:application:x-drawio"]
},
"doc/diagram/graphviz" : {
"doc:diagram:graphviz" : {
"ng:crdt": "YText",
"ng:n": "Diagram - Graphviz",
"ng:a": "Describe Diagrams with Graphviz",
"ng:compat": ["file/iana/text/vnd.graphviz"]
"ng:compat": ["file:iana:text:vnd.graphviz"]
},
"doc/diagram/excalidraw" : {
"doc:diagram:excalidraw" : {
"ng:crdt": "Automerge",
"ng:n": "Diagram - Excalidraw",
"ng:a": "Collaborate on Diagrams with Excalidraw",
"ng:compat": ["file/iana/application/vnd.excalidraw+json"]
"ng:compat": ["file:iana:application:vnd.excalidraw+json"]
},
"doc/diagram/gantt" : { //https://github.com/frappe/gantt
"doc: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
"doc: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
"doc: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
"doc: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
"doc: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
"doc:diagram:jsmind" : { //https://github.com/hizzgdev/jsmind
"ng:crdt": "Automerge",
"ng:n": "Diagram - jsmind",
"ng:a": "mindmaps with jsmind",
@ -457,137 +457,137 @@ export const official_classes = {
// https://github.com/Rich-Harris/pancake
// https://github.com/williamngan/pts
// https://visjs.org/
"doc/viz/cytoscape" : {
"doc: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" : {
"doc: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" : {
"doc: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
"doc: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" : {
"doc: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" : {
"doc: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" : {
"doc: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" : {
"doc: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" : {
"doc: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" : {
"doc: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" : {
"doc:chart:chartjs" : {
"ng:crdt": "Automerge",
"ng:n": "Charts - Chart.js",
"ng:a": "Simple yet flexible charting for designers & developers with Chart.js",
"ng:compat": [] // https://github.com/chartjs/Chart.js
},
// see if to provide plain D3, and also all the https://github.com/antvis libraries: G2, G6, L7, S2, X6. Have a look at AVA
"doc/pdf": {
"doc:pdf": {
"ng:crdt": "Graph",
"ng:n": "PDF",
"ng:a": "upload and display a PDF file",
"ng:compat": ["file/iana/application/pdf"] // https://github.com/mozilla/pdf.js https://viewerjs.org/
"ng:compat": ["file:iana:application:pdf"] // https://github.com/mozilla/pdf.js https://viewerjs.org/
},
"doc/odf": { //!!! becareful: AGPL
"doc:odf": { //!!! becareful: AGPL
"ng:crdt": "Graph",
"ng:n": "OpenDocumentFormat (ODF)",
"ng:a": "upload and display an ODF file",
"ng:compat": ["file/iana/application/vnd.oasis.opendocument*"] // https://webodf.org/ https://github.com/webodf/WebODF https://viewerjs.org/
"ng:compat": ["file:iana:application:vnd.oasis.opendocument*"] // https://webodf.org/ https://github.com/webodf/WebODF https://viewerjs.org/
},
// see also https://github.com/Mathpix/mathpix-markdown-it
"doc/latex": {
"doc:latex": {
"ng:crdt": "Graph",
"ng:n": "Latex",
"ng:a": "upload and display a Latex or Tex file",
"ng:compat": ["file/iana/application/x-tex","file/iana/text/x-tex"] // https://github.com/michael-brade/LaTeX.js https://github.com/mathjax/MathJax
"ng:compat": ["file:iana:application:x-tex","file:iana:text:x-tex"] // https://github.com/michael-brade/LaTeX.js https://github.com/mathjax/MathJax
},
"doc/ps": { //!!! becareful: AGPL https://github.com/ochachacha/ps-wasm
"doc:ps": { //!!! becareful: AGPL https://github.com/ochachacha/ps-wasm
"ng:crdt": "Graph",
"ng:n": "Postscript",
"ng:a": "upload and display a PostScript file",
"ng:compat": ["file/iana/application/postscript"] // https://www.npmjs.com/package/ghostscript4js
"ng:compat": ["file:iana:application:postscript"] // https://www.npmjs.com/package/ghostscript4js
},
"doc/music/abc": { //https://github.com/paulrosen/abcjs
"doc:music:abc": { //https://github.com/paulrosen/abcjs
"ng:crdt": "YText",
"ng:n": "Music ABC",
"ng:a": "sheet music notation",
"ng:compat": []
},
"doc/music/guitar": { //https://github.com/birdca/fretboard
"doc:music:guitar": { //https://github.com/birdca/fretboard
"ng:crdt": "YText",
"ng:n": "Music - Guitar",
"ng:a": "charts for guitar chords and scales",
"ng:compat": []
},
"doc/maths": { //https://github.com/KaTeX/KaTeX
"doc:maths": { //https://github.com/KaTeX/KaTeX
"ng:crdt": "YText",
"ng:n": "Maths",
"ng:a": "TeX math rendering",
"ng:compat": ["file/iana/application/x-tex","file/iana/text/x-tex"]
"ng:compat": ["file:iana:application:x-tex","file:iana:text:x-tex"]
},
"doc/chemistry": { //GPL!! https://github.com/aeris-data/ChemDoodle/tree/master/ChemDoodleWeb-8.0.0 or https://github.com/aseevia/smiles-3d-vue
"doc:chemistry": { //GPL!! https://github.com/aeris-data/ChemDoodle/tree/master/ChemDoodleWeb-8.0.0 or https://github.com/aseevia/smiles-3d-vue
"ng:crdt": "YText",
"ng:n": "Chemical",
"ng:a": "simplified molecular-input line-entry system (SMILES)",
"ng:compat": ["file/iana/chemical/x-daylight-smiles"] // https://en.wikipedia.org/wiki/SYBYL_line_notation and http://fileformats.archiveteam.org/wiki/Chemical_data
"ng:compat": ["file:iana:chemical:x-daylight-smiles"] // https://en.wikipedia.org/wiki/SYBYL_line_notation and http://fileformats.archiveteam.org/wiki/Chemical_data
},
"doc/ancientscript": { //https://dn-works.com/ufas/
"doc:ancientscript": { //https://dn-works.com/ufas/
"ng:crdt": "YText", // use Unicode and special fonts
"ng:n": "Ancient Script",
"ng:a": "Ancient Script",
"ng:compat": []
},
"doc/braille": { //https://en.wikipedia.org/wiki/Braille_Patterns
"doc:braille": { //https://en.wikipedia.org/wiki/Braille_Patterns
"ng:crdt": "YText", // use Unicode and special fonts
"ng:n": "Braille Patterns",
"ng:a": "Braille Patterns",
"ng:compat": []
},
"media/image": {
"media:image": {
"ng:crdt": "Graph",
"ng:n": "Image",
"ng:a": "upload and display an image",
@ -595,16 +595,16 @@ export const official_classes = {
"ng:x": {
"as":true,
},
"ng:compat": ["file/iana/image*","as:Image"]
"ng:compat": ["file:iana:image*","as:Image"]
},
"media/reel": {
"media:reel": {
"ng:crdt": "Graph",
"ng:n": "Reel",
"ng:a": "upload and display a Reel (video from mobile)",
"ng:o": "n:g:z:media",
"ng:compat": ["file/iana/video*"]
"ng:compat": ["file:iana:video*"]
},
"media/video": {
"media:video": {
"ng:crdt": "Graph",
"ng:n": "Video",
"ng:a": "upload and display a Video (and film)",
@ -612,17 +612,17 @@ export const official_classes = {
"ng:x": {
"as":true,
},
"ng:compat": ["file/iana/video*","as:Video"]
"ng:compat": ["file:iana:video*","as:Video"]
},
"media/album": {
"media:album": {
"ng:crdt": "Graph",
"ng:n": "Album",
"ng:a": "Assemble several images and/or videos into an ordered Album",
"ng:o": "n:g:z:gallery",
"ng:include": ["data/collection"],
"ng:include": ["data:collection"],
"ng:compat": []
},
"media/audio": {
"media:audio": {
"ng:crdt": "Graph",
"ng:n": "Audio",
"ng:a": "upload and play an Audio file, Audio note or Voice message",
@ -630,9 +630,9 @@ export const official_classes = {
"ng:x": {
"as":true,
},
"ng:compat": ["file/iana/audio*","as:Audio"]
"ng:compat": ["file:iana:audio*","as:Audio"]
},
"media/song": {
"media:song": {
"ng:crdt": "Graph",
"ng:n": "Song",
"ng:a": "A song from an artist,album and/or lyrics",
@ -644,20 +644,20 @@ export const official_classes = {
// see also https://polifonia-project.eu/wp-content/uploads/2022/01/Polifonia_D2.1_V1.0.pdf
// Music ontology http://musicontology.com/docs/faq.html with data based on existing databases https://musicbrainz.org/doc/MusicBrainz_Database/Schema https://github.com/megaconfidence/open-song-database https://www.discogs.com/developers
},
"media/subtitle": { //https://captioneasy.com/subtitle-file-formats/
"media:subtitle": { //https://captioneasy.com/subtitle-file-formats/
"ng:crdt": "YText",
"ng:n": "Subtitles",
"ng:a": "Subtitles",
"ng:compat": [] // TBD
},
"media/overlay": {
"media:overlay": {
"ng:crdt": "Graph",
"ng:n": "Overlay",
"ng:a": "Composition of an image, reel, text, icon, link, mention or other content into a layered content",
"ng:o": "n:g:z:media",
"ng:compat": []
},
"social/activity": {
"social:activity": {
"ng:crdt": "Graph",
"ng:n": "Activity",
"ng:a": "Activity sent in a Stream",
@ -666,19 +666,19 @@ export const official_classes = {
},
"ng:compat": ["as:Activity"]
},
"social/channel": {
"social:channel": {
"ng:crdt": "Graph",
"ng:n": "Channel",
"ng:a": "Broadcast channel with subscribers",
"ng:compat": []
},
"social/stream": {
"social:stream": {
"ng:crdt": "Graph",
"ng:n": "Stream",
"ng:a": "A document or store's stream branch",
"ng:compat": []
},
"social/contact": {
"social:contact": {
"ng:crdt": "Graph",
"ng:n": "Contact",
"ng:a": "Contact: an Individual, Organization or Group",
@ -686,20 +686,20 @@ export const official_classes = {
"vcard":true,
"foaf": true,
},
"ng:include": ["data/graph"],
"ng:compat": ["foaf:Person","foaf:Agent","vcard:Individual", "vcard:Organization", "vcard:Group", "file/iana/text/vcard", "file/iana/application/vcard+json", "file/iana/application/vcard+xml" ],
"ng:include": ["data:graph"],
"ng:compat": ["foaf:Person","foaf:Agent","vcard:Individual", "vcard:Organization", "vcard:Group", "file:iana:text:vcard", "file:iana:application:vcard+json", "file:iana:application:vcard+xml" ],
},
"social/event": {
"social:event": {
"ng:crdt": "Graph",
"ng:n": "Event",
"ng:a": "An event occuring in specific location and time",
"ng:x": {
"as":true,
},
"ng:include": ["post/*"],
"ng:include": ["post:*"],
"ng:compat": ["as:Event"]
},
"social/calendar": {
"social:calendar": {
"ng:crdt": "Graph",
"ng:n": "Calendar",
"ng:a": "A calendar where events are gathered",
@ -707,10 +707,10 @@ export const official_classes = {
"as":true,
"time": true,
},
"ng:include": ["data/collection"],
"ng:compat": ["time:TemporalEntity", "file/iana/text/calendar", "file/iana/application/calendar+xml", "file/iana/application/calendar+json"] //https://www.rfc-editor.org/rfc/rfc5545
},
"social/scheduler": {
"ng:include": ["data:collection"],
"ng:compat": ["time:TemporalEntity", "file:iana:text:calendar", "file:iana:application:calendar+xml", "file:iana:application:calendar+json"] //https://www.rfc-editor.org/rfc/rfc5545
},
"social:scheduler": {
"ng:crdt": "Graph",
"ng:n": "Scheduler",
"ng:a": "Helps finding a common time slot for several participants to a future event",
@ -719,7 +719,7 @@ export const official_classes = {
},
"ng:compat": ["as:Invite","as:Reject","as:Accept","as:TentativeAccept","as:TentativeReject"]
},
"social/reaction": {
"social:reaction": {
"ng:crdt": "Graph",
"ng:n": "Reaction",
"ng:a": "A reaction by user to some content",
@ -728,7 +728,12 @@ export const official_classes = {
},
"ng:compat": ["as:Like", "as:Dislike", "as:Listen", "as:Read", "as:View"]
},
"prod/task": {
"social:chatroom": {
"ng:crdt": "Graph",
"ng:n": "ChatRoom",
"ng:a": "A room for group chat",
},
"prod:task": {
"ng:crdt": "Graph",
"ng:n": "Task",
"ng:a": "A task to be done",
@ -736,12 +741,12 @@ export const official_classes = {
"as":true,
"pair": "http://virtual-assembly.org/ontologies/pair#",
},
"ng:include": ["post/*"],
"ng:include": ["post:*"],
"ng:compat": ["pair:Task"] //see VTODO in iCalendar https://www.cs.utexas.edu/~mfkb/RKF/tree/components/specs/ontologies/Calendar-onto.html
// see todo and todoList of Mobilizon https://framagit.org/framasoft/mobilizon/-/blob/main/lib/federation/activity_stream/converter/todo.ex
// https://framagit.org/framasoft/mobilizon/-/blob/main/lib/federation/activity_stream/converter/todo_list.ex
},
"prod/project": {
"prod:project": {
"ng:crdt": "Graph",
"ng:n": "Project",
"ng:a": "A project management / KanBan",
@ -749,7 +754,7 @@ export const official_classes = {
"as":true,
"pair": "http://virtual-assembly.org/ontologies/pair#",
},
"ng:include": ["post/*"],
"ng:include": ["post:*"],
"ng:compat": ["pair:Project"]
},
// see SRO https://www.researchgate.net/publication/350158531_From_a_Scrum_Reference_Ontology_to_the_Integration_of_Applications_for_Data-Driven_Software_Development
@ -769,7 +774,7 @@ export const official_classes = {
/// svelte: https://github.com/V-Py/svelte-kanban
// https://github.com/supabase-community/svelte-kanban
// https://github.com/therosbif/kanban
"prod/issue": {
"prod:issue": {
"ng:crdt": "Graph",
"ng:n": "Issue",
"ng:a": "An issue to be solved",
@ -777,19 +782,19 @@ export const official_classes = {
"as":true,
"pair": "http://virtual-assembly.org/ontologies/pair#",
},
"ng:include": ["prod/task"],
"ng:include": ["prod:task"],
"ng:compat": ["pair:Challenge"]
},
//https://github.com/go-gitea/gitea/issues/20232
// datamodel of gitea issues: https://github.com/go-gitea/gitea/blob/165346c15c6d021028a65121e692a17ffc927e2c/models/issue.go#L35-L79
"prod/form": {
"prod:form": {
"ng:crdt": "Graph",
"ng:n": "Form",
"ng:a": "A form to be filled-in",
"ng:x": {
"form" : "http://rdf.danielbeeke.nl/form/form-dev.ttl#",
},
"ng:compat": ["form:*","file/iana/application/schema+json"]
"ng:compat": ["form:*","file:iana:application:schema+json"]
},
// https://jsonforms.io/docs/
// https://github.com/jsonform/jsonform
@ -804,97 +809,97 @@ export const official_classes = {
// https://www.mediawiki.org/wiki/Extension:Page_Forms
// https://rdf-form.danielbeeke.nl/
// consider using Shapes
"prod/filling": {
"prod:filling": {
"ng:crdt": "Graph",
"ng:n": "Form filling",
"ng:a": "A form that has been filled-in",
"ng:compat": []
},
"prod/cad": { // https://mattferraro.dev/posts/cadmium
"prod:cad": { // https://mattferraro.dev/posts/cadmium
"ng:crdt": "Automerge",
"ng:n": "CAD",
"ng:a": "CADmium",
"ng:compat": []
"ng:compat": []
},
"prod/slides": { //https://github.com/hakimel/reveal.js
"prod:slides": { //https://github.com/hakimel/reveal.js
//https://pandoc.org/MANUAL.html#slide-shows
"ng:crdt": "Graph",
"ng:n": "Slides",
"ng:a": "Slides and presentations",
"ng:include": ["post/*"],
"ng:include": ["post:*"],
"ng:compat": []
},
"prod/question" : {
"prod:question" : {
"ng:crdt": "Graph",
"ng:n": "Question",
"ng:a": "A question that needs answers",
"ng:x": {
"as":true,
},
"ng:include": ["post/*"],
"ng:compat": ["as:Question"]
"ng:include": ["post:*"],
"ng:compat": ["as:Question"]
},
"prod/answer" :{
"prod:answer" :{
"ng:crdt": "Graph",
"ng:n": "Answer",
"ng:a": "An answer to a question",
"ng:x": {
"as":true,
},
"ng:include": ["post/*"],
"ng:compat": ["as:Note"]
"ng:include": ["post:*"],
"ng:compat": ["as:Note"]
},
"prod/poll" : {
"prod:poll" : {
"ng:crdt": "Graph",
"ng:n": "Poll",
"ng:a": "A poll where people will vote",
"ng:x": {
"as":true,
},
"ng:include": ["post/*"],
"ng:compat": ["as:Question"]
"ng:include": ["post:*"],
"ng:compat": ["as:Question"]
},
"prod/vote" : {
"prod:vote" : {
"ng:crdt": "Graph",
"ng:n": "Vote",
"ng:a": "A vote cast for a Poll",
"ng:x": {
"as":true,
},
"ng:compat": ["as:Note"]
"ng:compat": ["as:Note"]
},
"file" : {
"ng:crdt": "Graph",
"ng:n": "File",
"ng:a": "Binary file",
"ng:o": "n:g:z:file_viewer",
"ng:compat": []
"ng:compat": []
},
"file/ng/wallet" : {
"file:ng:wallet" : {
"ng:n": "NextGraph Wallet File",
"ng:a": "NextGraph Wallet File (.ngw)",
"ng:compat": []
"ng:compat": []
},
"file/ng/doc" : {
"file:ng:doc" : {
"ng:n": "NextGraph Document File",
"ng:a": "NextGraph Document File (.ngd)",
"ng:compat": []
"ng:compat": []
},
"file/ng/html" : {
"file:ng:html" : {
"ng:n": "NextGraph Document Html",
"ng:a": "NextGraph Document Html standalone file",
"ng:compat": []
"ng:compat": []
},
"file/text" : {
"file:text" : {
"ng:crdt": "Graph",
"ng:n": "File",
"ng:a": "Text file",
"ng:o": "n:g:z:file_viewer",
"ng:compat": ["file/iana/text/*", "file/iana/image/svg+xml", "file/iana/application/n-quads", "file/iana/application/trig", "file/iana/application/n-triples", "file/iana/application/rdf+xml", "file/iana/application/ld+json",
"file/iana/application/xml", "file/iana/application/yaml", "file/iana/application/xhtml+xml", "file/iana/application/node","file/iana/application/sparql-results+json","file/iana/application/sparql-results+xml",
"file/iana/message/rfc822","file/iana/multipart/related", "file/iana/text/vnd.graphviz", "file/iana/application/vnd.excalidraw+json", "file/iana/application/x-tex","file/iana/text/x-tex",
"file/iana/application/vcard+json", "file/iana/application/vcard+xml", "file/iana/text/calendar", "file/iana/application/calendar+xml", "file/iana/application/calendar+json",
"file/iana/application/schema+json", "file/iana/application/geo+json", "file/iana/application/json" ]
"ng:compat": ["file:iana:text:*", "file:iana:image:svg+xml", "file:iana:application:n-quads", "file:iana:application:trig", "file:iana:application:n-triples", "file:iana:application:rdf+xml", "file:iana:application:ld+json",
"file:iana:application:xml", "file:iana:application:yaml", "file:iana:application:xhtml+xml", "file:iana:application:node","file:iana:application:sparql-results+json","file:iana:application:sparql-results+xml",
"file:iana:message:rfc822","file:iana:multipart:related", "file:iana:text:vnd.graphviz", "file:iana:application:vnd.excalidraw+json", "file:iana:application:x-tex","file:iana:text:x-tex",
"file:iana:application:vcard+json", "file:iana:application:vcard+xml", "file:iana:text:calendar", "file:iana:application:calendar+xml", "file:iana:application:calendar+json",
"file:iana:application:schema+json", "file:iana:application:geo+json", "file:iana:application:json" ]
},
};

@ -65,7 +65,8 @@
Bolt,
Heart,
Cog,
Square3Stack3d
Square3Stack3d,
ChatBubbleLeftRight,
} from "svelte-heros-v2";
export let config = {};
@ -74,64 +75,65 @@
const exact_mapping = {
page: Window,
"app": Cog,
"app/z": SquaresPlus,
"app:z": SquaresPlus,
class: ViewfinderCircle,
contract: Briefcase,
"query/text": MagnifyingGlass,
"query/web": MagnifyingGlass,
"data/graph": Sun,
"data/table": TableCells,
"data/collection": ListBullet,
"data/container": Square3Stack3d,
"data/board": RectangleGroup,
"data/grid": Squares2x2,
"data/geomap": MapPin,
"e/email": Envelope,
"mc/text": Bars3BottomLeft,
"mc/link": Link,
"query:text": MagnifyingGlass,
"query:web": MagnifyingGlass,
"data:graph": Sun,
"data:table": TableCells,
"data:collection": ListBullet,
"data:container": Square3Stack3d,
"data:board": RectangleGroup,
"data:grid": Squares2x2,
"data:geomap": MapPin,
"e:email": Envelope,
"mc:text": Bars3BottomLeft,
"mc:link": Link,
"plato/card": Clipboard,
"plato/pad": Square2Stack,
"media/image": Photo,
"media/reel": Film,
"media/video": Film,
"media/album": RectangleStack,
"media/audio": Microphone,
"media/song": MusicalNote,
"media/subtitle": Ticket,
"media/overlay": CursorArrowRays,
"social/channel": Megaphone,
"social/stream": Bolt,
"social/contact": User,
"social/event": Clock,
"social/calendar": CalendarDays,
"social/scheduler": Calendar,
"social/reaction": Heart,
"prod/task": Stop,
"prod/project": Flag,
"prod/issue": HandRaised,
"prod/form": Newspaper,
"prod/filling": PencilSquare,
"prod/cad": CubeTransparent,
"prod/slides": PresentationChartBar,
"prod/question": QuestionMarkCircle,
"prod/answer": CheckCircle,
"prod/poll": QuestionMarkCircle,
"prod/vote": CheckCircle,
"media:image": Photo,
"media:reel": Film,
"media:video": Film,
"media:album": RectangleStack,
"media:audio": Microphone,
"media:song": MusicalNote,
"media:subtitle": Ticket,
"media:overlay": CursorArrowRays,
"social:channel": Megaphone,
"social:stream": Bolt,
"social:contact": User,
"social:event": Clock,
"social:calendar": CalendarDays,
"social:scheduler": Calendar,
"social:reaction": Heart,
"social:chatroom": ChatBubbleLeftRight,
"prod:task": Stop,
"prod:project": Flag,
"prod:issue": HandRaised,
"prod:form": Newspaper,
"prod:filling": PencilSquare,
"prod:cad": CubeTransparent,
"prod:slides": PresentationChartBar,
"prod:question": QuestionMarkCircle,
"prod:answer": CheckCircle,
"prod:poll": QuestionMarkCircle,
"prod:vote": CheckCircle,
};
const prefix_mapping = {
"post/": DocumentText,
"post:": DocumentText,
code: CodeBracket,
schema: ArrowsPointingOut,
service: Cube,
"e/": GlobeAlt,
"app/": StopCircle,
"query/": RocketLaunch,
"data/": CircleStack,
"doc/diagram": DocumentChartBar,
"doc/chart": ChartPie,
"doc/viz": ChartPie,
"doc/": ClipboardDocumentList,
"e:": GlobeAlt,
"app:": StopCircle,
"query:": RocketLaunch,
"data:": CircleStack,
"doc:diagram": DocumentChartBar,
"doc:chart": ChartPie,
"doc:viz": ChartPie,
"doc:": ClipboardDocumentList,
file: Document,
};
@ -147,7 +149,7 @@
<!--
did:ng:n:g:z:[official apps]
did:ng:n:g:ns
did:ng:n:g:x:ng
did:ng:n:g:x list of context used by nextgraph
rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#
rdfs: http://www.w3.org/2000/01/rdf-schema#
@ -171,19 +173,19 @@ did:ng:n:g:x list of context used by nextgraph
geo: http://www.opengis.net/ont/geosparql#
time: http://www.w3.org/2006/time#
ng: did:ng:n:g:ns# or http://nextgraph.org/ns#
ng: did:ng:n:g:x:ng# or http://nextgraph.org/x:ng#
did:ng:n:g:ns#post/rich
ng:class => shortcut for did:ng:n:g:ns#class
did:ng:n:g:x:ng#post:rich
ng:class => shortcut for did:ng:n:g:x:ng#class
a rdfs:Class
a ng:class
did:ng:o:xxxx:yy:yy
did:ng:n:xx.xx#name
did:ng:n:x: curated list of ontologies
did:ng:x: curated list of ontologies
did:ng:k common list of things (keyword)
did:ng:n:c common data
did:ng:n:z: curated list of external apps and services
http://nextgraph.org/ns# => the ng: ontology (did:ng:n:g:ns#)
did:ng:n:g:a common data
did:ng:z: curated list of external apps and services (app store)
http://nextgraph.org/x:ng# => the ng: ontology (did:ng:n:g:x:ng#)
ng:compat -> owl:unionOf rdf:List (alphabetical order, including itself as first element)

@ -0,0 +1,111 @@
<!--
// 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,
cannot_load_offline,
online,
get_blob,
} from "../store";
import {
Pencil,
} from "svelte-heros-v2";
import { t } from "svelte-i18n";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import { inview } from 'svelte-inview';
import { cur_tab, nav_bar, can_have_header, header_icon, header_title, header_description, cur_branch, set_header_in_view, edit_header_button, cur_app, load_official_app } from "../tab";
import NavIcon from "./components/NavIcon.svelte";
export let nuri = "";
let width;
let commits;
$: commits = $active_session && nuri && branch_subscribe(nuri, true);
const inview_options = {};//{rootMargin: "-44px"};
function openEditHeader() {
//TODO
}
</script>
<div bind:clientWidth={width}>
{#if $cannot_load_offline}
<div class="row p-4">
<Alert color="yellow">
{@html $t("doc.cannot_load_offline")}
<a href="#/user">{$t("pages.user_panel.title")}</a>.
</Alert>
</div>
{:else}
<div class="flex justify-left" class:justify-center={width>1024} use:inview={inview_options} on:inview_change={(event) => {
const { inView, entry, scrollDirection, observer, node} = event.detail;
if ($cur_branch) { set_header_in_view(inView); }
if (inView) $nav_bar.newest = 0;
}}>
<div class="flex flex-col ">
{#if $can_have_header}
<div class="flex p-4 max-w-screen-lg justify-start flex-wrap" class:w-[1024px]={width>1024} >
{#if $header_icon}
<NavIcon img={$header_icon} config={{
tabindex:"-1",
class:"w-8 h-8 mr-2 mb-2 flex-none focus:outline-none"
}}/>
{/if}
{#if $cur_tab.doc.can_edit}
<button class="p-1 mr-2 mb-2 w-8 h-8 flex-none" on:click={openEditHeader} title={$t($edit_header_button)}>
<Pencil tabindex=-1 class="w-5 h-5 focus:outline-none" />
</button>{#if !$header_title}<span role="button" on:click={openEditHeader} on:keypress={openEditHeader} tabindex="-1" class="h-8 py-1 inline-block align-middle ">{$t($edit_header_button)}</span> {/if}
{/if}
{#if $header_title}
<h1 class="grow text-left text-2xl">{$header_title}</h1>
{/if}
</div>
{#if $header_description}
<div class="flex p-4 max-w-screen-lg text-left text-gray-600 dark:text-white" class:w-[1024px]={width>1024}>
{$header_description}
</div>
{/if}
{/if}
{#if commits}
{#await commits.load()}
<div class="row p-4 max-w-screen-lg text-gray-600" class:w-[1024px]={width>1024}>
<p>{$t("connectivity.loading")}...</p>
</div>
{:then}
{#if $cur_app}
{#await load_official_app($cur_app) then app}
<div class="flex max-w-screen-lg" style="overflow-wrap: anywhere;" class:w-[1024px]={width>1024} >
<svelte:component this={app} commits={$commits}/>
</div>
{/await}
{/if}
{/await}
{/if}
</div>
</div>
{/if}
</div>

@ -18,7 +18,7 @@
Modal,
Toggle,
} from "flowbite-svelte";
import { link, location } from "svelte-spa-router";
import { link, location, push } from "svelte-spa-router";
import MobileBottomBarItem from "./MobileBottomBarItem.svelte";
import MobileBottomBar from "./MobileBottomBar.svelte";
import NavIcon from "./components/NavIcon.svelte";
@ -30,9 +30,12 @@
// @ts-ignore
import { t } from "svelte-i18n";
import { onMount, tick } from "svelte";
import { cur_branch_has_discrete, cur_tab, cur_viewer, cur_editor, toggle_graph_discrete, open_doc,
import { cur_tab, cur_viewer, cur_editor, toggle_graph_discrete, cur_tab_update,
available_editors, available_viewers, set_editor, set_viewer, set_view_or_edit, toggle_live_edit,
has_editor_chat, all_files_count, all_comments_count, nav_bar, save, hideMenu } from "../tab";
has_editor_chat, all_files_count, all_comments_count, nav_bar, save, hideMenu, show_modal_menu } from "../tab";
import {
active_session, redirect_after_login,
} from "../store";
import ZeraIcon from "./ZeraIcon.svelte";
import {
@ -81,6 +84,7 @@
PaperClip,
XMark,
ArrowLeft,
ArchiveBox,
} from "svelte-heros-v2";
import NavBar from "./components/NavBar.svelte";
@ -100,7 +104,7 @@
mobile = true;
}
let panes_available = 0;
let panes_available;
$: if (width < 983) {
panes_available = 0;
} else if (width >= 983 && width < 1304) {
@ -128,11 +132,12 @@
pane_left2_used = false;
pane_right_used = false;
if ($cur_tab.right_pane || $cur_tab.folders_pane || $cur_tab.toc_pane) {
$cur_tab.show_modal_menu = true;
$show_modal_menu = true;
}
} else {
if ($cur_tab.show_modal_menu && !$cur_tab.show_menu) {
$cur_tab.show_modal_menu = false;
}
$: if (panes_available > 0) {
if ($show_modal_menu && !$cur_tab.show_menu) {
$show_modal_menu = false;
}
if (panes_available == 1) {
if ($cur_tab.right_pane) {
@ -208,18 +213,24 @@
let toolsMenu;
async function scrollToTop() {
await tick();
top.scrollIntoView();
if (top) top.scrollIntoView();
}
async function scrollToMenuShare() {
await tick();
shareMenu.scrollIntoView();
if (shareMenu) shareMenu.scrollIntoView();
}
async function scrollToMenuTools() {
await tick();
toolsMenu.scrollIntoView();
if (toolsMenu) toolsMenu.scrollIntoView();
}
onMount(async () => {await open_doc(""); await scrollToTop()});
onMount(async () => {
await scrollToTop();
});
active_session.subscribe((as) => { if(!as) {
$redirect_after_login = $location;
push("#/");
} })
$: activeUrl = "#" + $location;
@ -234,38 +245,41 @@
}
const openPane = (pane:string) => {
// TODO
if ( pane == "folders") {
$cur_tab.folders_pane = !$cur_tab.folders_pane;
if ($cur_tab.folders_pane) {
if (panes_available <= 1 ) {
$cur_tab.right_pane = "";
cur_tab_update((ct) => {
if ( pane == "folders") {
ct.folders_pane = !ct.folders_pane;
if (ct.folders_pane) {
if (panes_available <= 1 ) {
ct.right_pane = "";
}
}
}
} else if ( pane == "toc") {
$cur_tab.toc_pane = !$cur_tab.toc_pane;
if ($cur_tab.toc_pane) {
if (panes_available <= 1 ) {
$cur_tab.folders_pane = false;
$cur_tab.right_pane = "";
} else if (panes_available == 2) {
if ($cur_tab.folders_pane && $cur_tab.right_pane)
$cur_tab.folders_pane = false;
} else if ( pane == "toc") {
ct.toc_pane = !ct.toc_pane;
if (ct.toc_pane) {
if (panes_available <= 1 ) {
ct.folders_pane = false;
ct.right_pane = "";
} else if (panes_available == 2) {
if (ct.folders_pane && ct.right_pane)
ct.folders_pane = false;
}
}
} else {
if (ct.right_pane == pane)
ct.right_pane = "";
else {
ct.right_pane = pane;
}
}
} else {
if ($cur_tab.right_pane == pane)
$cur_tab.right_pane = "";
else {
$cur_tab.right_pane = pane;
if (panes_available > 0) {
ct.show_menu = false;
$show_modal_menu = false;
} else {
$show_modal_menu = true;
ct.show_menu = false;
}
}
if (panes_available) {
hideMenu();
} else {
$cur_tab.show_modal_menu = true;
$cur_tab.show_menu = false;
}
return ct;
});
}
const openShare = (share:string) => {
@ -288,25 +302,30 @@
hideMenu();
}
const openArchive = (share:string) => {
// TODO
hideMenu();
}
const closeModal = () => {
cur_tab.update(ct => {
$show_modal_menu = false;
cur_tab_update(ct => {
ct.show_menu = false;
ct.show_modal_menu = false;
if (!panes_available) {
$cur_tab.right_pane = "";
$cur_tab.folders_pane = false;
$cur_tab.toc_pane = false;
if (panes_available === 0) {
ct.right_pane = "";
ct.folders_pane = false;
ct.toc_pane = false;
}
return ct;
});
}
const closePaneInModal = () => {
cur_tab.update(ct => {
cur_tab_update(ct => {
ct.show_menu = true;
$cur_tab.right_pane = "";
$cur_tab.folders_pane = false;
$cur_tab.toc_pane = false;
ct.right_pane = "";
ct.folders_pane = false;
ct.toc_pane = false;
return ct;
});
}
@ -357,7 +376,7 @@
<Modal id="menu-modal"
outsideclose
bind:open={$cur_tab.show_modal_menu}
bind:open={$show_modal_menu}
size = 'xs'
placement = 'top-right'
backdropClass="bg-gray-900 bg-opacity-50 dark:bg-opacity-80 menu-bg-modal"
@ -382,7 +401,7 @@
<aside style="width:305px; padding:5px;" class="bg-white" aria-label="Sidebar">
<div class="bg-gray-60 overflow-y-auto dark:bg-gray-800">
<ul class="space-y-1 space-x-0 mb-10">
{#if $cur_branch_has_discrete}
{#if $cur_tab.branch.has_discrete}
<li>
<div class="inline-flex graph-discrete-toggle mb-2 ml-2" role="group">
<button on:click={toggle_graph_discrete} disabled={$cur_tab.graph_or_discrete} type="button" style="border-top-left-radius: 0.375rem;border-bottom-left-radius: 0.375rem;" class:selected-toggle={$cur_tab.graph_or_discrete} class:unselected-toggle={!$cur_tab.graph_or_discrete} class="common-toggle" >
@ -451,7 +470,7 @@
</li>
{/if}
{:else}
<MenuItem clickable={()=>launchAppStore($cur_tab.cur_branch.class)}>
<MenuItem clickable={()=>launchAppStore($cur_tab.branch.class)}>
<ZeraIcon
zera="app_store"
config={{tabindex:"-1",
@ -478,123 +497,133 @@
</MenuItem>
{/if}
<MenuItem title={$t("doc.menu.items.mc.desc")} selected={$cur_tab.right_pane == "mc"} clickable={ ()=> openPane("mc") }>
<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.folders.desc")} selected={$cur_tab.folders_pane} clickable={ ()=> openPane("folders") }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["folders"]} />
<span class="ml-3">{$t("doc.menu.items.folders.label")}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.toc.desc")} selected={$cur_tab.toc_pane} clickable={ ()=> openPane("toc") }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["toc"]} />
<span class="ml-3">{$t("doc.menu.items.toc.label")}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.files.desc")} selected={$cur_tab.right_pane == "files"} clickable={ ()=> openPane("files") }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["files"]} />
<span class="ml-3">{$t("doc.menu.items.files.label")} {$all_files_count}</span>
</MenuItem>
<div style="padding:0;" bind:this={shareMenu}></div>
<MenuItem title={$t("doc.menu.items.share.desc")} clickable={ () => { open_share = !open_share; scrollToMenuShare(); } }>
<Share
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.share.label")}</span>
</MenuItem>
{#if open_share }
{#each share_items as share}
<MenuItem title={$t(`doc.menu.items.${share.n}.desc`)} extraClass="submenu" clickable={ () => openShare(share.n) }>
<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}
{#if $cur_tab.branch.id}
<MenuItem title={$t("doc.menu.items.folders.desc")} selected={$cur_tab.folders_pane} clickable={ ()=> openPane("folders") }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["folders"]} />
<span class="ml-3">{$t("doc.menu.items.folders.label")}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.toc.desc")} selected={$cur_tab.toc_pane} clickable={ ()=> openPane("toc") }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["toc"]} />
<span class="ml-3">{$t("doc.menu.items.toc.label")}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.files.desc")} selected={$cur_tab.right_pane == "files"} clickable={ ()=> openPane("files") }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["files"]} />
<span class="ml-3">{$t("doc.menu.items.files.label")} {$all_files_count}</span>
</MenuItem>
<div style="padding:0;" bind:this={shareMenu}></div>
<MenuItem title={$t("doc.menu.items.share.desc")} clickable={ () => { open_share = !open_share; scrollToMenuShare(); } }>
<Share
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.share.label")}</span>
</MenuItem>
{#if open_share }
{#each share_items as share}
<MenuItem title={$t(`doc.menu.items.${share.n}.desc`)} extraClass="submenu" clickable={ () => openShare(share.n) }>
<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}
<MenuItem title={$t("doc.menu.items.comments.desc")} selected={$cur_tab.right_pane == "comments"} clickable={ ()=> openPane("comments") }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["comments"]} />
<span class="ml-3">{$t("doc.menu.items.comments.label")} {$all_comments_count}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.comments.desc")} selected={$cur_tab.right_pane == "comments"} clickable={ ()=> openPane("comments") }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["comments"]} />
<span class="ml-3">{$t("doc.menu.items.comments.label")} {$all_comments_count}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.branches.desc")} selected={$cur_tab.right_pane == "branches"} clickable={ ()=> openPane("branches") }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["branches"]} />
<span class="ml-3">{$t("doc.menu.items.branches.label")}</span>
</MenuItem>
{#if $cur_tab.doc.is_member}
<MenuItem title={$t("doc.menu.items.branches.desc")} selected={$cur_tab.right_pane == "branches"} clickable={ ()=> openPane("branches") }>
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" variation="outline" color="currentColor" icon={pane_items["branches"]} />
<span class="ml-3">{$t("doc.menu.items.branches.label")}</span>
</MenuItem>
{/if}
<MenuItem title={$t("doc.menu.items.history.desc")} selected={$cur_tab.right_pane == "history"} clickable={ ()=> openPane("history") }>
<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.history.desc")} selected={$cur_tab.right_pane == "history"} clickable={ ()=> openPane("history") }>
<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 }>
<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.find.desc")} clickable={ find }>
<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
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.bookmark.desc")} clickable={ bookmark }>
<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 }>
<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.annotate.desc")} clickable={ annotate }>
<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") }>
<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.info.desc")} selected={$cur_tab.right_pane == "info"} clickable={ ()=> openPane("info") }>
<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") }>
<Bell
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.notifs.label")}</span>
</MenuItem>
{#if $cur_tab.doc.is_member}
<MenuItem title={$t("doc.menu.items.permissions.desc")} clickable={ ()=> openAction("permissions") }>
<LockOpen
<MenuItem title={$t("doc.menu.items.notifs.desc")} clickable={ ()=> openAction("notifs") }>
<Bell
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.notifs.label")}</span>
</MenuItem>
{#if $cur_tab.doc.is_member}
<MenuItem title={$t("doc.menu.items.permissions.desc")} clickable={ ()=> openAction("permissions") }>
<LockOpen
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.permissions.label")}</span>
</MenuItem>
{/if}
<MenuItem title={$t("doc.menu.items.settings.desc")} clickable={ ()=> openAction("settings") }>
<Cog6Tooth
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.permissions.label")}</span>
<span class="ml-3">{$t("doc.menu.items.settings.label")}</span>
</MenuItem>
<div style="padding:0;" bind:this={toolsMenu}></div>
<MenuItem title={$t("doc.menu.items.tools.desc")} clickable={ () => {open_tools = !open_tools; scrollToMenuTools();} } >
<WrenchScrewdriver
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.tools.label")}</span>
</MenuItem>
{#if open_tools }
{#each tools_items as tool}
<MenuItem title={$t(`doc.menu.items.${tool.n}.desc`)} extraClass="submenu" clickable={ () => openAction(tool.n) }>
<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>
{/each}
{/if}
{/if}
<MenuItem title={$t("doc.menu.items.settings.desc")} clickable={ ()=> openAction("settings") }>
<Cog6Tooth
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.settings.label")}</span>
<MenuItem title={$t("doc.menu.items.mc.desc")} selected={$cur_tab.right_pane == "mc"} clickable={ ()=> openPane("mc") }>
<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>
<div style="padding:0;" bind:this={toolsMenu}></div>
<MenuItem title={$t("doc.menu.items.tools.desc")} clickable={ () => {open_tools = !open_tools; scrollToMenuTools();} } >
<WrenchScrewdriver
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.tools.label")}</span>
<MenuItem title={$t("doc.menu.items.archive.desc")} selected={$cur_tab.right_pane == "mc"} clickable={ ()=> openArchive() }>
<ArchiveBox
tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
/>
<span class="ml-3">{$t("doc.menu.items.archive.label")}</span>
</MenuItem>
{#if open_tools }
{#each tools_items as tool}
<MenuItem title={$t(`doc.menu.items.${tool.n}.desc`)} extraClass="submenu" clickable={ () => openAction(tool.n) }>
<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>
{/each}
{/if}
</ul>
</div>
</aside>
@ -621,7 +650,7 @@
{#if mobile}
<div class="full-layout">
{#if !withoutNavBar}
<div class="fixed top-0 left-0 right-0">
<div class="fixed top-0 left-0 right-0" style="z-index:39;">
<NavBar {scrollToTop}/>
</div>
{/if}
@ -637,15 +666,14 @@
13
</span>
</MobileBottomBarItem>
<MobileBottomBarItem href="#/stream" icon={Bolt} on:click={scrollToTop} />
<MobileBottomBarItem href="#/stream" icon={Bolt} />
<MobileBottomBarItem
href="#/search"
icon={MagnifyingGlass}
on:click={scrollToTop}
/>
<MobileBottomBarItem href="#/create" icon={PlusCircle} />
<MobileBottomBarItem href="#/site" icon={User} on:click={scrollToTop} />
<MobileBottomBarItem href="#/shared" icon={Users} on:click={scrollToTop} />
</MobileBottomBar>
</div>
{:else}
@ -712,6 +740,7 @@
<SidebarItem
label={$t("pages.full_layout.shared")}
href="#/shared"
on:click={scrollToTop} on:keypress={scrollToTop}
class="py-1 tall-xs:p-2"
>
<svelte:fragment slot="icon">
@ -724,6 +753,7 @@
<SidebarItem
label={$t("pages.full_layout.site")}
href="#/site"
on:click={scrollToTop} on:keypress={scrollToTop}
class="py-1 tall-xs:p-2"
>
<svelte:fragment slot="icon">
@ -790,7 +820,7 @@
</div>
{/if}
<div class:left-[192px]={pane_lefts_used==0} class:left-[513px]={pane_lefts_used==1} class:left-[834px]={pane_lefts_used==2} class:right-0={!pane_right_used} class:right-[321px]={pane_right_used} class="full-layout absolute top-0">
<div class:left-[192px]={pane_lefts_used==0} class:left-[513px]={pane_lefts_used==1} class:left-[834px]={pane_lefts_used==2} class:right-0={!pane_right_used} class:right-[321px]={pane_right_used} class="fixed top-0">
<div style="z-index:39;" class:left-[192px]={pane_lefts_used==0} class:left-[513px]={pane_lefts_used==1} class:left-[834px]={pane_lefts_used==2} class:right-0={!pane_right_used} class:right-[321px]={pane_right_used} class="fixed top-0">
<NavBar {scrollToTop}/>
</div>
<div bind:this={top}></div>

@ -10,16 +10,28 @@
-->
<script lang="ts">
import { onMount, tick } from "svelte";
import { t } from "svelte-i18n";
import FullLayout from "./FullLayout.svelte";
import Test from "./Test.svelte";
import Document from "./Document.svelte";
import {
active_session,
} from "../store";
import {
change_nav_bar,reset_in_memory
} from "../tab";
import {
PaperAirplane,
Bell,
ArrowRightOnRectangle,
Users,
User,
Bookmark,
Sparkles,
Square3Stack3d,
ArchiveBox,
} from "svelte-heros-v2";
import Logo from "./components/Logo.svelte";
import NavBar from "./components/NavBar.svelte";
import NavBar from "./components/NavBar.svelte";
let top;
let width: number;
@ -34,6 +46,12 @@
function scrollToTop() {
top.scrollIntoView();
}
onMount(() => {
change_nav_bar("nav:private",$t("doc.private_store"), false);
reset_in_memory();
});
let nuri = $active_session && ("o:"+$active_session.private_store_id);
</script>
<FullLayout withoutNavBar={true}>
@ -52,8 +70,8 @@
>
</a>
<div class="w-auto flex row">
<a href="#/shared" class="row items-center" on:click>
<Users
<a href="#/site" class="row items-center" on:click={scrollToTop}>
<User
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white focus:outline-none"
/>
@ -84,13 +102,23 @@
</div>
</div>
</nav>
<div class="sticky top-0 w-full">
<div class="sticky top-0 w-full" style="z-index:39;">
<NavBar {scrollToTop}/>
</div>
{/if}
<Test />
<div class="bg-gray-100 flex p-1 justify-around md:justify-start h-11 gap-0 xs:gap-3 text-gray-500">
<div class="overflow-hidden w-24 sm:ml-3 flex justify-start mr-1" role="button" tabindex="0">
<Bookmark tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.bookmarked")}</div></div>
</div>
<div class="overflow-hidden w-32 sm:ml-3 flex justify-start mr-1" role="button" tabindex="0" title={$t("doc.menu.items.mc.desc")}>
<Sparkles tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.menu.items.mc.label")}</div></div>
</div>
<div class="overflow-hidden w-28 sm:ml-3 flex justify-start" role="button" tabindex="0">
<Square3Stack3d tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.all_docs")}</div></div>
</div>
</div>
<Document {nuri}/>
</FullLayout>
<svelte:window bind:innerWidth={width} />

@ -590,7 +590,7 @@
class:h-[160px]={!mobile}
class:h-[93px]={mobile}
class:text-8xl={!mobile}
on:click={async () => await on_pin_key(num)}
on:click={async () => {window.document.activeElement.blur(); await on_pin_key(num)}}
disabled={pin_code.length >= 4}
>
<span>{num}</span>
@ -606,7 +606,7 @@
class:h-[160px]={!mobile}
class:h-[93px]={mobile}
class:text-8xl={!mobile}
on:click={async () => await on_pin_key(shuffle_pin[9])}
on:click={async () => {window.document.activeElement.blur();await on_pin_key(shuffle_pin[9])}}
disabled={pin_code.length >= 4}
>
<span>{shuffle_pin[9]}</span>

@ -17,7 +17,7 @@
} from "../history/gitgraph-js/gitgraph";
import ng from "../api";
import {
branch_subs,
branch_subscribe,
active_session,
cannot_load_offline,
online,
@ -25,7 +25,7 @@
} from "../store";
import { link } from "svelte-spa-router";
import { onMount, onDestroy, tick } from "svelte";
import { Button, Progressbar, Spinner } from "flowbite-svelte";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import DataClassIcon from "./DataClassIcon.svelte";
import { t } from "svelte-i18n";
let is_tauri = import.meta.env.TAURI_PLATFORM;
@ -33,7 +33,7 @@
let upload_progress: null | { total: number; current: number; error?: any } =
null;
let files = $active_session && branch_subs($active_session.private_store_id);
let commits = $active_session && branch_subscribe($active_session.private_store_id, true);
let gitgraph;
@ -658,16 +658,14 @@
<div id="graph-container"></div>
{#if $cannot_load_offline}
<div class="row p-4">
<p>
{@html $t("pages.test.cannot_load_offline")}
<Alert color="yellow">
{@html $t("doc.cannot_load_offline")}
<a href="#/user">{$t("pages.user_panel.title")}</a>.
</p>
</Alert>
</div>
{:else}
<div class="row pt-2">
<!-- <a use:link href="/">
<button tabindex="-1" class=" mr-5 select-none"> Back home </button>
</a> -->
<Button
type="button"
on:click={() => {
@ -721,11 +719,13 @@
/>
</div>
{/if}
{#if files}
{#await files.load()}
{#if commits}
{#await commits.load()}
<p>{$t("connectivity.loading")}...</p>
{:then}
{#each $files as file}
{#each $commits.graph as triple} {triple}<br/> {/each}
{#each $commits.heads as head} {head} <br/> {/each}
{#each $commits.files as file}
<p>
{file.name}

@ -28,7 +28,7 @@ Provide classes using the `className` prop.
export let className: string = "";
let connection_status_class = "logo-blue";
// Color is adjusted to connection status.
$: if ($connection_status === "connecting") {
$: if ($connection_status === "connecting" || $connection_status === "starting") {
connection_status_class = "logo-pulse";
} else if ($connection_status === "disconnected") {
connection_status_class = "logo-gray";

@ -16,10 +16,15 @@
CheckCircle,
CheckBadge,
EllipsisVertical,
ExclamationTriangle,
} from "svelte-heros-v2";
import NavIcon from "./NavIcon.svelte";
import {save, nav_bar, showMenu} from "../../tab";
import {
Popover,
} from "flowbite-svelte";
import {save, nav_bar, showMenu, cur_tab, cur_tab_store_name_override, cur_tab_store_icon_override} from "../../tab";
export let scrollToTop = () => {};
@ -27,6 +32,11 @@
// going back
window.history.go(-1);
}
const closeErrorPopup = () => {
window.document.getElementById("error_popover_btn").click();
}
</script>
<div style="background-color: #fbfbfb;" class="h-11 pb-1 flex text-center text-gray-700 dark:text-white">
@ -35,24 +45,36 @@
<ArrowLeft tabindex="-1" class="w-8 h-8 focus:outline-none"/>
</div>
{/if}
{#if $nav_bar.icon}
{#if $cur_tab_store_icon_override || $nav_bar.icon}
<div style="cursor:pointer;" class:w-10={!$nav_bar.back} class:ml-3={!$nav_bar.back} class="flex-none w-8 m-1 " on:click={scrollToTop} on:keypress={scrollToTop}>
<NavIcon img={$nav_bar.icon} config={{
<NavIcon img={$cur_tab_store_icon_override || $nav_bar.icon} config={{
tabindex:"-1",
class:"w-8 h-8 focus:outline-none"
}}/>
</div>
{/if}
<div style="cursor:pointer;" class:pl-3={!$nav_bar.back && !$nav_bar.icon} class="grow w-10 items-center flex px-1"><span class="inline-block truncate" on:click={scrollToTop} on:keypress={scrollToTop}> {$nav_bar.title} </span></div>
{#if $nav_bar.newest}
<div style="cursor:pointer;" class:pl-3={!$nav_bar.back && !$nav_bar.icon} class="grow w-10 items-center flex px-1" on:click={scrollToTop} on:keypress={scrollToTop}>
<span class="inline-block truncate" > {$cur_tab_store_name_override || $nav_bar.title} </span>
</div>
{#if $nav_bar.newest && !$cur_tab.header_in_view}
<div role="button" tabindex="0" class="flex-none m-1 rounded-full bg-primary-700 text-white dark:bg-primary-700" on:click={scrollToTop} on:keypress={scrollToTop}>
<div class="flex items-center grow pr-2">
<ChevronDoubleUp tabindex="-1" class="w-6 h-6 m-1 focus:outline-none"/>
<span class="inline-block">{@html $nav_bar.newest < 100 ? "+ "+$nav_bar.newest : "<span class=\"text-xl\">&infin;</span>"}</span>
<span class="inline-block">{@html $nav_bar.newest < 100 ? "+"+$nav_bar.newest : "<span class=\"text-xl\">&infin;</span>"}</span>
</div>
</div>
{/if}
{#if $nav_bar.save !== undefined}
{#if $cur_tab.persistent_error}
<div id="error_popover_btn" tabindex="0" class="flex-none w-10" role="button" title={$cur_tab.persistent_error.title + ". Click for more details"}>
<ExclamationTriangle variation="outline" tabindex="-1" strokeWidth="2" class="w-9 h-9 mt-1 text-red-700 focus:outline-none"/>
</div>
<Popover class="text-left text-black w-[300px] text-sm error-popover" title={$cur_tab.persistent_error.title} triggeredBy="#error_popover_btn" trigger="click" placement = 'bottom'
open={true}
>{@html $cur_tab.persistent_error.desc}
<br/><br/><span class="text-primary-700" on:click={closeErrorPopup} on:keypress={closeErrorPopup} role="button" tabindex="0">Dismiss</span>
</Popover>
{:else if $nav_bar.save !== undefined}
{#if $nav_bar.save }
<div tabindex="0" class="flex-none w-10" role="button" on:click={save} on:keypress={save} title="Save">

@ -21,7 +21,12 @@
Bolt,
Megaphone,
QuestionMarkCircle,
ExclamationCircle,
Key,
LockClosed,
GlobeAlt,
UserGroup,
PaperAirplane,
} from "svelte-heros-v2";
import DataClassIcon from "../DataClassIcon.svelte";
@ -33,6 +38,11 @@
stream: Bolt,
channel: Megaphone,
private: Key,
protected: LockClosed,
public: GlobeAlt,
group: UserGroup,
dialog: PaperAirplane,
unknown_doc: ExclamationCircle,
};
const find = (dataClass: string) => {

@ -18,19 +18,22 @@
EllipsisVertical,
} from "svelte-heros-v2";
import { t } from "svelte-i18n";
import {cur_tab} from "../../tab";
import {cur_tab_update} from "../../tab";
export let pane_name = "";
export let pane_items = {};
const closePane = (pane:string|boolean) => {
if (pane=="folders") {
$cur_tab.folders_pane = false;
} else if (pane=="toc") {
$cur_tab.toc_pane = false;
} else {
$cur_tab.right_pane = "";
}
cur_tab_update(($cur_tab) => {
if (pane=="folders") {
$cur_tab.folders_pane = false;
} else if (pane=="toc") {
$cur_tab.toc_pane = false;
} else {
$cur_tab.right_pane = "";
}
return $cur_tab;
});
}
</script>

@ -202,7 +202,7 @@
"pins_no_match": "Du hast nicht zweimal die gleiche PIN eingegeben",
"almost_done": "Wir sind fast fertig!",
"save_wallet_options": {
"description": "Es gibt 4 Optionen, die du auswählen musst, bevor wir dein Wallet erstellen können. Diese Optionen können dir helfen, dein Wallet zu nutzen und zu behalten. Wir möchten aber auch vorsichtig mit deiner Sicherheit und Privatsphäre umgehen.<br /><br />Denk daran, dass du in jedem Fall, sobald dein Wallet erstellt ist, eine Datei herunterladen wirst, die du privat irgendwo auf deinem Gerät, USB-Stick oder einer Festplatte aufbewahren solltest. Dies ist die Standardmethode, mit der du dein Wallet nutzen und aufbewahren kannst. Die folgenden Wahlmöglichkeiten können dir dein Leben ein wenig erleichtern.",
"description": "Es gibt 2 Optionen, die du auswählen musst, bevor wir dein Wallet erstellen können.",
"trust": "Vertraust du diesem Gerät?",
"trust_description": "Wenn ja, wenn dieses Gerät dir gehört oder von wenigen vertrauenswürdigen Personen deiner Familie oder deines Arbeitsplatzes genutzt wird und du dich in Zukunft wieder von diesem Gerät aus anmelden möchtest, dann kannst du dein Wallet auf diesem Gerät speichern. Wenn dieses Gerät hingegen öffentlich ist und von Fremden geteilt wird, speichere dein Wallet nicht hier.",
"trust_toggle": "Mein Wallet auf diesem Gerät speichern?",

@ -1,5 +1,33 @@
{
"doc": {
"doc": "Document",
"protected_store": "Protected Profile",
"public_store": "Public Site",
"private_store": "Private Store",
"group_store": "Group Store",
"dialog_store": "Dialog Store",
"not_found" : "Document not found",
"not_found_details_online" : "The document could not be found locally on this device, nor on the broker.",
"not_found_details_offline" : "The document could not be found locally on this device, and it seems like you are offline, so it could not be retrieved from any broker neither.<br/><br/>If you are opening this document for the first time on this device, you have to be online now so the document can be fetched.<br/><br/> We will try connecting and fetching it every 20 seconds.",
"cannot_load_offline": "You are offline and using the web app. There is currently a limitation on local storage within the Web App, and you need to connect to the broker every time you login with the Web App.<br/><br/>For now, the Web App does not keep a local copy of your documents. due to the limit of 5MB in localStorage. We will remove this limitation soon. Stay tuned!<br/><br/>Check your connectivity status in the ",
"header": {
"buttons": {
"edit": "Edit title, icon, intro",
"edit_profile": "Edit profile",
"bookmarked": "Saved",
"all_docs": "All Docs",
"groups": "Groups",
"channels": "Channels",
"inbox": "Inbox",
"chat": "Chat"
}
},
"errors": {
"InvalidNuri": "Invalid NURI"
},
"errors_details": {
"InvalidNuri": "The provided NextGraph URI is invalid"
},
"graph" : "Graph",
"discrete" : "Document",
"menu" : {
@ -152,6 +180,10 @@
"mc": {
"label": "Magic Carpet",
"desc": "Opens the Magic Carpet (like a clipboard, but better)"
},
"archive": {
"label": "Archive",
"desc": "Archive documents that you don't need anymore"
}
}
}
@ -299,10 +331,10 @@
"2": "In your wallet, we store all the permissions to access documents you have been granted with, or that you have created yourself.",
"3": "In order to open it, you will need to enter your <b >pazzle</b > and a <b>PIN code</b> of 4 digits. Your personal pazzle (contraction of puzzle and password) is composed of 9 images you should remember. The order of the images is important too.",
"4": "Don't worry, it is easier to remember 9 images than a password like \"69$g&ms%C*%\", and it has the same strength as a complex password. The entropy of your pazzle is <b >66bits</b >, which is considered very high by all standards.",
"5": "You should only create <b>one unique wallet for yourself</b >. All your accounts, identities and permissions will be added to this unique wallet later on. Do not create another wallet if you already have one. Instead, you will <b>import</b> your existing wallet in all the apps and websites where you need it.",
"6": "Your wallet can be imported with the help of a small file that you download, or with a QRcode. In any case, you should never share this file or QRcode with anybody else.",
"5": "You should only create <b>one unique wallet for yourself</b >. All your accounts, identities and permissions will be added to this unique wallet later on. Do not create another wallet if you already have one. Instead, you will <b>import</b> your unique existing wallet in all the apps and websites where you need it.",
"6": "Your wallet can be transferred from one device to another with the help of a small file that you download, or with a QRcode, or with a TextCode that you copy/paste. In any case, you should never share this file or QRcode with anybody else.",
"7": "We at NextGraph will never see the content of your wallet. It is encrypted and we do not know your pazzle, so we cannot see what is inside.",
"8": "For the same reason, we won't be able to help you if you forget your pazzle or PIN code, or if you loose the wallet file. <span class=\"text-bold\"> There is no \"password recovery\" option</span > in this case. You can note your pazzle down on a piece of paper until you remember it, but don't forget to destroy this note after a while."
"8": "For the same reason, we won't be able to help you if you forget your pazzle or PIN code, or if you loose the wallet file. <span class=\"text-bold\"> There is no \"password recovery\" option</span > in this case. You can note your pazzle down on a piece of paper until you remember it, but don't forget to destroy this note after a while."
},
"create_wallet_now": "I create my wallet now!",
"select_server": "NextGraph is based on an efficient decentralized P2P network, and in order to join this network and start using the app, you need to first select a <b>broker&nbsp;server</b>.",
@ -319,7 +351,7 @@
},
"choose_broker": "Please choose one broker among the list",
"register_with_broker": "Register with {broker}",
"for_eu_citizens": "European Union Citizens",
"for_eu_citizens": "European Union Server",
"for_rest": "For the rest of the world",
"enter_invite_link": "Enter an invitation link",
"scan_invite_qr": "Scan an invitation QR-code",
@ -362,18 +394,18 @@
"pins_no_match": "You didn't enter the same PIN twice",
"almost_done": "We are almost done!",
"save_wallet_options": {
"description": "There are 4 options to choose before we can create your wallet. Those options can help you to use and keep your wallet. But we also want to be careful with your security and privacy.<br /><br />Remember that in any case, once your wallet will be created, you will download a file that you should keep privately somewhere on your device, USB key or hard-disk. This is the default way you can use and keep your wallet. Now let's look at some options that can make your life a bit easier.",
"description": "There are 2 options to choose before we can create your wallet.",
"trust": "Do you trust this device?",
"trust_description": "If you do, if this device is yours, or it is used by a few trusted persons of your family or workplace, and you would like to login again from this device in the future, then you can save your wallet on this device. To the contrary, if this device is public and shared by strangers, do not save your wallet here.",
"trust_toggle": "Save my wallet on this device?",
"trust_toggle": "Yes, save my wallet on this device",
"device_name_description": "To see which devices you are connected with, every device should have a name (e.g. Bob's laptop). Please enter it here.",
"cloud": "Keep a copy in the cloud?",
"cloud_description": "Are you afraid that you will loose the file containing your wallet? If this would happen, your wallet would be lost forever, together with all your documents. We can keep an encrypted copy of your wallet in our cloud. Only you will be able to download it with a special link. You would have to keep this link safely though. By selecting this option, you agree to the",
"cloud_toggle": "Save my wallet in the cloud?",
"cloud_tos": "Terms of Service of our cloud",
"pdf": "Create a PDF file of your wallet?",
"pdf_description": "We can prepare for you a PDF file containing all the information of your wallet, unencrypted. You should print this file and then delete the PDF (and empty the trash). Keep this printed document in a safe place. It contains all the information to regenerate your wallet in case you lost access to it.",
"pdf_toggle": "Create a PDF of my wallet?",
"pdf": "Should we create a PDF file of your wallet?",
"pdf_description": "We can prepare for you a PDF file containing all the information of your wallet, unencrypted. In the next screen, once your wallet is ready, you will be able to download such PDF file. You should print this file and then delete the PDF (and empty the trash). Keep this printed document in a safe place. It contains all the information to regenerate your wallet in case you lost access to it.",
"pdf_toggle": "Yes, create a PDF of my wallet",
"link": "Create a link to access your wallet easily?",
"link_description": "When you want to use your wallet on the web or from other devices, we can help you find your wallet by creating a simple link accessible from anywhere. Only you will have access to this link. In order to do so, we will keep your wallet ID and some information about your broker on our cloud servers. If you prefer to opt out, just uncheck this option. By selecting this option, you agree to the",
"link_toggle": "Create a link to my wallet?"

@ -29,7 +29,6 @@
let display_login_create = !$has_wallets || !$active_wallet;
let unsubscribe;
onMount(() => {
cannot_load_offline.set(false);
//setTimeout(function () {}, 2);
const combined = derived([active_wallet, has_wallets], ([$s1, $s2]) => [
$s1,

@ -12,24 +12,56 @@
import { onMount } from "svelte";
import { push } from "svelte-spa-router";
import FullLayout from "../lib/FullLayout.svelte";
import Document from "../lib/Document.svelte";
import { t } from "svelte-i18n";
// The params prop contains values matched from the URL
export let params = {};
import {
active_session,
} from "../store";
console.log(params);
onMount(async () => {
if (!$active_session) {
push("#/");
} else {
//await scrollToTop();
}
import {
change_nav_bar, cur_tab, reset_in_memory
} from "../tab";
import {
Square3Stack3d,
Megaphone,
InboxArrowDown,
ChatBubbleLeftRight,
Phone,
VideoCamera,
} from "svelte-heros-v2";
//console.log(params);
let nuri = "";
$: if ($active_session && params[1]) { if (params[1].startsWith("o:"+$active_session.private_store_id)) push("#/");
else if (params[1].startsWith("o:"+$active_session.protected_store_id)) push("#/shared");
else if (params[1].startsWith("o:"+$active_session.public_store_id)) push("#/site"); else nuri = params[1]; }
onMount(() => {
change_nav_bar("nav:unknown_doc",$t("doc.doc"), true);
reset_in_memory();
});
</script>
<FullLayout>
{ params[1] }
{#if nuri && $cur_tab.doc.is_store && $cur_tab.store.store_type === "group"}
<div class="bg-gray-100 flex p-1 justify-around md:justify-start h-11 gap-0 xs:gap-3 md:gap-10 text-gray-500">
<div class="overflow-hidden w-16 xs:ml-3 flex justify-start" role="button" tabindex="0">
<ChatBubbleLeftRight tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.chat")}</div></div>
</div>
<div class="overflow-hidden w-8 xs:ml-2 flex justify-start" role="button" tabindex="0">
<Phone tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"></div>
</div>
<div class="overflow-hidden w-8 xs:ml-2 flex justify-start" role="button" tabindex="0">
<VideoCamera tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"></div>
</div>
<div class="overflow-hidden w-8 xs:ml-3 flex justify-start" role="button" tabindex="0">
<Megaphone tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"></div>
</div>
<div class="overflow-hidden w-16 xxs:w-20 xs:w-24 xs:ml-2 flex justify-start" role="button" tabindex="0">
<Square3Stack3d tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.all_docs")}</div></div>
</div>
</div>
{/if}
{#if nuri}
<Document {nuri}/>
{/if}
</FullLayout>

@ -16,6 +16,7 @@
import {
ArrowLeft,
} from "svelte-heros-v2";
export let params;
</script>
<CenteredLayout displayFooter={true}>

@ -0,0 +1,54 @@
<!--
// 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 { onMount, tick } from "svelte";
import { t } from "svelte-i18n";
import FullLayout from "../lib/FullLayout.svelte";
import Document from "../lib/Document.svelte";
import {
active_session,
} from "../store";
import {
change_nav_bar,reset_in_memory
} from "../tab";
import {
Square3Stack3d,
Megaphone,
UserGroup,
InboxArrowDown,
} from "svelte-heros-v2";
onMount(() => {
change_nav_bar("nav:protected",$t("doc.protected_store"), false);
reset_in_memory();
});
let nuri = $active_session && ("o:"+$active_session.protected_store_id);
</script>
<FullLayout>
<div class="bg-gray-100 flex p-1 justify-around md:justify-start h-11 gap-0 xs:gap-3 text-gray-500">
<div class="overflow-hidden w-20 xxs:w-28 xs:w-36 xs:ml-3 flex justify-start" role="button" tabindex="0">
<UserGroup tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.groups")}</div></div>
</div>
<div class="overflow-hidden w-20 xxs:w-28 xs:w-36 xs:ml-2 flex justify-start" role="button" tabindex="0">
<InboxArrowDown tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.inbox")}</div></div>
</div>
<div class="overflow-hidden w-24 xxs:w-28 xs:w-36 xs:ml-2 flex justify-start" role="button" tabindex="0">
<Megaphone tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.channels")}</div></div>
</div>
<div class="overflow-hidden w-16 xxs:w-28 xs:w-36 xs:ml-2 flex justify-start" role="button" tabindex="0">
<Square3Stack3d tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.all_docs")}</div></div>
</div>
</div>
<Document {nuri}/>
</FullLayout>

@ -0,0 +1,46 @@
<!--
// 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 { onMount, tick } from "svelte";
import { t } from "svelte-i18n";
import FullLayout from "../lib/FullLayout.svelte";
import Document from "../lib/Document.svelte";
import {
active_session,
} from "../store";
import {
change_nav_bar,reset_in_memory
} from "../tab";
import {
Square3Stack3d,
Megaphone,
} from "svelte-heros-v2";
onMount(() => {
change_nav_bar("nav:public",$t("doc.public_store"), false);
reset_in_memory();
});
let nuri = $active_session && ("o:"+$active_session.public_store_id);
</script>
<FullLayout>
<div class="bg-gray-100 flex p-1 justify-start h-11 gap-3 text-gray-500">
<div class="overflow-hidden w-32 ml-3 flex justify-start mr-1" role="button" tabindex="0">
<Megaphone tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-10">{$t("doc.header.buttons.channels")}</div></div>
</div>
<div class="overflow-hidden w-32 ml-3 flex justify-start" role="button" tabindex="0">
<Square3Stack3d tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-10">{$t("doc.header.buttons.all_docs")}</div></div>
</div>
</div>
<Document {nuri}/>
</FullLayout>

@ -992,7 +992,7 @@
</button>
</div>
<div class="row mt-5">
<!-- <div class="row mt-5">
<button
on:click|once={selectNET}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
@ -1014,7 +1014,7 @@
</svg>
{$t("pages.wallet_create.for_rest")}
</button>
</div>
</div> -->
{/if}
<div class="row mt-5">
@ -1445,7 +1445,7 @@
type="text"
/>
{/if}
<p class="max-w-xl md:mx-auto mt-10 lg:max-w-2xl text-left">
<!-- <p class="max-w-xl md:mx-auto mt-10 lg:max-w-2xl text-left">
<span class="text-xl"
>{@html $t(
"pages.wallet_create.save_wallet_options.cloud"
@ -1470,7 +1470,7 @@
"pages.wallet_create.save_wallet_options.cloud_toggle"
)}</Toggle
>
</p>
</p> -->
<p class="max-w-xl md:mx-auto mt-10 lg:max-w-2xl text-left">
<span class="text-xl"
>{@html $t("pages.wallet_create.save_wallet_options.pdf")}</span
@ -1486,7 +1486,7 @@
)}</Toggle
>
</p>
{#if !options.cloud}
<!-- {#if !options.cloud}
<p class="max-w-xl md:mx-auto mt-10 lg:max-w-2xl text-left">
<span class="text-xl"
>{@html $t("pages.wallet_create.save_wallet_options.link")}
@ -1511,7 +1511,7 @@
)}</Toggle
>
</p>
{/if}
{/if} -->
<button
on:click|once={do_wallet}
class="mt-10 mb-20 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-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"

@ -32,9 +32,9 @@
active_session,
set_active_session,
has_wallets,
scanned_qr_code,
display_error,
wallet_from_import,
redirect_after_login
} from "../store";
import { CheckBadge, ExclamationTriangle, QrCode } from "svelte-heros-v2";
@ -107,7 +107,14 @@
function loggedin() {
step = "loggedin";
push("#/");
if ($redirect_after_login) {
let redir=$redirect_after_login;
$redirect_after_login=undefined;
push("#"+redir);
} else {
push("#/");
}
}
function start_login_from_import() {
@ -364,15 +371,17 @@
<a href="/wallet/login-qr" use:link>
<button
style="justify-content: left;"
tabindex="-1"
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2"
>
<QrCode class="w-8 h-8 mr-2 -ml-1" />
<QrCode class="w-8 h-8 mr-2 -ml-1" tabindex="-1"/>
{$t("pages.wallet_login.import_qr")}
</button>
</a>
<a href="/wallet/login-text-code" use:link>
<button
style="justify-content: left;"
tabindex="-1"
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
>
<svg

@ -17,8 +17,11 @@ 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 } from "./tab";
import { encode } from "./base64url";
let all_branches = {};
let retry_branches = {};
// Make sure that a file named `locales/<lang>.json` exists when adding it here.
export const available_languages = {
@ -82,7 +85,9 @@ export const connections: Writable<Record<string, any>> = writable({});
export const active_session = writable(undefined);
export const connection_status: Writable<"disconnected" | "connected" | "connecting"> = writable("disconnected");
export const redirect_after_login = writable(undefined);
export const connection_status: Writable<"disconnected" | "connected" | "connecting" | "starting"> = writable("starting");
let next_reconnect: NodeJS.Timeout | null = null;
@ -117,7 +122,7 @@ export const check_has_camera = async () => {
return has_camera;
};
const updateConnectionStatus = ($connections: Record<string, any>) => {
const updateConnectionStatus = async ($connections: Record<string, any>) => {
// Reset error state for PeerAlreadyConnected errors.
Object.entries($connections).forEach(([cnx, connection]) => {
if (connection.error === "PeerAlreadyConnected") {
@ -149,34 +154,50 @@ const updateConnectionStatus = ($connections: Record<string, any>) => {
if (is_connected) {
connection_status.set("connected");
cannot_load_offline.set(false);
for (const retry of Object.entries(retry_branches)) {
if (!await retry[1]()) {
delete retry_branches[retry[0]];
}
}
} else if (is_connecting) {
connection_status.set("connecting");
} else {
} else if (Object.keys($connections).length) {
connection_status.set("disconnected");
if (get(cannot_load_offline) === undefined) {
cannot_load_offline.set(true);
}
for (const retry of Object.entries(retry_branches)) {
if (!await retry[1]()) {
delete retry_branches[retry[0]];
}
}
} else {
connection_status.set("starting");
}
};
connections.subscribe(($connections) => {
updateConnectionStatus($connections);
connections.subscribe(async ($connections) => {
await updateConnectionStatus($connections);
});
export const online = derived(connection_status, ($connectionStatus) => $connectionStatus == "connected");
export const cannot_load_offline = writable(false);
export const cannot_load_offline = writable(undefined);
if (get(connection_status) == "disconnected" && !import.meta.env.TAURI_PLATFORM) {
cannot_load_offline.set(true);
// if (get(connection_status) == "disconnected" && !import.meta.env.TAURI_PLATFORM) {
// cannot_load_offline.set(true);
let unsubscribe = connection_status.subscribe(async (value) => {
if (value != "disconnected") {
cannot_load_offline.set(false);
if (value == "connected") {
unsubscribe();
}
} else {
cannot_load_offline.set(true);
}
});
}
// let unsubscribe = connection_status.subscribe(async (value) => {
// if (value != "disconnected") {
// cannot_load_offline.set(false);
// if (value == "connected") {
// unsubscribe();
// }
// } else {
// cannot_load_offline.set(true);
// }
// });
// }
export const has_wallets = derived(wallets, ($wallets) => Object.keys($wallets).length);
@ -218,6 +239,7 @@ export const close_active_session = async function() {
let sub = all_branches[branch];
sub.unsubscribe();
}
retry_branches = {};
}
@ -245,7 +267,7 @@ export const reconnect = async function() {
location.href
));
} catch (e) {
console.error(e)
//console.error(e)
}
}
// export const disconnections_subscribe = async function() {
@ -286,13 +308,31 @@ readable(false, function start(set) {
can_connect.subscribe(async (value) => {
if (value[0] && value[0].wallet && value[1]) {
//setTimeout(async()=> {await reconnect();},5000);
await reconnect();
}
});
export const branch_subs = function(nuri) {
// console.log("branch_commits")
export const digest_to_string = function(digest) {
let copy = [...digest.Blake3Digest32];
copy.reverse();
copy.push(0);
let buffer = Uint8Array.from(copy);
return encode(buffer.buffer);
};
export const sparql_update = async function(sparql:string) {
let session = get(active_session);
if (!session) {
console.error("no session");
return;
}
let nuri = "did:ng:"+get(cur_tab).branch.nuri;
await ng.sparql_update(session.session_id, nuri, sparql);
}
export const branch_subscribe = function(nuri:string, in_tab:boolean) {
//console.log("branch_subscribe", nuri)
// const { subscribe, set, update } = writable([]); // create the underlying writable store
// let unsub = () => {};
@ -320,6 +360,8 @@ export const branch_subs = function(nuri) {
// // update,
// };
open_branch(nuri, in_tab);
return {
load: async () => {
@ -331,13 +373,16 @@ export const branch_subs = function(nuri) {
let loader = already_subscribed.load;
already_subscribed.load = undefined;
// already_subscribed.load2 = loader;
await loader();
if (await loader()) {
retry_branches[nuri] = loader;
}
}
},
subscribe: (run, invalid) => {
//console.log("sub");
let already_subscribed = all_branches[nuri];
if (!already_subscribed) {
const { subscribe, set, update } = writable([]); // create the underlying writable store
const { subscribe, set, update } = writable({graph:[], discrete:[], files:[], history: false, heads: []}); // create the underlying writable store
let count = 0;
let unsub = () => { };
already_subscribed = {
@ -351,23 +396,132 @@ export const branch_subs = function(nuri) {
}
unsub();
unsub = () => { };
set([]);
let req = await ng.doc_fetch_repo_subscribe(nuri);
persistent_error(nuri, false);
let req = await ng.doc_fetch_repo_subscribe("did:ng:"+nuri);
req.V0.session_id = session.session_id;
unsub = await ng.app_request_stream(req,
async (commit) => {
//console.log("GOT APP RESPONSE", commit);
if (commit.V0.State) {
for (const file of commit.V0.State.files) {
update((old) => { old.unshift(file); return old; })
async (response) => {
console.log("GOT APP RESPONSE", response);
if (response.V0.TabInfo) {
tab_update(nuri, ($cur_tab) => {
if (response.V0.TabInfo.branch?.id) {
//console.log("BRANCH ID",response.V0.TabInfo.branch?.id);
$cur_tab.branch.id = response.V0.TabInfo.branch.id;
}
if (response.V0.TabInfo.branch?.class) {
$cur_tab = update_class($cur_tab, response.V0.TabInfo.branch.class);
}
if (response.V0.TabInfo.branch?.readcap) {
$cur_tab.branch.readcap = response.V0.TabInfo.branch.readcap;
}
if (response.V0.TabInfo.doc?.nuri) {
$cur_tab.doc.nuri = response.V0.TabInfo.doc.nuri;
}
if (response.V0.TabInfo.doc?.can_edit) {
$cur_tab.doc.can_edit = response.V0.TabInfo.doc.can_edit;
}
if (response.V0.TabInfo.doc?.is_store) {
$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.store?.overlay) {
$cur_tab.store.overlay = response.V0.TabInfo.store.overlay;
}
if (response.V0.TabInfo.store?.store_type) {
if (get(cur_branch) == nuri) {
change_nav_bar(`nav:${response.V0.TabInfo.store.store_type}`,get(format)(`doc.${response.V0.TabInfo.store.store_type}_store`), undefined);
}
$cur_tab.store.store_type = response.V0.TabInfo.store.store_type;
}
update_branch_display($cur_tab);
return $cur_tab;
});
}
else update((old) => {
if (response.V0.State) {
for (const head of response.V0.State.heads) {
let commitId = digest_to_string(head);
old.heads.push(commitId);
}
for (const file of response.V0.State.files) {
old.files.unshift(file);
}
if (response.V0.State.graph) {
for (const triple of response.V0.State.graph.triples){
// TODO: detect ng:a ng:i ng:n and update the tab accordingly
old.graph.push(triple);
}
old.graph.sort();
}
} else if (response.V0.Patch) {
let i = old.heads.length;
while (i--) {
if (response.V0.Patch.commit_info.past.includes(old.heads[i])) {
old.heads.splice(i, 1);
}
}
old.heads.push(response.V0.Patch.commit_id);
if (old.history!==false) {
let commit = [response.V0.Patch.commit_id, response.V0.Patch.commit_info];
if (old.history === true) {
old.history = [commit];
} else {
old.history.push(commit);
}
}
if (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);
}
}
for (const insert of response.V0.Patch.graph.inserts){
// TODO: detect ng:a ng:i ng:n and update the tab accordingly
if (!duplicates.includes(insert)) {
old.graph.push(insert);
}
}
old.graph.sort();
} else if (response.V0.Patch.other?.FileAdd) {
old.files.unshift(response.V0.Patch.other.FileAdd);
} else {
}
}
} else if (commit.V0.Patch.other?.FileAdd) {
update((old) => { old.unshift(commit.V0.Patch.other.FileAdd); return old; })
}
return old;
});
});
}
catch (e) {
console.error(e);
if (e=="RepoNotFound") {
let cnx_status = get(connection_status);
if (cnx_status=="connected" || cnx_status=="disconnected") {
persistent_error(nuri, {
title: get(format)("doc.not_found"),
desc: get(format)(cnx_status=="disconnected"?"doc.not_found_details_offline":"doc.not_found_details_online")
});
}
if (cnx_status!="connected") return true;
} else if (e=="InvalidNuri" || e=="InvalidKey") {
persistent_error(nuri, {
title: get(format)("doc.errors.InvalidNuri"),
desc: get(format)("doc.errors_details.InvalidNuri")
});
} else {
console.error(e);
// TODO: display persistent_error
}
}
// this is in case decrease has been called before the load function returned.
if (count == 0) { unsub(); }

@ -41,6 +41,18 @@
.toggle * {
cursor: pointer;
}
.error-popover h3 {
text-align: center;
color: rgb(200 30 30);
}
.error-popover > div:first-child {
background-color: rgb(200 30 30);
}
.error-popover > div:first-child > h3 {
color: white;
}
/*
#scanner-div {
border: none !important;

@ -18,20 +18,23 @@ import {
import { official_classes } from "./classes";
import { official_apps, official_services } from "./zeras";
import { format } from "svelte-i18n";
let loaded_external_apps = {};
export const load_app = async (appName: string) => {
if (appName.startsWith("n:g:z")) {
let app = official_apps[appName];
if (!app) throw new Error("Unknown official app");
return await import(`./apps/${app["ng:b"]}.svelte`);
} else {
//TODO: load external app from its repo
// TODO: return IFrame component
}
let app = await get_app(appName);
return await import(`./apps/${app["ng:b"]}.svelte`);
};
export const load_official_app = async (app) => {
//console.log(app);
//console.log(app["ng:b"]);
let component = await import(`./apps/${app["ng:b"]}.svelte`);
//console.log(component.default);
return component.default;
};
export const get_app = (appName: string) => {
@ -64,7 +67,7 @@ export const invoke_service = async (serviceName: string, nuri: string, args: ob
};
export const get_class = (class_name) => {
if (class_name.startsWith("app/") && class_name !== "app/z") {
if (class_name.startsWith("app:") && class_name !== "app:z") {
//TODO: load external app from its repo
// cache it in loaded_external_apps
// return the class
@ -124,8 +127,8 @@ const class_to_viewers_editors = (class_name: string) => {
if (class_def["ng:o"]) graph_viewers.push(class_def["ng:o"]);
if (class_def["ng:w"]) graph_editors.push(class_def["ng:w"]);
}
graph_viewers.push.apply(graph_viewers, find_viewers_for_class("data/graph"));
graph_editors.push.apply(graph_editors, find_editors_for_class("data/graph"));
graph_viewers.push.apply(graph_viewers, find_viewers_for_class("data:graph"));
graph_editors.push.apply(graph_editors, find_editors_for_class("data:graph"));
let graph_viewer = graph_viewers[0];
let graph_editor = graph_editors[0];
@ -156,131 +159,286 @@ const class_to_viewers_editors = (class_name: string) => {
}
}
export const open_branch = async (nuri: string) => {
let class_name = "post/rich";
export const open_branch = (nuri: string, in_tab: boolean) => {
all_tabs.update((old) => {
if (!old[nuri]) {
//console.log("creating tab for ",nuri)
old[nuri] = JSON.parse(JSON.stringify(old[""]));
old[nuri].branch.nuri = nuri;
}
return old;
});
if (in_tab) {
cur_branch.set(nuri);
let store_type = get(all_tabs)[nuri].store.store_type;
if (store_type) change_nav_bar(`nav:${store_type}`,get(format)(`doc.${store_type}_store`), undefined);
}
}
export const update_class = (cur_tab, class_name) => {
cur_tab.branch.class = class_name;
cur_tab.graph_or_discrete = get_class(class_name)["ng:crdt"] === "Graph";
cur_tab.branch.has_discrete = !cur_tab.graph_or_discrete;
return {...cur_tab, ...class_to_viewers_editors(class_name)};
}
export const update_branch_display = (cur_tab) => {
if (cur_tab.doc.nuri == cur_tab.branch.nuri) {
cur_tab.branch.type = "main";
cur_tab.branch.display = "main";
}
}
export const open_doc = async (nuri: string) => {
//let class_name = "doc/viz/plotly";
let class_name = "post/md";
export const show_modal_menu = writable(false);
cur_tab.update(ct => {
return {...ct, ...class_to_viewers_editors(class_name)};
});
export const in_memory_graph = writable("");
export const in_memory_discrete = writable("");
}
//TODO: call it also when switching branches inside same repo
export const reset_in_memory = () => {
//console.log("reset_in_memory");
in_memory_graph.update((d)=> {return "";});
in_memory_discrete.update((d)=> {return "";});
//console.log(get(in_memory_graph));
//console.log(get(in_memory_discrete));
};
export const cur_tab = writable({
cur_store: {
has_outer: {
nuri_trail: ":v:l"
export const all_tabs = writable({
"":{
store: {
overlay: "", // "v:"
has_outer: "", // "l:"
store_type: "", //"public" "protected", "private", "group", "dialog",
readcap: "", // "r:" readcap of main
is_member: "", // "r:" readcap of store root branch
inner: "", // "w:l:"
// comes from main branch of store
title: "",
icon: "",
description: "",
},
type: "public", // "protected", "private", "group", "dialog",
favicon: "",
title: "Group B",
is_member: true,
},
cur_branch: {
b: "b:xxx", //branch id (can be null if not of type "branch")
c: "c:xxx", //commit(s) id
type: "main", // "stream", "detached", "branch", "in_memory" (does not save)
display: "c:X", // or main or stream or a:xx or branch:X (only 7 chars)
attachments: 1,
files: 1,
comments: 2,
class: "post/rich",
title: false,
icon: false,
description: "",
app: "n:g:z:json_ld_editor", // current app being used
},
view_or_edit: true,
graph_viewer: "", // selected viewer
graph_editor: "", // selected editor
discrete_viewer: "", // selected viewer
discrete_editor: "", // selected editor
graph_viewers: [], // list of available viewers
graph_editors: [], // list of available editors
discrete_viewers: [], // list of available viewers
discrete_editors: [], // list of available editors
find: false,//or string to find
graph_or_discrete: false, // set to cur_branch.class === "Graph"
read_cap: 'r:',
doc: {
is_store: false,
is_member: true,
can_edit: true,
live_edit: false,
title: "Doc A",
authors: "",
icon: "",
description: "",
stream: {
notif: 1,
last: "",
plato: {
nuri: "",
can_edit: false,
},
live_editors: {
branch: {
nuri: "", // :o or :o:b
readcap: "", // "r:"
comment_branch: "", // nuri
class: "",
has_discrete: false,
id: "", //"b:xxx" branch id (can be null if not of type "branch")
c: "", //"c:xxx" commit(s) id
type: "", // "main", "stream", "detached", "branch", "in_memory" (does not save)
display: "", // or main or stream or a:xx or branch:X or c:X (only 7 chars)
attachments: 0,
files: 0,
comments: 0,
title: "",
icon: "",
description: "",
app: "", // current app being used
},
},
folders_pane: false,
toc_pane: false,
right_pane: "", // "branches", "files", "history", "comments", "info", "chat", "mc"
action: false, // "repost", "dm", "react", "author", "copy", "forward", "link", "qr", "download", "embed", "new_block", "notifs", "schema", "signature", "permissions", "settings", "print", "console", "source", "services", "dev",
show_modal_menu: false,
show_menu: false,
view_or_edit: true, // true=> view, false=> edit
graph_viewer: "", // selected viewer
graph_editor: "", // selected editor
discrete_viewer: "", // selected viewer
discrete_editor: "", // selected editor
graph_viewers: [], // list of available viewers
graph_editors: [], // list of available editors
discrete_viewers: [], // list of available viewers
discrete_editors: [], // list of available editors
find: "",//or string to find
graph_or_discrete: false, // set to branch.crdt === "data/graph"
doc: {
nuri: "",// :o
is_store: false,
is_member: "", // ":r" readcap of root branch
authors: [],
inbox: "",
can_edit: false,
live_edit: false,
title: "",
icon: "",
description: "",
stream: { // only if is_store
notif: 0,
last: "",
nuri: "",
},
live_editors: {
},
branches : {},
},
folders_pane: false,
toc_pane: false,
messenger_pane: false,
right_pane: "", // "branches", "files", "history", "comments", "info", "chat", "mc"
action: false, // "repost", "dm", "react", "author", "copy", "forward", "link", "qr", "download", "embed", "new_block", "notifs", "schema", "signature", "permissions", "settings", "print", "console", "source", "services", "dev",
show_menu: false,
persistent_error: false,
header_in_view: true,
}
});
export const set_header_in_view = function(val) {
cur_tab_update((old) => { old.header_in_view = val; return old;});
}
export const cur_branch = writable("");
export const cur_tab = derived([cur_branch, all_tabs], ([cb, all]) => 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"));
});
export const edit_header_button = derived(cur_tab, ($cur_tab) => {
return ($cur_tab.doc.is_store && ( $cur_tab.store.store_type === "public" || $cur_tab.store.store_type === "protected"))? "doc.header.buttons.edit_profile" : "doc.header.buttons.edit";
});
export const header_title = derived(cur_tab, ($cur_tab) => {
if ($cur_tab.doc.is_store) {
if ($cur_tab.store.store_type !== "private" && $cur_tab.store.store_type !== "dialog") {
return $cur_tab.store.title;
}
} else {
let title = $cur_tab.branch.title || $cur_tab.doc.title;
if (title) return title;
let app = get_app($cur_tab.branch.class);
if (app) return app["ng:n"];
}
return false;
});
/* to be used with <NavIcon img={$cur_tab_store_icon_override || $nav_bar.icon} config={{
tabindex:"-1",
class:"w-8 h-8 focus:outline-none"
}}/>
*/
export const header_icon = derived(cur_tab, ($cur_tab) => {
if ($cur_tab.doc.is_store) {
if ($cur_tab.store.store_type !== "private") {
return $cur_tab.store.icon;// TODO: fetch image and return blob:
}
} else {
let icon = $cur_tab.branch.icon || $cur_tab.doc.icon;
if (icon) return icon;// TODO: fetch image and return blob:
let app = get_app($cur_tab.branch.class);
if (app) return "class:" + app["ng:n"];
}
return false;
});
export const header_description = derived(cur_tab, ($cur_tab) => {
if ($cur_tab.doc.is_store) {
if ($cur_tab.store.store_type !== "private") {
return $cur_tab.store.description;
}
} else {
let description = $cur_tab.branch.description || $cur_tab.doc.description;
if (description) return description;
}
return false;
});
export const cur_tab_store_name_override = derived(cur_tab, ($cur_tab) => { if ($cur_tab.doc.is_store && $cur_tab.store.store_type !== "private" && $cur_tab.store.title && !$cur_tab.header_in_view) return $cur_tab.store.title; else return false; });
export const cur_tab_store_icon_override = derived(cur_tab, ($cur_tab) => { if ($cur_tab.doc.is_store && $cur_tab.store.store_type !== "private" && $cur_tab.store.icon && !$cur_tab.header_in_view) return $cur_tab.store.icon; else return false; });
export const tab_update = function( tab, fn ) {
all_tabs.update((all) => {
all[tab] = fn(all[tab]);
return all;
});
};
export const cur_tab_update = function( fn ) {
all_tabs.update((all) => {
all[get(cur_branch)] = fn(all[get(cur_branch)]);
return all;
});
};
export const showMenu = () => {
cur_tab.update(ct => {
show_modal_menu.set(true);
cur_tab_update(ct => {
ct.show_menu = true;
ct.show_modal_menu = true;
return ct;
});
}
export const hideMenu = () => {
cur_tab.update(ct => {
show_modal_menu.set(false);
cur_tab_update(ct => {
ct.show_menu = false;
ct.show_modal_menu = false;
return ct;
});
}
export const nav_bar = writable({
//icon: "class:post/rich",
icon: "nav:private",
//icon: "class:post:rich",
icon: "",
//icon: "blob:http://localhost:1421/be6f968f-ff51-4e8f-bd32-36c60b7af49a",
title: "Private Store",
title: "",
back: false,
newest: 0,
save: true,
save: undefined,
toasts: [],
});
export const change_nav_bar = (icon, title, back) => {
nav_bar.update((old) => {
if (icon !== undefined) {
old.icon = icon;
}
if (title !== undefined) {
old.title = title;
}
if (back !== undefined) {
old.back = back;
}
return old;
});
};
export const persistent_error = (nuri, pe) => {
tab_update(nuri, tab => {
tab.persistent_error = pe;
return tab;
});
}
export const save = async () => {
// saving the doc
}
export const all_files_count = derived(cur_tab, ($cur_tab) => {
let total = $cur_tab.cur_branch.attachments + $cur_tab.cur_branch.files;
let total = $cur_tab.branch.attachments + $cur_tab.branch.files;
return total ? `(${total})` : "";
});
export const all_comments_count = derived(cur_tab, ($cur_tab) => {
let total = $cur_tab.cur_branch.comments;
let total = $cur_tab.branch.comments;
return total ? `(${total})` : "";
});
export const has_editor_chat = derived(cur_tab, ($cur_tab) => {
return $cur_tab.doc.can_edit && $cur_tab.cur_store.type !== "private" && $cur_tab.cur_store.type !== "dialog";
return $cur_tab.doc.can_edit && $cur_tab.store.store_type !== "private" && $cur_tab.store.store_type !== "dialog";
});
export const toggle_live_edit = () => {
cur_tab.update(ct => {
cur_tab_update(ct => {
ct.doc.live_edit = !ct.doc.live_edit;
return ct;
});
@ -288,36 +446,36 @@ export const toggle_live_edit = () => {
export const set_viewer = (app_name: string) => {
if (get(cur_tab).graph_or_discrete) {
cur_tab.update(ct => {ct.graph_viewer = app_name; ct.cur_branch.app = app_name; return ct;});
cur_tab_update(ct => {ct.graph_viewer = app_name; ct.branch.app = app_name; return ct;});
} else {
cur_tab.update(ct => {ct.discrete_viewer = app_name; ct.cur_branch.app = app_name; return ct;});
cur_tab_update(ct => {ct.discrete_viewer = app_name; ct.branch.app = app_name; return ct;});
}
}
export const set_editor = (app_name: string) => {
if (get(cur_tab).graph_or_discrete) {
cur_tab.update(ct => {ct.graph_editor = app_name; ct.cur_branch.app = app_name; return ct;});
cur_tab_update(ct => {ct.graph_editor = app_name; ct.branch.app = app_name; return ct;});
} else {
cur_tab.update(ct => {ct.discrete_editor = app_name; ct.cur_branch.app = app_name; return ct;});
cur_tab_update(ct => {ct.discrete_editor = app_name; ct.branch.app = app_name; return ct;});
}
}
export const toggle_graph_discrete = () => {
cur_tab.update(ct => {
cur_tab_update(ct => {
ct.graph_or_discrete = !ct.graph_or_discrete;
return ct;
});
}
export const set_graph_discrete = (val:boolean) => {
cur_tab.update(ct => {
cur_tab_update(ct => {
ct.graph_or_discrete = val;
return ct;
});
}
export const set_view_or_edit = (val:boolean) => {
cur_tab.update(ct => {
cur_tab_update(ct => {
ct.view_or_edit = val;
return ct;
});
@ -339,6 +497,14 @@ export const cur_editor = derived(cur_tab, ($cur_tab) => {
}
});
export const cur_app = derived(cur_tab, ($cur_tab) => {
let app_name = $cur_tab.view_or_edit ? $cur_tab.graph_or_discrete ? $cur_tab.graph_viewer : $cur_tab.discrete_viewer : $cur_tab.graph_or_discrete ? $cur_tab.graph_editor : $cur_tab.discrete_editor;
if (app_name) {
let app = get_app(app_name);
return app;
}
});
export const available_viewers = derived(cur_tab, ($cur_tab) => {
let list = $cur_tab.graph_or_discrete ? $cur_tab.graph_viewers : $cur_tab.discrete_viewers;
return list.map((viewer) => {
@ -357,4 +523,3 @@ export const available_editors = derived(cur_tab, ($cur_tab) => {
});
});
export const cur_branch_has_discrete = derived(cur_tab, ($cur_tab) => (get_class($cur_tab.cur_branch.class)["ng:crdt"]) !== "Graph");

@ -28,7 +28,7 @@ export const official_apps = {
"ng:u": "json_ld_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:json_ld_editor",
"ng:b": "JsonLdEditor",
"ng:w": ["data/graph"],
"ng:w": ["data:graph"],
},
"n:g:z:json_editor": {
"ng:n": "JSON Editor",
@ -37,7 +37,7 @@ export const official_apps = {
"ng:u": "json_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:json_editor",
"ng:b": "JsonEditor",
"ng:w": ["data/json","data/array","data/map"],
"ng:w": ["data:json","data:array","data:map"],
},
"n:g:z:triple_editor": {
"ng:n": "Graph Triples Editor",
@ -46,7 +46,7 @@ export const official_apps = {
"ng:u": "triple_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:triple_editor",
"ng:b": "TripleEditor",
"ng:w": ["data/graph"],
"ng:w": ["data:graph"],
},
"n:g:z:json_ld_viewer": {
"ng:n": "JSON-LD",
@ -55,7 +55,7 @@ export const official_apps = {
"ng:u": "json_ld_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:json_ld_viewer",
"ng:b": "JsonLdViewer",
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
},
"n:g:z:rdf_viewer:graph": {
"ng:n": "Graph Explorer",
@ -64,7 +64,7 @@ export const official_apps = {
"ng:u": "graph_viewer",//favicon. can be a did:ng:j
"ng:g": "n:g:z:rdf_viewer:graph",
"ng:b": "GraphViewer", // GraphExplorer https://github.com/zazuko/graph-explorer !! AGPL
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
"ng:w": [],
},
"n:g:z:json_viewer": {
@ -74,7 +74,7 @@ export const official_apps = {
"ng:u": "json_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:json_viewer",
"ng:b": "JsonViewer",
"ng:o": ["data/json","data/array","data/map"],
"ng:o": ["data:json","data:array","data:map"],
},
"n:g:z:triple_viewer": {
"ng:n": "Graph Triples",
@ -83,37 +83,37 @@ export const official_apps = {
"ng:u": "triple_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:triple_viewer",
"ng:b": "TripleViewer",
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
},
"n:g:z:sparql_query:yasgui": {
"ng:n": "SPARQL Query",
"ng:a": "View, edit and invoke the Graph 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:b": "SparqlQueryEditor", // YASGUI of Zazuko https://github.com/zazuko/trifid/tree/main/packages/yasgui
"ng:o": ["data/graph"],
"ng:w": ["query/sparql"],
"ng:o": ["data:graph"],
"ng:w": ["query:sparql"],
},
"n:g:z:sparql_query:sparnatural": {
"ng:n": "SPARNatural Query",
"ng:a": "View, edit and invoke the Graph SPARQL query with SPARnatural tool",
"ng:a": "View, edit and invoke a Graph SPARQL query with SPARnatural tool",
"ng:c": "app",
"ng:u": "sparnatural",//favicon. can be a did:ng:j
"ng:g": "n:g:z:sparql_query:sparnatural",
"ng:b": "SparNaturalEditor",
"ng:o": ["data/graph"],
"ng:w": ["query/sparql"],
"ng:o": ["data:graph"],
"ng:w": ["query:sparql"],
},
"n:g:z:graphql_query": {
"ng:n": "GraphQL Query",
"ng:a": "View, edit and invoke the GraphQL query",
"ng:a": "View, edit and invoke a GraphQL query",
"ng:c": "app",
"ng:u": "graphql",//favicon. can be a did:ng:j
"ng:g": "n:g:z:graphql_query",
"ng:b": "GraphqlEditor",
"ng:o": ["data/graph"],
"ng:w": ["query/graphql"],
"ng:o": ["data:graph"],
"ng:w": ["query:graphql"],
},
"n:g:z:sparql_update:yasgui": {
"ng:n": "SPARQL Update",
@ -123,7 +123,7 @@ export const official_apps = {
"ng:g": "n:g:z:sparql_update:yasgui",
"ng:b": "SparqlUpdateEditor", // YASGUI of Zazuko https://github.com/zazuko/trifid/tree/main/packages/yasgui
"ng:o": [],
"ng:w": ["query/sparql_update","data/graph"],
"ng:w": ["query:sparql_update","data:graph"],
},
"n:g:z:rdf_viewer:turtle": { // https://github.com/highlightjs/highlightjs-turtle/tree/master
"ng:n": "Turtle",
@ -132,7 +132,7 @@ export const official_apps = {
"ng:u": "rdf_viewer",//favicon. can be a did:ng:j
"ng:g": "n:g:z:rdf_viewer:turtle",
"ng:b": "TurtleViewer",
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
"ng:w": [],
},
"n:g:z:rdf_viewer:n3": { // ?
@ -142,7 +142,7 @@ export const official_apps = {
"ng:u": "rdf_viewer",//favicon. can be a did:ng:j
"ng:g": "n:g:z:rdf_viewer:n3",
"ng:b": "N3Viewer",
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
"ng:w": [],
},
"n:g:z:rdf_viewer:json_ld": { // highlight.js JSON
@ -152,7 +152,7 @@ export const official_apps = {
"ng:u": "rdf_viewer",//favicon. can be a did:ng:j
"ng:g": "n:g:z:rdf_viewer:json_ld",
"ng:b": "JsonLdViewer",
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
"ng:w": [],
},
"n:g:z:ontology_editor": {
@ -163,7 +163,7 @@ 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",
@ -172,7 +172,7 @@ export const official_apps = {
"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:o": ["schema:owl"],
"ng:w": [],
},
"n:g:z:sparql:invoke": { // displayed with highlight.js https://github.com/highlightjs/highlightjs-turtle/tree/master
@ -182,7 +182,7 @@ export const official_apps = {
"ng:u": "invoke",//favicon. can be a did:ng:j
"ng:g": "n:g:z:sparql:invoke",
"ng:b": "SparqlInvoker",
"ng:o": ["query/sparql","query/sparql_update"],
"ng:o": ["query:sparql","query:sparql_update"],
"ng:w": [],
},
"n:g:z:graphql:invoke": {
@ -192,7 +192,7 @@ export const official_apps = {
"ng:u": "invoke",//favicon. can be a did:ng:j
"ng:g": "n:g:z:graphql:invoke",
"ng:b": "GraphqlInvoker",
"ng:o": ["query/graphql"],
"ng:o": ["query:graphql"],
"ng:w": [],
},
"n:g:z:dump_download": {
@ -202,7 +202,7 @@ export const official_apps = {
"ng:u": "download",//favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_download",
"ng:b": "Downloader",
"ng:o": ["data/graph","file*","data/*"],
"ng:o": ["data:graph","file*","data:*"],
"ng:w": [],
},
"n:g:z:post_rich_editor": {
@ -213,7 +213,7 @@ export const official_apps = {
"ng:g": "n:g:z:post_rich_editor",
"ng:b": "ProseMirrorEditor",
"ng:o": [],
"ng:w": ["post/rich"],
"ng:w": ["post:rich"],
},
"n:g:z:post_md_editor": {
"ng:n": "Post MD Editor",
@ -223,7 +223,7 @@ export const official_apps = {
"ng:g": "n:g:z:post_md_editor",
"ng:b": "MilkDownEditor",
"ng:o": [],
"ng:w": ["post/md"],
"ng:w": ["post:md"],
},
"n:g:z:code_editor": {
"ng:n": "Code and Text Editor",
@ -233,7 +233,7 @@ export const official_apps = {
"ng:g": "n:g:z:code_editor",
"ng:b": "CodeMirrorEditor",
"ng:o": [],
"ng:w": ["code*","post/text"],
"ng:w": ["code*","post:text"],
},
"n:g:z:file_viewer": {
"ng:n": "File details",
@ -252,7 +252,7 @@ export const official_apps = {
"ng:u": "source",//favicon. can be a did:ng:j
"ng:g": "n:g:z:file_source",
"ng:b": "FileSource",
"ng:o": ["file/text"],
"ng:o": ["file:text"],
"ng:w": [],
},
"n:g:z:crdt_source_viewer:xml": {
@ -262,7 +262,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", "doc:diagram:drawio"],
"ng:w": [],
},
"n:g:z:viewer:md": {
@ -272,7 +272,7 @@ export const official_apps = {
"ng:u": "source",//favicon. can be a did:ng:j
"ng:g": "n:g:z:viewer:md",
"ng:b": "MdSource", // displayed with highlight.js , with option to download
"ng:o": ["post/md"],
"ng:o": ["post:md"],
"ng:w": [],
},
"n:g:z:crdt_source_viewer:json": {
@ -282,7 +282,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": "JsonSource", // displayed with highlight.js , with option to download
"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", "doc:diagram:jsmind", "doc:diagram:gantt", "doc:diagram:excalidraw", "doc:viz:*", "doc:chart:*", "prod:cad"],
"ng:w": [],
},
"n:g:z:crdt_source_viewer:text": {
@ -292,8 +292,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
"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","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:w": [],
},
"n:g:z:crdt_source_viewer:rdf": {
@ -303,7 +303,7 @@ export const official_apps = {
"ng:u": "source",//favicon. can be a did:ng:j
"ng:g": "n:g:z:crdt_source_viewer:rdf",
"ng:b": "TurtleViewer", //, with option to download
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
"ng:w": [],
},
"n:g:z:post:rich": {
@ -313,7 +313,7 @@ export const official_apps = {
"ng:u": "post",//favicon. can be a did:ng:j
"ng:g": "n:g:z:post:rich",
"ng:b": "PostRichViewer", // https://www.npmjs.com/package/prosemirror-to-html-js or https://prosemirror.net/docs/ref/version/0.4.0.html#toDOM https://prosemirror.net/docs/ref/version/0.4.0.html#toHTML
"ng:o": ["post/rich"],
"ng:o": ["post:rich"],
"ng:w": [],
},
"n:g:z:post:md": {
@ -323,7 +323,7 @@ export const official_apps = {
"ng:u": "post",//favicon. can be a did:ng:j
"ng:g": "n:g:z:post:md",
"ng:b": "PostMdViewer", // https://github.com/wooorm/markdown-rs
"ng:o": ["post/md"],
"ng:o": ["post:md"],
"ng:w": [],
},
"n:g:z:post:text": {
@ -333,7 +333,7 @@ export const official_apps = {
"ng:u": "post",//favicon. can be a did:ng:j
"ng:g": "n:g:z:post:text",
"ng:b": "TextViewer", // displayed with a <p>
"ng:o": ["post/text"],
"ng:o": ["post:text"],
"ng:w": [],
},
"n:g:z:pre": {
@ -343,7 +343,7 @@ export const official_apps = {
"ng:u": "post",//favicon. can be a did:ng:j
"ng:g": "n:g:z:pre",
"ng:b": "PreTextViewer", // displayed with highlight.js
"ng:o": ["code*","post/text"],
"ng:o": ["code*","post:text"],
"ng:w": [],
},
"n:g:z:pad": {
@ -373,7 +373,7 @@ export const official_apps = {
"ng:u": "gallery",//favicon. can be a did:ng:j
"ng:g": "n:g:z:gallery",
"ng:b": "Gallery",
"ng:o": ["media/album","data/collection"],
"ng:o": ["media:album","data:collection"],
"ng:w": [],
},
"n:g:z:app_store": {
@ -383,7 +383,7 @@ export const official_apps = {
"ng:u": "app_store",//favicon. can be a did:ng:j
"ng:g": "n:g:z:app_store",
"ng:b": "AppStore",
"ng:o": ["app/z"],
"ng:o": ["app:z"],
"ng:w": [],
},
"n:g:z:app_editor": {
@ -393,37 +393,37 @@ export const official_apps = {
"ng:u": "app_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:app_editor",
"ng:b": "AppEditor",
"ng:o": ["app/z"],
"ng:w": ["app/z"],
"ng:o": ["app:z"],
"ng:w": ["app:z"],
},
"n:g:z:list": {
"ng:n": "List view",
"ng:n": "List",
"ng:a": "See the content of document as a list",
"ng:c": "app",
"ng:u": "list",//favicon. can be a did:ng:j
"ng:g": "n:g:z:list",
"ng:b": "ListView",
"ng:o": ["data/collection","data/container"],
"ng:w": ["data/collection","data/container"],
"ng:o": ["data:collection","data:container"],
"ng:w": ["data:collection","data:container"],
},
"n:g:z:grid": {
"ng:n": "Grid view",
"ng:n": "Grid",
"ng:a": "See the content of document as a grid",
"ng:c": "app",
"ng:u": "grid",//favicon. can be a did:ng:j
"ng:g": "n:g:z:grid",
"ng:b": "GridView",
"ng:o": ["data/grid"],
"ng:w": ["data/grid"],
"ng:o": ["data:grid"],
"ng:w": ["data:grid"],
},
"n:g:z:media": {
"ng:n": "Media view",
"ng:n": "Media",
"ng:a": "View media",
"ng:c": "app",
"ng:u": "view",//favicon. can be a did:ng:j
"ng:g": "n:g:z:media",
"ng:b": "MediaView",
"ng:o": ["media/*"],
"ng:o": ["media:*"],
"ng:w": [],
},
"n:g:z:service_editor": {
@ -434,7 +434,7 @@ export const official_apps = {
"ng:g": "n:g:z:service_editor",
"ng:b": "CodeMirrorEditor",
"ng:o": [],
"ng:w": ["service/*"],
"ng:w": ["service:*"],
},
"n:g:z:service_invoke": {
"ng:n": "Service Invoker",
@ -453,7 +453,7 @@ export const official_apps = {
"ng:u": "invoke",//favicon. can be a did:ng:j
"ng:g": "n:g:z:external_service_invoke",
"ng:b": "ExternalServiceInvoker",
"ng:o": ["service/*"],
"ng:o": ["service:*"],
"ng:w": [],
},
"n:g:z:upload_file": {
@ -464,7 +464,7 @@ export const official_apps = {
"ng:g": "n:g:z:upload_file",
"ng:b": "UploadFile",
"ng:o": [],
"ng:w": ["data/graph"],
"ng:w": ["data:graph"],
},
"n:g:z:import_file": {
"ng:n": "Import from external file",
@ -474,7 +474,7 @@ export const official_apps = {
"ng:g": "n:g:z:import_file",
"ng:b": "UploadFile",
"ng:o": [],
"ng:w": ["data/graph"],
"ng:w": ["data:graph"],
},
// TODO: "n:g:z:columns", "n:g:z:tree", "n:g:z:summary", "n:g:z:list_n_post", "n:g:z:grid_n_post", "n:g:z:board",
// TODO: "n:g:z:map", "n:g:z:chart", "n:g:z:pivot", "n:g:z:timeline", "n:g:z:email", "n:g:z:web_archive", "n:g:z:diagram_editor", "n:g:z:pdf", "n:g:z:latex", "n:g:z:media_editor",
@ -496,9 +496,9 @@ export const official_services = {
"ng:c": "service",
"ng:u": "data",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_rdf:turtle",
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
"ng:w": [],
"ng:result": ["file/iana/text/turtle"],
"ng:result": ["file:iana:text:turtle"],
},
"n:g:z:dump_rdf:n3": {
"ng:n": "N3 export",
@ -506,9 +506,9 @@ export const official_services = {
"ng:c": "service",
"ng:u": "data",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_rdf:n3",
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
"ng:w": [],
"ng:result": ["file/iana/text/n3"],
"ng:result": ["file:iana:text:n3"],
},
"n:g:z:dump_rdf:json_ld": {
"ng:n": "JSON-LD export",
@ -516,9 +516,9 @@ export const official_services = {
"ng:c": "service",
"ng:u": "data",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_rdf:json_ld",
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
"ng:w": [],
"ng:result": ["file/iana/application/ld+json"],
"ng:result": ["file:iana:application:ld+json"],
},
"n:g:z:load_rdf:turtle": {
"ng:n": "Import Turtle triples",
@ -527,7 +527,7 @@ export const official_services = {
"ng:u": "load_graph",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_rdf:turtle",
"ng:o": [],
"ng:w": ["data/graph"],
"ng:w": ["data:graph"],
},
"n:g:z:load_rdf:n3": {
"ng:n": "Import N3 triples",
@ -536,7 +536,7 @@ export const official_services = {
"ng:u": "load_graph",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_rdf:n3",
"ng:o": [],
"ng:w": ["data/graph"],
"ng:w": ["data:graph"],
},
"n:g:z:load_rdf:json_ld": {
"ng:n": "Import JSON-LD triples",
@ -545,7 +545,7 @@ export const official_services = {
"ng:u": "load_graph",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_rdf:json_ld",
"ng:o": [],
"ng:w": ["data/graph"],
"ng:w": ["data:graph"],
},
"n:g:z:load_file": {
"ng:n": "Add file",
@ -554,7 +554,7 @@ export const official_services = {
"ng:u": "load",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_file",
"ng:o": [],
"ng:w": ["data/graph"],
"ng:w": ["data:graph"],
},
"n:g:z:dump_file": {
"ng:n": "Export file",
@ -563,7 +563,7 @@ export const official_services = {
"ng:u": "dump",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_file",
"ng:o": ["file*"],
"ng:result": ["file/iana/*"],
"ng:result": ["file:iana:*"],
},
"n:g:z:dump_json": {
"ng:n": "JSON export",
@ -571,9 +571,9 @@ 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", "doc:diagram:jsmind", "doc:diagram:gantt", "doc:diagram:excalidraw", "doc:viz:*", "doc:chart:*", "prod:cad"],
"ng:w": [],
"ng:result": ["file/iana/application/json"],
"ng:result": ["file:iana:application:json"],
},
"n:g:z:dump_xml": {
"ng:n": "XML export",
@ -581,9 +581,9 @@ 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", "doc:diagram:drawio"],
"ng:w": [],
"ng:result": ["file/iana/text/xml"],
"ng:result": ["file:iana:text:xml"],
},
"n:g:z:dump_text": {
"ng:n": "Text export",
@ -591,10 +591,10 @@ 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","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:w": [],
"ng:result": ["file/iana/text/plain"],
"ng:result": ["file:iana:text:plain"],
},
"n:g:z:dump_ng_html_file": {
"ng:n": "NextGraph Standalone file",
@ -602,9 +602,9 @@ export const official_services = {
"ng:c": "service",
"ng:u": "ext",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_ng_html_file",
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
"ng:w": [],
"ng:result": ["file/iana/text/html"],
"ng:result": ["file:iana:text:html"],
},
"n:g:z:load_json": {
"ng:n": "Import JSON",
@ -613,7 +613,7 @@ export const official_services = {
"ng:u": "load",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_json",
"ng:o": [],
"ng:w": ["data/json","data/map", "data/array"],
"ng:w": ["data:json","data:map", "data:array"],
},
"n:g:z:load_xml": {
"ng:n": "Import XML",
@ -622,7 +622,7 @@ export const official_services = {
"ng:u": "load",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_xml",
"ng:o": [],
"ng:w": ["data/xml"],
"ng:w": ["data:xml"],
},
"n:g:z:load_text": {
"ng:n": "Import Text",
@ -631,7 +631,7 @@ export const official_services = {
"ng:u": "load",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_text",
"ng:o": [],
"ng:w": ["post/text","post/rich","post/md"],
"ng:w": ["post:text","post:rich","post:md"],
},
"n:g:z:load_md": {
"ng:n": "Import Markdown",
@ -640,7 +640,7 @@ export const official_services = {
"ng:u": "load",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_md",
"ng:o": [],
"ng:w": ["post/md"],
"ng:w": ["post:md"],
},
"n:g:z:sparql_query": {
"ng:n": "SPARQL query",
@ -648,9 +648,9 @@ export const official_services = {
"ng:c": "service",
"ng:u": "sparql_query",// favicon. can be a did:ng:j
"ng:g": "n:g:z:sparql_query",
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
"ng:w": [],
"ng:result": ["file/iana/application/sparql-results+json","file/iana/application/json"]
"ng:result": ["file:iana:application:sparql-results+json","file:iana:application:json"]
},
"n:g:z:sparql_update": {
"ng:n": "SPARQL update",
@ -659,7 +659,7 @@ export const official_services = {
"ng:u": "sparql_query",// favicon. can be a did:ng:j
"ng:g": "n:g:z:sparql_update",
"ng:o": [],
"ng:w": ["data/graph"],
"ng:w": ["data:graph"],
},
"n:g:z:dump_crdt_source": { // uses dump_rdf, dump_text, dump_json or dump_xml depending on the CRDT type
"ng:n": "Export source",
@ -667,8 +667,8 @@ export const official_services = {
"ng:c": "service",
"ng:u": "source",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_crdt_source",
"ng:o": ["data/graph"],
"ng:o": ["data:graph"],
"ng:w": [],
"ng:result": ["file/iana/*"]
"ng:result": ["file:iana:*"]
},
};

@ -11,6 +11,9 @@ import topLevelAwait from "vite-plugin-top-level-await";
export default defineConfig(async () => {
const host = await internalIpV4()
const config = {
// optimizeDeps: {
// exclude: ["codemirror", "@codemirror/legacy-modes/mode/sparql"]
// },
worker: {
format: 'es',
plugins : [

@ -34,12 +34,12 @@ impl PinRepo {
for (_, branch) in repo.branches.iter() {
if let Some(privkey) = &branch.topic_priv_key {
rw_topics.push(PublisherAdvert::new(
branch.topic,
branch.topic.unwrap(),
privkey.clone(),
*broker_id,
));
} else {
ro_topics.push(branch.topic);
ro_topics.push(branch.topic.unwrap());
}
}
PinRepo::V0(PinRepoV0 {

@ -33,7 +33,7 @@ impl TopicSub {
(
repo.store.inner_overlay(),
Some(PublisherAdvert::new(
branch.topic,
branch.topic.unwrap(),
branch.topic_priv_key.to_owned().unwrap(),
*broker_id.unwrap(),
)),
@ -45,7 +45,7 @@ impl TopicSub {
TopicSub::V0(TopicSubV0 {
repo_hash: repo.id.into(),
overlay: Some(overlay),
topic: branch.topic,
topic: branch.topic.unwrap(),
publisher,
})
}

@ -28,6 +28,9 @@ lazy_static! {
static ref RE_FILE_READ_CAP: Regex =
Regex::new(r"^did:ng:j:([A-Za-z0-9-_]*):k:([A-Za-z0-9-_]*)$").unwrap();
#[doc(hidden)]
static ref RE_REPO_O: Regex =
Regex::new(r"^did:ng:o:([A-Za-z0-9-_]*)$").unwrap();
#[doc(hidden)]
static ref RE_REPO: Regex =
Regex::new(r"^did:ng:o:([A-Za-z0-9-_]*):v:([A-Za-z0-9-_]*)$").unwrap();
#[doc(hidden)]
@ -143,7 +146,7 @@ pub struct CommitInfoJs {
pub author: String,
pub final_consistency: bool,
pub commit_type: CommitType,
pub branch: String,
pub branch: Option<String>,
pub x: u32,
pub y: u32,
}
@ -157,7 +160,7 @@ impl From<&CommitInfo> for CommitInfoJs {
author: info.author.clone(),
final_consistency: info.final_consistency,
commit_type: info.commit_type.clone(),
branch: info.branch.unwrap().to_string(),
branch: info.branch.map(|b| b.to_string()),
x: info.x,
y: info.y,
}
@ -211,7 +214,7 @@ impl NuriV0 {
}
pub fn object_ref(obj_ref: &ObjectRef) -> String {
format!("{DID_PREFIX}{}", obj_ref.nuri())
format!("{DID_PREFIX}:{}", obj_ref.object_nuri())
}
pub fn token(token: &Digest) -> String {
@ -283,80 +286,99 @@ impl NuriV0 {
}
}
pub fn new_from(from: &String) -> Result<Self, NgError> {
let c = RE_FILE_READ_CAP.captures(from);
let c = RE_REPO_O.captures(from);
if c.is_some()
&& c.as_ref().unwrap().get(1).is_some()
&& c.as_ref().unwrap().get(2).is_some()
{
if c.is_some() && c.as_ref().unwrap().get(1).is_some() {
let cap = c.unwrap();
let j = cap.get(1).unwrap().as_str();
let k = cap.get(2).unwrap().as_str();
let id = decode_digest(j)?;
let key = decode_sym_key(k)?;
let o = cap.get(1).unwrap().as_str();
let repo_id = decode_key(o)?;
Ok(Self {
identity: None,
target: NuriTargetV0::PrivateStore,
target: NuriTargetV0::Repo(repo_id),
entire_store: false,
object: Some(id),
object: None,
branch: None,
overlay: None,
access: vec![NgAccessV0::Key(key)],
access: vec![],
topic: None,
locator: vec![],
})
} else {
let c = RE_REPO.captures(from);
let c = RE_FILE_READ_CAP.captures(from);
if c.is_some()
&& c.as_ref().unwrap().get(1).is_some()
&& c.as_ref().unwrap().get(2).is_some()
{
let cap = c.unwrap();
let o = cap.get(1).unwrap().as_str();
let v = cap.get(2).unwrap().as_str();
let repo_id = decode_key(o)?;
let overlay_id = decode_overlayid(v)?;
let j = cap.get(1).unwrap().as_str();
let k = cap.get(2).unwrap().as_str();
let id = decode_digest(j)?;
let key = decode_sym_key(k)?;
Ok(Self {
identity: None,
target: NuriTargetV0::Repo(repo_id),
target: NuriTargetV0::PrivateStore,
entire_store: false,
object: None,
object: Some(id),
branch: None,
overlay: Some(overlay_id.into()),
access: vec![],
overlay: None,
access: vec![NgAccessV0::Key(key)],
topic: None,
locator: vec![],
})
} else {
let c = RE_BRANCH.captures(from);
let c = RE_REPO.captures(from);
if c.is_some()
&& c.as_ref().unwrap().get(1).is_some()
&& c.as_ref().unwrap().get(2).is_some()
&& c.as_ref().unwrap().get(3).is_some()
{
let cap = c.unwrap();
let o = cap.get(1).unwrap().as_str();
let v = cap.get(2).unwrap().as_str();
let b = cap.get(3).unwrap().as_str();
let repo_id = decode_key(o)?;
let overlay_id = decode_overlayid(v)?;
let branch_id = decode_key(b)?;
Ok(Self {
identity: None,
target: NuriTargetV0::Repo(repo_id),
entire_store: false,
object: None,
branch: Some(TargetBranchV0::BranchId(branch_id)),
branch: None,
overlay: Some(overlay_id.into()),
access: vec![],
topic: None,
locator: vec![],
})
} else {
Err(NgError::InvalidNuri)
let c = RE_BRANCH.captures(from);
if c.is_some()
&& c.as_ref().unwrap().get(1).is_some()
&& c.as_ref().unwrap().get(2).is_some()
&& c.as_ref().unwrap().get(3).is_some()
{
let cap = c.unwrap();
let o = cap.get(1).unwrap().as_str();
let v = cap.get(2).unwrap().as_str();
let b = cap.get(3).unwrap().as_str();
let repo_id = decode_key(o)?;
let overlay_id = decode_overlayid(v)?;
let branch_id = decode_key(b)?;
Ok(Self {
identity: None,
target: NuriTargetV0::Repo(repo_id),
entire_store: false,
object: None,
branch: Some(TargetBranchV0::BranchId(branch_id)),
overlay: Some(overlay_id.into()),
access: vec![],
topic: None,
locator: vec![],
})
} else {
Err(NgError::InvalidNuri)
}
}
}
}
@ -535,6 +557,8 @@ pub enum DiscreteUpdate {
#[serde(with = "serde_bytes")]
YMap(Vec<u8>),
#[serde(with = "serde_bytes")]
YArray(Vec<u8>),
#[serde(with = "serde_bytes")]
YXml(Vec<u8>),
#[serde(with = "serde_bytes")]
YText(Vec<u8>),
@ -599,6 +623,8 @@ pub enum DiscretePatch {
#[serde(with = "serde_bytes")]
YMap(Vec<u8>),
#[serde(with = "serde_bytes")]
YArray(Vec<u8>),
#[serde(with = "serde_bytes")]
YXml(Vec<u8>),
#[serde(with = "serde_bytes")]
YText(Vec<u8>),
@ -623,6 +649,8 @@ pub enum DiscreteState {
#[serde(with = "serde_bytes")]
YMap(Vec<u8>),
#[serde(with = "serde_bytes")]
YArray(Vec<u8>),
#[serde(with = "serde_bytes")]
YXml(Vec<u8>),
#[serde(with = "serde_bytes")]
YText(Vec<u8>),
@ -687,8 +715,8 @@ pub enum OtherPatch {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AppPatch {
pub commit_id: ObjectId,
pub commit_info: CommitInfo,
pub commit_id: String,
pub commit_info: CommitInfoJs,
// or graph, or discrete, or both, or other.
pub graph: Option<GraphPatch>,
pub discrete: Option<DiscretePatch>,
@ -708,9 +736,54 @@ pub struct FileMetaV0 {
pub size: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AppTabStoreInfo {
pub overlay: Option<String>, //+
pub has_outer: Option<String>,
pub store_type: Option<String>, //+
pub readcap: Option<String>,
pub is_member: Option<String>,
pub inner: Option<String>,
pub title: Option<String>,
pub icon: Option<String>,
pub description: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AppTabDocInfo {
pub nuri: Option<String>, //+
pub is_store: Option<bool>, //+
pub is_member: Option<String>, //+
pub title: Option<String>,
pub icon: Option<String>,
pub description: Option<String>,
pub authors: Option<Vec<String>>,
pub inbox: Option<String>,
pub can_edit: Option<bool>, //+
//TODO stream
//TODO live_editors
//TODO branches
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AppTabBranchInfo {
pub id: Option<String>, //+
pub readcap: Option<String>, //+
pub comment_branch: Option<String>,
pub class: Option<String>, //+
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AppTabInfo {
pub branch: Option<AppTabBranchInfo>,
pub doc: Option<AppTabDocInfo>,
pub store: Option<AppTabStoreInfo>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AppResponseV0 {
SessionStart(AppSessionStartResponse),
TabInfo(AppTabInfo),
State(AppState),
Patch(AppPatch),
History(AppHistory),

@ -21,6 +21,7 @@ use async_std::sync::Mutex;
use async_std::sync::{Arc, RwLock};
use either::Either;
use futures::channel::mpsc;
use futures::channel::mpsc::UnboundedSender;
use futures::SinkExt;
use once_cell::sync::Lazy;
@ -115,12 +116,15 @@ pub struct ServerConfig {
pub bootstrap: BootstrapContent,
}
#[doc(hidden)]
#[async_trait::async_trait]
pub trait ILocalBroker: Send + Sync + EActor {
async fn deliver(&mut self, event: Event, overlay: OverlayId, user: UserId);
async fn user_disconnected(&mut self, user_id: UserId);
pub enum LocalBrokerMessage {
Deliver {
event: Event,
overlay: OverlayId,
user: UserId,
},
Disconnected {
user_id: UserId,
},
}
pub static BROKER: Lazy<Arc<RwLock<Broker>>> = Lazy::new(|| Arc::new(RwLock::new(Broker::new())));
@ -139,7 +143,7 @@ pub struct Broker {
server_broker: Option<Arc<RwLock<dyn IServerBroker + Send + Sync>>>,
//local_broker: Option<Box<dyn ILocalBroker + Send + Sync + 'a>>,
local_broker: Option<Arc<RwLock<dyn ILocalBroker>>>,
local_broker: Option<UnboundedSender<LocalBrokerMessage>>,
#[cfg(not(target_arch = "wasm32"))]
listeners: HashMap<String, ListenerInfo>,
@ -197,9 +201,9 @@ impl Broker {
}
#[doc(hidden)]
pub fn set_local_broker(&mut self, broker: Arc<RwLock<dyn ILocalBroker>>) {
pub fn set_local_broker(&mut self, pump: UnboundedSender<LocalBrokerMessage>) {
//log_debug!("set_local_broker");
self.local_broker = Some(broker);
self.local_broker = Some(pump);
}
pub fn set_server_config(&mut self, config: ServerConfig) {
@ -246,12 +250,14 @@ impl Broker {
// }
//Option<Arc<RwLock<dyn ILocalBroker>>>,
pub(crate) fn get_local_broker(&self) -> Result<Arc<RwLock<dyn ILocalBroker>>, ProtocolError> {
Ok(Arc::clone(
self.local_broker
.as_ref()
.ok_or(ProtocolError::NoLocalBrokerFound)?,
))
pub(crate) fn get_local_broker(
&self,
) -> Result<UnboundedSender<LocalBrokerMessage>, ProtocolError> {
Ok(self
.local_broker
.as_ref()
.ok_or(ProtocolError::NoLocalBrokerFound)?
.clone())
}
#[cfg(not(target_arch = "wasm32"))]
@ -1020,7 +1026,7 @@ impl Broker {
_peer_pubkey: PubKey,
remote_peer_id: [u8; 32],
config: StartConfig,
local_broker: Arc<async_std::sync::RwLock<dyn ILocalBroker>>,
mut local_broker: UnboundedSender<LocalBrokerMessage>,
) -> ResultSend<()> {
async move {
let res = join.next().await;
@ -1046,7 +1052,9 @@ impl Broker {
// if all attempts fail :
if let Some(user) = config.get_user() {
local_broker.write().await.user_disconnected(user).await;
let _ = local_broker
.send(LocalBrokerMessage::Disconnected { user_id: user })
.await;
}
} else {
log_debug!("REMOVED");

@ -38,6 +38,7 @@ use ng_repo::utils::verify;
use crate::actor::{Actor, SoS};
use crate::actors::*;
use crate::broker::LocalBrokerMessage;
use crate::broker::{ClientPeerId, BROKER};
use crate::types::*;
use crate::utils::*;
@ -1032,13 +1033,15 @@ impl NoiseFSM {
None => {
if let ProtocolMessage::ClientMessage(cm) = msg {
if let Some((event, overlay)) = cm.forwarded_event() {
BROKER
let _ = BROKER
.read()
.await
.get_local_broker()?
.write()
.await
.deliver(event, overlay, self.user_id()?)
.send(LocalBrokerMessage::Deliver {
event,
overlay,
user: self.user_id()?,
})
.await;
return Ok(StepReply::NONE);
}

@ -46,6 +46,7 @@ quick-xml = ">=0.29, <0.32"
memchr = "2.5"
peg = "0.8"
base64-url = "2.0.0"
ng-repo = { path = "../ng-repo", version = "0.1.0-preview.1" }
[target.'cfg(all(not(target_family = "wasm"),not(docsrs)))'.dependencies]
libc = "0.2"

@ -52,6 +52,8 @@ use std::{io, thread};
use self::numeric_encoder::EncodedTriple;
use ng_repo::log::*;
mod backend;
mod binary_encoder;
mod error;
@ -715,6 +717,7 @@ impl StorageReader {
graph_name_string: &String,
iri_id: Option<StrHash>,
) -> Result<MatchBy, StorageError> {
//log_info!("{}", graph_name_string);
let graph_name_string_len = graph_name_string.len();
// TODO: deal with <:v> and <:v:n>
if graph_name_string_len < 100 {
@ -725,6 +728,7 @@ impl StorageReader {
let (repo_part, other_part) = graph_name_string.split_at(100);
let c = RE_REPO.captures(repo_part);
//log_info!("{:?}", c);
let (_repo, overlay) = if c.is_some()
&& c.as_ref().unwrap().get(1).is_some()
&& c.as_ref().unwrap().get(2).is_some()
@ -746,10 +750,8 @@ impl StorageReader {
// we check that did:ng:o:v is present in dataset
self.get_str(&ov_hash)?.ok_or::<StorageError>(
CorruptionError::msg(
"Invalid graph_name (did:ng:o:v part not found) in parse_graph_name",
)
.into(),
CorruptionError::msg(format!("Graph {} not found in dataset", graph_name_string))
.into(),
)?;
Ok(if graph_name_string_len > 100 && iri_id.is_some() {
@ -948,7 +950,7 @@ impl StorageReader {
self.aggregate_causal_past(aggregate, next, cache)?;
}
} else {
// we add the last one (that doesnt have past) as it must be the first commit in branch that hold content
// we add the last one (that doesnt have past) as it must be the first commit in branch that holds content
aggregate.insert(current, false);
}
}
@ -1963,7 +1965,7 @@ pub const COMMIT_PREFIX: u8 = 1;
#[inline]
fn is_added(val: u8) -> bool {
(val & MASK_ADDED) == 1
(val & MASK_ADDED) == MASK_ADDED
}
#[inline]

@ -300,18 +300,29 @@ impl Store {
graph_name: Option<GraphNameRef<'_>>,
) -> QuadIter {
let reader = self.storage.snapshot();
let match_by = if graph_name.is_some() {
if let GraphName::NamedNode(nn) = graph_name.unwrap().into_owned() {
match reader.parse_graph_name(nn.as_string(), None) {
Err(e) => {
return QuadIter {
iter: OneErrorQuadIter::new_boxed(e),
reader,
}
}
Ok(o) => Some(o),
}
} else {
panic!("invalid graph name");
}
} else {
None
};
QuadIter {
iter: reader.quads_for_pattern(
subject.map(EncodedTerm::from).as_ref(),
predicate.map(EncodedTerm::from).as_ref(),
object.map(EncodedTerm::from).as_ref(),
graph_name.map(|graph_name_ref| {
if let GraphName::NamedNode(nn) = graph_name_ref.into_owned() {
reader.parse_graph_name(nn.as_string(), None).unwrap() //TODO improve error mng (remove unwrap)
} else {
panic!("invalid graph name");
}
}),
match_by,
),
reader,
}
@ -1202,18 +1213,29 @@ impl<'a> Transaction<'a> {
graph_name: Option<GraphNameRef<'_>>,
) -> QuadIter {
let reader = self.writer.reader();
let match_by = if graph_name.is_some() {
if let GraphName::NamedNode(nn) = graph_name.unwrap().into_owned() {
match reader.parse_graph_name(nn.as_string(), None) {
Err(e) => {
return QuadIter {
iter: OneErrorQuadIter::new_boxed(e),
reader,
}
}
Ok(o) => Some(o),
}
} else {
panic!("invalid graph name");
}
} else {
None
};
QuadIter {
iter: reader.quads_for_pattern(
subject.map(EncodedTerm::from).as_ref(),
predicate.map(EncodedTerm::from).as_ref(),
object.map(EncodedTerm::from).as_ref(),
graph_name.map(|graph_name_ref| {
if let GraphName::NamedNode(nn) = graph_name_ref.into_owned() {
reader.parse_graph_name(nn.as_string(), None).unwrap() //TODO improve error mng (remove unwrap)
} else {
panic!("invalid graph name");
}
}),
match_by,
),
reader,
}
@ -1705,6 +1727,25 @@ impl Iterator for QuadIter {
}
}
pub struct OneErrorQuadIter {
err: Option<StorageError>,
}
impl OneErrorQuadIter {
pub fn new_boxed(err: StorageError) -> Box<OneErrorQuadIter> {
Box::new(Self { err: Some(err) })
}
}
impl Iterator for OneErrorQuadIter {
type Item = Result<EncodedQuad, StorageError>;
fn next(&mut self) -> Option<Self::Item> {
match self.err.take() {
Some(e) => Some(Err(e)),
None => None,
}
}
}
/// An iterator returning the graph names contained in a [`Store`].
pub struct GraphNameIter {
iter: DecodingGraphIterator,

@ -37,7 +37,7 @@ impl BranchV0 {
let topic = topic_priv.to_pub();
BranchV0 {
id,
content_type: BranchContentType::None,
crdt: BranchCrdt::None,
repo,
root_branch_readcap_id,
topic,

@ -208,13 +208,18 @@ impl EventV0 {
let repo_id = repo.id;
let store = Arc::clone(&repo.store);
let branch = repo.branch(branch_id)?;
let topic_id = &branch.topic;
let topic_id = &branch.topic.unwrap();
let topic_priv_key = branch
.topic_priv_key
.as_ref()
.ok_or(NgError::PermissionDenied)?;
let publisher_pubkey = publisher.to_pub();
let key = Self::derive_key(&repo_id, branch_id, &branch.read_cap.key, &publisher_pubkey);
let key = Self::derive_key(
&repo_id,
branch_id,
&branch.read_cap.as_ref().unwrap().key,
&publisher_pubkey,
);
let commit_key = commit.key().unwrap();
let mut encrypted_commit_key = Vec::from(commit_key.slice());
let mut nonce = seq.to_le_bytes().to_vec();
@ -274,7 +279,7 @@ impl EventV0 {
&repo.store,
&repo.id,
&branch.id,
&branch.read_cap.key,
&branch.read_cap.as_ref().unwrap().key,
true,
)
}

@ -93,11 +93,17 @@ pub struct BranchInfo {
pub branch_type: BranchType,
pub topic: TopicId,
pub crdt: BranchCrdt,
pub topic: Option<TopicId>,
pub topic_priv_key: Option<BranchWriteCapSecret>,
pub read_cap: ReadCap,
pub read_cap: Option<ReadCap>,
pub fork_of: Option<BranchId>,
pub merged_in: Option<BranchId>,
pub current_heads: Vec<ObjectRef>,
@ -549,7 +555,7 @@ impl Repo {
pub fn overlay_branch_read_cap(&self) -> Option<&ReadCap> {
match self.overlay_branch() {
Some(bi) => Some(&bi.read_cap),
Some(bi) => Some(bi.read_cap.as_ref().unwrap()),
None => self.read_cap.as_ref(), // this is for private stores that don't have an overlay branch
}
}

@ -178,6 +178,7 @@ impl Store {
fn create_branch(
&self,
branch_type: BranchType,
crdt: BranchCrdt,
creator: &UserId,
creator_priv_key: &PrivKey,
repo_pub_key: BranchId,
@ -193,7 +194,7 @@ impl Store {
let branch_commit_body = CommitBody::V0(CommitBodyV0::Branch(Branch::V0(BranchV0 {
id: branch_pub_key,
content_type: BranchContentType::None,
crdt: crdt.clone(),
repo: repository_commit_ref,
root_branch_readcap_id,
topic: branch_topic_pub_key,
@ -227,9 +228,12 @@ impl Store {
let add_branch_commit_body =
CommitBody::V0(CommitBodyV0::AddBranch(AddBranch::V0(AddBranchV0 {
branch_type: branch_type.clone(),
topic_id: branch_topic_pub_key,
branch_id: branch_pub_key,
branch_read_cap: branch_read_cap.clone(),
topic_id: Some(branch_topic_pub_key),
branch_read_cap: Some(branch_read_cap.clone()),
fork_of: None,
merged_in: None,
crdt: crdt.clone(),
})));
let add_branch_commit = Commit::new_with_body_acks_deps_and_save(
@ -252,9 +256,12 @@ impl Store {
let branch_info = BranchInfo {
id: branch_pub_key,
branch_type,
topic: branch_topic_pub_key,
topic: Some(branch_topic_pub_key),
topic_priv_key: Some(branch_topic_priv_key),
read_cap: branch_read_cap,
read_cap: Some(branch_read_cap),
fork_of: None,
merged_in: None,
crdt,
current_heads: vec![],
commits_nbr: 0,
};
@ -370,6 +377,7 @@ impl Store {
let (main_branch_commit, main_add_branch_commit, main_branch_info) =
self.as_ref().create_branch(
BranchType::Main,
BranchCrdt::Graph("data:container".to_string()),
creator,
creator_priv_key,
repo_pub_key,
@ -390,6 +398,7 @@ impl Store {
let (store_branch_commit, store_add_branch_commit, store_branch_info) =
self.as_ref().create_branch(
BranchType::Store,
BranchCrdt::None,
creator,
creator_priv_key,
repo_pub_key,
@ -413,6 +422,7 @@ impl Store {
} else {
BranchType::Overlay
},
BranchCrdt::None,
creator,
creator_priv_key,
repo_pub_key,
@ -451,13 +461,13 @@ 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.id];
let mut signed_commits = 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![
oou_add_branch.id().unwrap(),
store_branch.read_cap.id,
oou_branch.read_cap.id,
store_branch.read_cap.as_ref().unwrap().id,
oou_branch.read_cap.as_ref().unwrap().id,
]);
} else {
signed_commits.push(main_add_branch_commit.id().unwrap());
@ -573,8 +583,8 @@ impl Store {
creator,
*branch_id,
QuorumType::IamTheSignature,
vec![branch_info.read_cap.clone()],
vec![branch_info.read_cap.clone()],
vec![branch_info.read_cap.as_ref().unwrap().clone()],
vec![branch_info.read_cap.as_ref().unwrap().clone()],
sync_sig_commit_body.clone(),
&self,
)?;
@ -606,9 +616,12 @@ impl Store {
let root_branch = BranchInfo {
id: repo_pub_key.clone(),
branch_type: BranchType::Root,
topic: topic_pub_key,
topic: Some(topic_pub_key),
topic_priv_key: Some(topic_priv_key),
read_cap: root_branch_readcap.clone(),
read_cap: Some(root_branch_readcap.clone()),
fork_of: None,
merged_in: None,
crdt: BranchCrdt::None,
current_heads: vec![sync_sig_on_root_branch_commit_ref],
commits_nbr: 0,
};

@ -463,8 +463,13 @@ impl BlockRef {
BlockRef { id, key }
}
pub fn nuri(&self) -> String {
format!(":j:{}:k:{}", self.id, self.key)
pub fn object_nuri(&self) -> String {
format!("j:{}:k:{}", self.id, self.key)
}
pub fn readcap_nuri(&self) -> String {
let ser = serde_bare::to_vec(self).unwrap();
format!("r:{}", base64_url::encode(&ser))
}
pub fn tokenize(&self) -> Digest {
@ -627,33 +632,51 @@ pub enum StoreOverlayV0 {
Dialog(Digest),
}
impl fmt::Display for StoreOverlayV0 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "StoreOverlay V0 ")?;
match self {
StoreOverlayV0::PublicStore(k) => writeln!(f, "PublicStore: {}", k),
StoreOverlayV0::ProtectedStore(k) => writeln!(f, "ProtectedStore: {}", k),
StoreOverlayV0::PrivateStore(k) => writeln!(f, "PrivateStore: {}", k),
StoreOverlayV0::Group(k) => writeln!(f, "Group: {}", k),
StoreOverlayV0::Dialog(k) => writeln!(f, "Dialog: {}", k),
}
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum StoreOverlay {
V0(StoreOverlayV0),
Own(BranchId), // The repo is a store, so the overlay can be derived from its own ID. In this case, the branchId of the `overlay` branch is entered here.
OwnV0(StoreOverlayV0), // The repo is a store, so the overlay can be derived from its own ID. In this case, the branchId of the `overlay` branch is entered here as PubKey of the StoreOverlayV0 variants.
}
impl fmt::Display for StoreOverlay {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::V0(v0) => {
write!(f, "StoreOverlay V0 ")?;
match v0 {
StoreOverlayV0::PublicStore(k) => writeln!(f, "PublicStore: {}", k),
StoreOverlayV0::ProtectedStore(k) => writeln!(f, "ProtectedStore: {}", k),
StoreOverlayV0::PrivateStore(k) => writeln!(f, "PrivateStore: {}", k),
StoreOverlayV0::Group(k) => writeln!(f, "Group: {}", k),
StoreOverlayV0::Dialog(k) => writeln!(f, "Dialog: {}", k),
}
}
Self::Own(b) => writeln!(f, "Own: {}", b),
Self::V0(v0) => writeln!(f, "{}", v0),
Self::OwnV0(v0) => writeln!(f, "Own: {}", v0),
}
}
}
impl StoreOverlay {
pub fn from_store_repo(overlay_branch: BranchId) -> StoreOverlay {
StoreOverlay::Own(overlay_branch)
pub fn from_store_repo(store_repo: &StoreRepo, overlay_branch: BranchId) -> StoreOverlay {
match store_repo {
StoreRepo::V0(v0) => match v0 {
StoreRepoV0::PublicStore(_id) => {
StoreOverlay::V0(StoreOverlayV0::PublicStore(overlay_branch))
}
StoreRepoV0::ProtectedStore(_id) => {
StoreOverlay::V0(StoreOverlayV0::ProtectedStore(overlay_branch))
}
StoreRepoV0::PrivateStore(_id) => {
StoreOverlay::V0(StoreOverlayV0::PrivateStore(overlay_branch))
}
StoreRepoV0::Group(_id) => StoreOverlay::V0(StoreOverlayV0::Group(overlay_branch)),
StoreRepoV0::Dialog((_, d)) => StoreOverlay::V0(StoreOverlayV0::Dialog(d.clone())),
},
}
}
pub fn overlay_id_for_read_purpose(&self) -> OverlayId {
@ -663,7 +686,7 @@ impl StoreOverlay {
| StoreOverlay::V0(StoreOverlayV0::PrivateStore(id))
| StoreOverlay::V0(StoreOverlayV0::Group(id)) => OverlayId::outer(id),
StoreOverlay::V0(StoreOverlayV0::Dialog(d)) => OverlayId::Inner(d.clone().to_slice()),
StoreOverlay::Own(_) => unimplemented!(),
StoreOverlay::OwnV0(_) => unimplemented!(),
}
}
@ -679,7 +702,7 @@ impl StoreOverlay {
OverlayId::inner(id, &store_overlay_branch_readcap_secret)
}
StoreOverlay::V0(StoreOverlayV0::Dialog(d)) => OverlayId::Inner(d.clone().to_slice()),
StoreOverlay::Own(_) => unimplemented!(),
StoreOverlay::OwnV0(_) => unimplemented!(),
}
}
}
@ -739,6 +762,19 @@ impl fmt::Display for StoreRepo {
}
impl StoreRepo {
pub fn store_type_for_app(&self) -> String {
match self {
Self::V0(v0) => match v0 {
StoreRepoV0::PublicStore(_) => "public",
StoreRepoV0::ProtectedStore(_) => "protected",
StoreRepoV0::PrivateStore(_) => "private",
StoreRepoV0::Group(_) => "group",
StoreRepoV0::Dialog(_) => "dialog",
},
}
.to_string()
}
pub fn repo_id(&self) -> &RepoId {
match self {
Self::V0(v0) => match v0 {
@ -1292,17 +1328,59 @@ pub enum Quorum {
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum BranchContentType {
Graph,
YMap,
YXml,
YText,
Automerge,
pub enum BranchCrdt {
Graph(String),
YMap(String),
YArray(String),
YXml(String),
YText(String),
Automerge(String),
Elmer(String),
//Rdfs,
//Owl,
//Shacl,
//Shex,
None, // this is used by Overlay and User BranchTypes
None, // this is used by Overlay, Store and User BranchTypes
}
impl BranchCrdt {
pub fn name(&self) -> String {
match self {
BranchCrdt::Graph(_) => "Graph",
BranchCrdt::YMap(_) => "YMap",
BranchCrdt::YArray(_) => "YArray",
BranchCrdt::YXml(_) => "YXml",
BranchCrdt::YText(_) => "YText",
BranchCrdt::Automerge(_) => "Automerge",
BranchCrdt::Elmer(_) => "Elmer",
BranchCrdt::None => panic!("BranchCrdt::None does not have a name"),
}
.to_string()
}
pub fn class(&self) -> &String {
match self {
BranchCrdt::Graph(c)
| BranchCrdt::YMap(c)
| BranchCrdt::YArray(c)
| BranchCrdt::YXml(c)
| BranchCrdt::YText(c)
| BranchCrdt::Automerge(c)
| BranchCrdt::Elmer(c) => c,
BranchCrdt::None => panic!("BranchCrdt::None does not have a class"),
}
}
pub fn from(name: String, class: String) -> Self {
match name.as_str() {
"Graph" => BranchCrdt::Graph(class),
"YMap" => BranchCrdt::YMap(class),
"YArray" => BranchCrdt::YArray(class),
"YXml" => BranchCrdt::YXml(class),
"YText" => BranchCrdt::YText(class),
"Automerge" => BranchCrdt::Automerge(class),
"Elmer" => BranchCrdt::Elmer(class),
_ => panic!("Invalid CRDT name"),
}
}
}
/// Branch definition
@ -1318,7 +1396,7 @@ pub struct BranchV0 {
/// Branch public key ID
pub id: PubKey,
pub content_type: BranchContentType,
pub crdt: BranchCrdt,
/// Reference to the repository commit
pub repo: ObjectRef,
@ -1383,8 +1461,7 @@ pub enum BranchType {
Comments,
BackLinks,
Context,
Ontology,
//Ontology,
Transactional, // this could have been called OtherTransactional, but for the sake of simplicity, we use Transactional for any branch that is not the Main one.
Root, // only used for BranchInfo
//Unknown, // only used temporarily when loading a branch info from commits (Branch commit, then AddBranch commit)
@ -1416,7 +1493,7 @@ impl fmt::Display for BranchType {
Self::Comments => "Comments",
Self::BackLinks => "BackLinks",
Self::Context => "Context",
Self::Ontology => "Ontology",
//Self::Ontology => "Ontology",
//Self::Unknown => "==unknown==",
}
)
@ -1428,17 +1505,24 @@ impl fmt::Display for BranchType {
/// DEPS: if update branch: previous AddBranch commit of the same branchId
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct AddBranchV0 {
/// the new topic_id (will be needed immediately by future readers
/// in order to subscribe to the pub/sub). should be identical to the one in the Branch definition
pub topic_id: TopicId,
// the new topic_id (will be needed immediately by future readers
// in order to subscribe to the pub/sub). should be identical to the one in the Branch definition.
// None if merged_in
pub topic_id: Option<TopicId>,
// the new branch definition commit
// (we need the ObjectKey in order to open the pub/sub Event)
// None if merged_in
pub branch_read_cap: Option<ReadCap>,
pub crdt: BranchCrdt,
pub branch_id: BranchId,
pub branch_type: BranchType,
// the new branch definition commit
// (we need the ObjectKey in order to open the pub/sub Event)
pub branch_read_cap: ReadCap,
pub fork_of: Option<BranchId>,
pub merged_in: Option<BranchId>,
}
impl fmt::Display for AddBranch {
@ -1446,8 +1530,23 @@ impl fmt::Display for AddBranch {
match self {
Self::V0(v0) => {
writeln!(f, "V0 {}", v0.branch_type)?;
writeln!(f, "topic_id: {}", v0.topic_id)?;
writeln!(f, "branch_read_cap: {}", v0.branch_read_cap)?;
writeln!(f, "branch_id: {}", v0.branch_id)?;
if v0.topic_id.is_some() {
writeln!(f, "topic_id: {}", v0.topic_id.as_ref().unwrap())?;
}
if v0.branch_read_cap.is_some() {
writeln!(
f,
"branch_read_cap: {}",
v0.branch_read_cap.as_ref().unwrap()
)?;
}
if v0.fork_of.is_some() {
writeln!(f, "fork_of: {}", v0.fork_of.as_ref().unwrap())?;
}
if v0.merged_in.is_some() {
writeln!(f, "merged_in: {}", v0.merged_in.as_ref().unwrap())?;
}
Ok(())
}
}

@ -23,7 +23,6 @@ crate-type = ["cdylib"]
serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0"
serde_bytes = "0.11.7"
serde_json = "1.0"
async-std = { version = "1.12.0", features = ["attributes","unstable"] }
once_cell = "1.17.1"
getrandom = { version = "0.1.1", features = ["wasm-bindgen"] }

@ -21,10 +21,9 @@ use std::sync::Arc;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use serde_json::json;
// use js_sys::Reflect;
use async_std::stream::StreamExt;
use js_sys::Array;
use js_sys::{Array, Object};
use oxrdf::Triple;
use sys_locale::get_locales;
use wasm_bindgen::prelude::*;
@ -303,15 +302,19 @@ pub async fn sparql_query(session_id: JsValue, sparql: String) -> Result<JsValue
}
}
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen]
pub async fn sparql_update(session_id: JsValue, sparql: String) -> Result<(), String> {
pub async fn sparql_update(
session_id: JsValue,
nuri: String,
sparql: String,
) -> 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(|_| "Deserialization error of Nuri".to_string())?;
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_write_query(),
nuri: NuriV0::new_private_store_target(),
nuri,
payload: Some(AppRequestPayload::new_sparql_query(sparql)),
session_id,
});
@ -765,7 +768,50 @@ pub async fn app_request_stream(
callback: js_sys::Function,
) -> ResultSend<()> {
while let Some(app_response) = reader.next().await {
let app_response = nextgraph::verifier::prepare_app_response_for_js(app_response)?;
//let mut graph_triples_js: Option<JsValue> = None;
// if let AppResponse::V0(AppResponseV0::State(AppState { ref mut graph, .. })) =
// app_response
// {
// if graph.is_some() {
// let graph_state = graph.take().unwrap();
// let triples: Vec<Triple> = serde_bare::from_slice(&graph_state.triples)
// .map_err(|_| "Deserialization error of graph".to_string())?;
// let results = Array::new();
// for triple in triples {
// results.push(&JsQuad::from(triple).into());
// }
// let list:JsValue = results.into();
// list.
// };
// };
let response_js = serde_wasm_bindgen::to_value(&app_response).unwrap();
// if let Some(graph_triples) = graph_triples_js {
// let response: Object = response_js.try_into().map_err(|_| {
// "Error while adding triples to AppResponse.V0.State".to_string()
// })?;
// let v0 = Object::get_own_property_descriptor(&response, &JsValue::from_str("V0"));
// let v0_obj: Object = v0.try_into().map_err(|_| {
// "Error while adding triples to AppResponse.V0.State".to_string()
// })?;
// let state =
// Object::get_own_property_descriptor(&v0_obj, &JsValue::from_str("State"));
// let state_obj: Object = state.try_into().map_err(|_| {
// "Error while adding triples to AppResponse.V0.State".to_string()
// })?;
// let kv = Array::new_with_length(2);
// kv.push(&JsValue::from_str("triples"));
// kv.push(&graph_triples);
// let entries = Array::new_with_length(1);
// entries.push(&kv.into());
// let graph = Object::from_entries(&entries).map_err(|_| {
// "Error while creating the triples for AppResponse.V0.State.graph".to_string()
// })?;
// let response =
// Object::define_property(&state_obj, &JsValue::from_str("graph"), &graph);
// response_js = response.into();
// };
let this = JsValue::null();
match callback.call1(&this, &response_js) {
Ok(jsval) => {
@ -1024,7 +1070,7 @@ pub async fn file_put_to_private_store(
.map_err(|e| e.as_string().unwrap())?;
let reference = serde_wasm_bindgen::from_value::<ObjectRef>(reference)
.map_err(|_| "Deserialization error of reference".to_string())?;
let nuri = format!("did:ng{}", reference.nuri());
let nuri = format!("did:ng:{}", reference.object_nuri());
Ok(nuri)
}
@ -1098,10 +1144,10 @@ pub async fn doc_fetch_private_subscribe() -> Result<JsValue, String> {
}
#[wasm_bindgen]
pub async fn doc_fetch_repo_subscribe(repo_id: String) -> Result<JsValue, String> {
pub async fn doc_fetch_repo_subscribe(repo_o: String) -> Result<JsValue, String> {
let request = AppRequest::new(
AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)),
NuriV0::new_repo_target_from_string(repo_id).map_err(|e| e.to_string())?,
NuriV0::new_from(&repo_o).map_err(|e| e.to_string())?,
None,
);
Ok(serde_wasm_bindgen::to_value(&request).unwrap())

@ -22,6 +22,7 @@ testing = []
serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0"
serde_bytes = "0.11.7"
serde_json = "1.0"
rand = { version = "0.7", features = ["getrandom"] }
web-time = "0.2.0"
either = "1.8.1"

@ -110,9 +110,12 @@ impl CommitVerifier for RootBranch {
let root_branch = BranchInfo {
id: root_branch.id.clone(),
branch_type: BranchType::Root,
topic: root_branch.topic,
topic: Some(root_branch.topic),
topic_priv_key,
read_cap: reference.clone(),
read_cap: Some(reference.clone()),
fork_of: None,
crdt: BranchCrdt::None,
merged_in: None,
current_heads: vec![reference.clone()],
commits_nbr: 1,
};
@ -180,7 +183,7 @@ impl CommitVerifier for Branch {
let reference = commit.reference().unwrap();
let branch_info = repo.branch_mut(&branch.id)?;
if branch_info.read_cap != reference {
if branch_info.read_cap.as_ref().unwrap() != &reference {
return Err(VerifierError::InvalidBranch);
}
branch_info.topic_priv_key = topic_priv_key;
@ -248,12 +251,17 @@ impl CommitVerifier for AddBranch {
return Err(VerifierError::InvalidBranch);
}
// TODO fetch the readcap and verify that crdt and other infos in Branch definition are the same as in AddBranch commit
let branch_info = BranchInfo {
id: v0.branch_id,
branch_type: v0.branch_type.clone(),
topic: v0.topic_id,
topic_priv_key: None,
read_cap: v0.branch_read_cap.clone(),
fork_of: v0.fork_of,
merged_in: v0.merged_in,
crdt: v0.crdt.clone(),
current_heads: vec![],
commits_nbr: 0,
};
@ -467,8 +475,8 @@ impl CommitVerifier for AddFile {
.push_app_response(
branch_id,
AppResponse::V0(AppResponseV0::Patch(AppPatch {
commit_id,
commit_info: commit.as_info(repo),
commit_id: commit_id.to_string(),
commit_info: (&commit.as_info(repo)).into(),
graph: None,
discrete: None,
other: Some(OtherPatch::FileAdd(filename)),

@ -16,7 +16,7 @@ use ng_oxigraph::oxigraph::storage_ng::numeric_encoder::{EncodedQuad, EncodedTer
use ng_oxigraph::oxigraph::storage_ng::*;
use serde::{Deserialize, Serialize};
use ng_net::app_protocol::{NuriV0, TargetBranchV0};
use ng_net::app_protocol::*;
use ng_oxigraph::oxrdf::{GraphName, GraphNameRef, NamedNode, Quad, Triple, TripleRef};
use ng_repo::errors::VerifierError;
use ng_repo::log::*;
@ -31,12 +31,23 @@ pub struct GraphTransaction {
pub removes: Vec<Triple>,
}
impl GraphTransaction {
fn as_patch(&self) -> GraphPatch {
GraphPatch {
inserts: serde_bare::to_vec(&self.inserts).unwrap(),
removes: serde_bare::to_vec(&self.removes).unwrap(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DiscreteTransaction {
/// A yrs::Update
#[serde(with = "serde_bytes")]
YMap(Vec<u8>),
#[serde(with = "serde_bytes")]
YArray(Vec<u8>),
#[serde(with = "serde_bytes")]
YXml(Vec<u8>),
#[serde(with = "serde_bytes")]
YText(Vec<u8>),
@ -69,6 +80,7 @@ struct BranchUpdateInfo {
previous_heads: HashSet<ObjectId>,
commit_id: ObjectId,
transaction: GraphTransaction,
commit_info: CommitInfoJs,
}
impl Verifier {
@ -210,24 +222,27 @@ impl Verifier {
let repo = self.get_repo(repo_id, store.get_store_repo())?;
let branch = repo.branch(branch_id)?;
let commit_id = commit.id().unwrap();
let commit_info: CommitInfoJs = (&commit.as_info(repo)).into();
if body.graph.is_some() {
let info = BranchUpdateInfo {
branch_id: *branch_id,
branch_is_main: branch.branch_type.is_main(),
repo_id: *repo_id,
topic_id: branch.topic,
token: branch.read_cap.tokenize(),
topic_id: branch.topic.clone().unwrap(),
token: branch.read_cap.as_ref().unwrap().tokenize(),
overlay_id: store.overlay_id,
previous_heads: commit.direct_causal_past_ids(),
commit_id: commit.id().unwrap(),
commit_id,
transaction: body.graph.take().unwrap(),
commit_info,
};
self.update_graph(&[info])
} else {
Ok(())
self.update_graph(vec![info]).await?;
}
//TODO: discrete update
Ok(())
}
fn find_branch_and_repo_for_quad(
@ -258,8 +273,8 @@ impl Verifier {
b.id,
b.topic_priv_key.is_some(),
true,
b.topic,
b.read_cap.tokenize(),
b.topic.clone().unwrap(),
b.read_cap.as_ref().unwrap().tokenize(),
)
}
Some(TargetBranchV0::BranchId(id)) => {
@ -269,8 +284,8 @@ impl Verifier {
id,
b.topic_priv_key.is_some(),
false,
b.topic,
b.read_cap.tokenize(),
b.topic.clone().unwrap(),
b.read_cap.as_ref().unwrap().tokenize(),
)
}
// TODO: implement TargetBranchV0::Named
@ -367,6 +382,9 @@ impl Verifier {
)
.await?;
let repo = self.get_repo(&repo_id, &store_repo)?;
let commit_info: CommitInfoJs = (&commit.as_info(repo)).into();
let graph_update = transac.graph.take().unwrap();
let info = BranchUpdateInfo {
@ -379,21 +397,24 @@ impl Verifier {
previous_heads: commit.direct_causal_past_ids(),
commit_id: commit.id().unwrap(),
transaction: graph_update,
commit_info,
};
updates.push(info);
}
self.update_graph(&updates)
self.update_graph(updates).await
}
fn update_graph(&mut self, updates: &[BranchUpdateInfo]) -> Result<(), VerifierError> {
self.graph_dataset
async fn update_graph(&mut self, updates: Vec<BranchUpdateInfo>) -> Result<(), VerifierError> {
let updates_ref = &updates;
let res = self
.graph_dataset
.as_ref()
.unwrap()
.ng_transaction(
move |mut transaction| -> Result<(), ng_oxigraph::oxigraph::store::StorageError> {
let reader = transaction.ng_get_reader();
for update in updates {
for update in updates_ref {
let commit_name =
NuriV0::commit_graph_name(&update.commit_id, &update.overlay_id);
let commit_encoded = numeric_encoder::StrHash::new(&commit_name);
@ -477,9 +498,9 @@ impl Verifier {
));
}
let at_current_heads = current_heads != direct_causal_past_encoded;
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
log_info!("AT CURRENT HEADS {}", at_current_heads);
let value = if update.branch_is_main {
REMOVED_IN_MAIN
} else {
@ -487,6 +508,7 @@ impl Verifier {
};
for remove in update.transaction.removes.iter() {
log_info!("REMOVING {}", remove.to_string());
let encoded_subject = remove.subject.as_ref().into();
let encoded_predicate = remove.predicate.as_ref().into();
let encoded_object = remove.object.as_ref().into();
@ -498,6 +520,11 @@ impl Verifier {
&direct_causal_past_encoded,
at_current_heads,
)?;
log_info!(
"direct_causal_past_encoded {:?}",
direct_causal_past_encoded
);
log_info!("observed_adds {:?}", observed_adds);
for removing in observed_adds {
let graph_encoded = EncodedTerm::NamedNode { iri_id: removing };
let quad_encoded = EncodedQuad::new(
@ -521,6 +548,10 @@ impl Verifier {
)?
.is_empty()
};
log_info!(
"should_remove_ov_triples {}",
should_remove_ov_triples
);
if should_remove_ov_triples {
let ov_graphname_ref =
GraphNameRef::NamedNode(ov_graphname.into());
@ -536,7 +567,24 @@ impl Verifier {
Ok(())
},
)
.map_err(|e| VerifierError::OxigraphError(e.to_string()))
.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;
}
}
res
}
pub(crate) async fn process_sparql_update(

@ -13,3 +13,41 @@ mod request_processor;
#[cfg(all(not(target_family = "wasm"), not(docsrs)))]
mod rocksdb_user_storage;
use ng_net::app_protocol::*;
use ng_oxigraph::oxrdf::Triple;
fn triples_ser_to_json_ser(ser: &Vec<u8>) -> Result<Vec<u8>, String> {
let triples: Vec<Triple> = serde_bare::from_slice(ser)
.map_err(|_| "Deserialization error of Vec<Triple>".to_string())?;
let mut triples_json: Vec<serde_json::Value> = Vec::with_capacity(triples.len());
for insert in triples {
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<Triple> to JSON".to_string())?;
Ok(json.as_bytes().to_vec())
}
pub fn prepare_app_response_for_js(mut app_response: AppResponse) -> Result<AppResponse, String> {
if let AppResponse::V0(AppResponseV0::State(AppState { ref mut graph, .. })) = app_response {
if graph.is_some() {
let graph_state = graph.take().unwrap();
*graph = Some(GraphState {
triples: triples_ser_to_json_ser(&graph_state.triples)?,
});
};
} else if let AppResponse::V0(AppResponseV0::Patch(AppPatch { ref mut graph, .. })) =
app_response
{
if graph.is_some() {
let mut graph_patch = graph.take().unwrap();
graph_patch.inserts = triples_ser_to_json_ser(&graph_patch.inserts)?;
graph_patch.removes = triples_ser_to_json_ser(&graph_patch.removes)?;
*graph = Some(graph_patch);
};
}
Ok(app_response)
}

@ -15,7 +15,9 @@ use std::sync::{Arc, RwLock};
use either::Either::{Left, Right};
use ng_net::app_protocol::FileName;
use ng_net::app_protocol::{
AppTabBranchInfo, AppTabDocInfo, AppTabInfo, AppTabStoreInfo, FileName, NuriV0,
};
use ng_repo::block_storage::BlockStorage;
use ng_repo::log::*;
use ng_repo::repo::{BranchInfo, Repo};
@ -105,4 +107,55 @@ impl UserStorage for RocksDbUserStorage {
fn branch_get_all_files(&self, branch: &BranchId) -> Result<Vec<FileName>, StorageError> {
BranchStorage::get_all_files(&branch, &self.user_storage)
}
fn branch_get_tab_info(
&self,
branch: &BranchId,
repo: &RepoId,
store: &StoreRepo,
) -> Result<AppTabInfo, StorageError> {
let branch_info = BranchStorage::load(branch, &self.user_storage)?;
let branch_tab_info = AppTabBranchInfo {
id: Some(format!("b:{}", branch.to_string())),
readcap: Some(branch_info.read_cap.unwrap().readcap_nuri()),
class: Some(branch_info.crdt.class().clone()),
comment_branch: None, //TODO
};
let root_branch_info = BranchStorage::load(repo, &self.user_storage)?;
let doc_tab_info = AppTabDocInfo {
nuri: Some(format!("o:{}", repo.to_string())),
is_store: Some(store.repo_id() == repo),
is_member: Some(root_branch_info.read_cap.unwrap().readcap_nuri()), // TODO
authors: None, // TODO
inbox: None, // TODO
can_edit: Some(true),
title: None,
icon: None,
description: None,
};
let store_tab_info = AppTabStoreInfo {
overlay: Some(format!(
"v:{}",
store.overlay_id_for_read_purpose().to_string()
)),
store_type: Some(store.store_type_for_app()),
has_outer: None, //TODO
inner: None, //TODO
is_member: None, //TODO
readcap: None, //TODO
title: None,
icon: None,
description: None,
};
Ok(AppTabInfo {
branch: Some(branch_tab_info),
doc: Some(doc_tab_info),
store: Some(store_tab_info),
})
}
}

@ -38,13 +38,21 @@ impl<'a> BranchStorage<'a> {
const READ_CAP: u8 = b'r';
const TOPIC: u8 = b't';
const COMMITS_NBR: u8 = b'n';
const FORK_OF: u8 = b'f';
const MERGED_IN: u8 = b'm';
const CRDT: u8 = b'd';
const CLASS: u8 = b'c';
const ALL_PROPERTIES: [u8; 5] = [
const ALL_PROPERTIES: [u8; 9] = [
Self::TYPE,
Self::PUBLISHER,
Self::READ_CAP,
Self::TOPIC,
Self::COMMITS_NBR,
Self::FORK_OF,
Self::MERGED_IN,
Self::CRDT,
Self::CLASS,
];
const PREFIX_HEADS: u8 = b'h';
@ -83,6 +91,9 @@ impl<'a> BranchStorage<'a> {
&info.read_cap,
&info.branch_type,
&info.topic,
&info.fork_of,
&info.merged_in,
&info.crdt,
info.topic_priv_key.as_ref(),
&info.current_heads,
storage,
@ -93,9 +104,12 @@ impl<'a> BranchStorage<'a> {
pub fn create(
id: &BranchId,
read_cap: &ReadCap,
read_cap: &Option<ReadCap>,
branch_type: &BranchType,
topic: &TopicId,
topic: &Option<TopicId>,
fork_of: &Option<BranchId>,
merged_in: &Option<BranchId>,
crdt: &BranchCrdt,
publisher: Option<&BranchWriteCapSecret>,
current_heads: &Vec<ObjectRef>,
storage: &'a dyn KCVStorage,
@ -110,12 +124,31 @@ impl<'a> BranchStorage<'a> {
storage.write_transaction(&mut |tx| {
let id_ser = to_vec(&id)?;
let value = to_vec(read_cap)?;
tx.put(Self::PREFIX, &id_ser, Some(Self::READ_CAP), &value, &None)?;
if read_cap.is_some() {
let value = to_vec(read_cap.as_ref().unwrap())?;
tx.put(Self::PREFIX, &id_ser, Some(Self::READ_CAP), &value, &None)?;
}
let value = to_vec(branch_type)?;
tx.put(Self::PREFIX, &id_ser, Some(Self::TYPE), &value, &None)?;
let value = to_vec(topic)?;
tx.put(Self::PREFIX, &id_ser, Some(Self::TOPIC), &value, &None)?;
if topic.is_some() {
let value = to_vec(topic.as_ref().unwrap())?;
tx.put(Self::PREFIX, &id_ser, Some(Self::TOPIC), &value, &None)?;
}
if merged_in.is_some() {
let value = to_vec(merged_in.as_ref().unwrap())?;
tx.put(Self::PREFIX, &id_ser, Some(Self::MERGED_IN), &value, &None)?;
}
if fork_of.is_some() {
let value = to_vec(fork_of.as_ref().unwrap())?;
tx.put(Self::PREFIX, &id_ser, Some(Self::FORK_OF), &value, &None)?;
}
if *crdt != BranchCrdt::None {
let value = to_vec(&crdt.name())?;
tx.put(Self::PREFIX, &id_ser, Some(Self::CRDT), &value, &None)?;
let value = to_vec(&crdt.class())?;
tx.put(Self::PREFIX, &id_ser, Some(Self::CLASS), &value, &None)?;
}
if let Some(privkey) = publisher {
let value = to_vec(privkey)?;
tx.put(Self::PREFIX, &id_ser, Some(Self::PUBLISHER), &value, &None)?;
@ -140,11 +173,22 @@ impl<'a> BranchStorage<'a> {
&None,
)?;
let crdt_name = prop(Self::CRDT, &props).ok();
let class = prop(Self::CLASS, &props).ok();
let crdt: BranchCrdt = if crdt_name.is_none() || class.is_none() {
BranchCrdt::None
} else {
BranchCrdt::from(crdt_name.unwrap(), class.unwrap())
};
let bs = BranchInfo {
id: id.clone(),
branch_type: prop(Self::TYPE, &props)?,
read_cap: prop(Self::READ_CAP, &props)?,
topic: prop(Self::TOPIC, &props)?,
read_cap: prop(Self::READ_CAP, &props).ok(),
topic: prop(Self::TOPIC, &props).ok(),
fork_of: prop(Self::FORK_OF, &props).ok(),
merged_in: prop(Self::MERGED_IN, &props).ok(),
crdt,
topic_priv_key: prop(Self::PUBLISHER, &props).ok(),
current_heads: Self::get_all_heads(id, storage)?,
commits_nbr: prop(Self::COMMITS_NBR, &props).unwrap_or(0),

@ -240,7 +240,7 @@ impl<'a> RepoStorage<'a> {
for branch in branch_ids {
let info = BranchStorage::load(&branch, storage)?;
if info.branch_type == BranchType::Overlay {
overlay_branch_read_cap = Some(info.read_cap.clone());
overlay_branch_read_cap = Some(info.read_cap.clone().unwrap());
}
//log_info!("LOADING BRANCH INFO {}", branch);
//log_info!("TOPIC {}", info.topic);
@ -263,13 +263,13 @@ impl<'a> RepoStorage<'a> {
prop(Self::STORE_REPO, &props).map_err(|_| StorageError::NotAStoreRepo)?;
let store_info = branches.get(id).ok_or(StorageError::NotFound)?;
let overlay_branch_read_cap = if store_repo.is_private() {
store_info.read_cap.clone()
store_info.read_cap.clone().unwrap()
} else {
overlay_branch_read_cap.ok_or(StorageError::OverlayBranchNotFound)?
};
Arc::new(Store::new(
store_repo,
store_info.read_cap.clone(),
store_info.read_cap.clone().unwrap(),
overlay_branch_read_cap,
bs,
))

@ -14,7 +14,7 @@ use std::{
sync::{Arc, RwLock},
};
use ng_net::app_protocol::FileName;
use ng_net::app_protocol::{AppTabInfo, FileName};
use ng_repo::{
block_storage::BlockStorage,
errors::StorageError,
@ -51,6 +51,13 @@ pub trait UserStorage: Send + Sync {
fn branch_get_all_files(&self, branch: &BranchId) -> Result<Vec<FileName>, StorageError>;
fn branch_get_tab_info(
&self,
branch: &BranchId,
repo: &RepoId,
store: &StoreRepo,
) -> Result<AppTabInfo, StorageError>;
fn update_branch_current_heads(
&self,
repo_id: &RepoId,
@ -93,6 +100,15 @@ impl UserStorage for InMemoryUserStorage {
}
}
fn branch_get_tab_info(
&self,
branch: &BranchId,
repo: &RepoId,
store: &StoreRepo,
) -> Result<AppTabInfo, StorageError> {
unimplemented!();
}
fn get_all_store_and_repo_ids(&self) -> Result<HashMap<StoreRepo, Vec<RepoId>>, StorageError> {
unimplemented!();
}

@ -26,7 +26,6 @@ use async_std::sync::{Mutex, RwLockReadGuard};
use bloomfilter::Bloom;
use futures::channel::mpsc;
use futures::SinkExt;
use ng_repo::object::Object;
use serde::{Deserialize, Serialize};
use web_time::SystemTime;
@ -35,6 +34,8 @@ use web_time::SystemTime;
//use ng_oxigraph::oxigraph::model::GroundQuad;
//use yrs::{StateVector, Update};
use ng_oxigraph::oxrdf::{GraphNameRef, NamedNode, Triple};
use ng_repo::file::ReadFile;
use ng_repo::log::*;
#[cfg(any(test, feature = "testing"))]
@ -43,6 +44,7 @@ use ng_repo::{
block_storage::{store_max_value_size, BlockStorage, HashMapBlockStorage},
errors::{NgError, ProtocolError, ServerError, StorageError, VerifierError},
file::RandomAccessFile,
object::Object,
repo::{BranchInfo, Repo},
store::Store,
types::*,
@ -233,6 +235,49 @@ impl Verifier {
}
}
fn branch_get_tab_info(repo: &Repo, branch: &BranchId) -> Result<AppTabInfo, NgError> {
let branch_info = repo.branch(branch)?;
let branch_tab_info = AppTabBranchInfo {
id: Some(format!("b:{}", branch.to_string())),
readcap: Some(branch_info.read_cap.as_ref().unwrap().readcap_nuri()),
class: Some(branch_info.crdt.class().clone()),
comment_branch: None, //TODO
};
let root_branch_info = repo.branch(&repo.id)?;
let doc_tab_info = AppTabDocInfo {
nuri: Some(format!("o:{}", repo.id.to_string())),
is_store: Some(repo.store.id() == &repo.id),
is_member: Some(root_branch_info.read_cap.as_ref().unwrap().readcap_nuri()), // TODO
authors: None, // TODO
inbox: None, // TODO
can_edit: Some(true),
title: None,
icon: None,
description: None,
};
let store_tab_info = AppTabStoreInfo {
overlay: Some(format!("v:{}", repo.store.outer_overlay().to_string())),
store_type: Some(repo.store.get_store_repo().store_type_for_app()),
has_outer: None, //TODO
inner: None, //TODO
is_member: None, //TODO
readcap: None, //TODO
title: None,
icon: None,
description: None,
};
Ok(AppTabInfo {
branch: Some(branch_tab_info),
doc: Some(doc_tab_info),
store: Some(store_tab_info),
})
}
pub(crate) async fn create_branch_subscription(
&mut self,
repo_id: RepoId,
@ -260,12 +305,54 @@ impl Verifier {
.as_ref()
.unwrap()
.branch_get_all_files(&branch_id)?;
let tab_info = Self::branch_get_tab_info(repo, &branch_id)?;
// let tab_info = self.user_storage.as_ref().unwrap().branch_get_tab_info(
// &branch_id,
// &repo_id,
// &store_repo,
// )?;
let store = self.graph_dataset.as_ref().unwrap();
let graph_name = self
.resolve_target_for_sparql(&NuriTargetV0::Repo(repo_id), false)?
.unwrap(); //TODO: deal with branch
let quad_iter = store.quads_for_pattern(
None,
None,
None,
Some(GraphNameRef::NamedNode(
NamedNode::new_unchecked(graph_name).as_ref(),
)),
);
let mut results = vec![];
for quad in quad_iter {
match quad {
Err(e) => {} //return Err(VerifierError::OxigraphError(e.to_string())),
Ok(quad) => results.push(Triple::from(quad)),
}
}
let state = AppState {
heads: branch.current_heads.iter().map(|h| h.id.clone()).collect(),
graph: None,
graph: if results.is_empty() {
None
} else {
Some(GraphState {
triples: serde_bare::to_vec(&results).unwrap(),
})
},
discrete: None,
files,
};
self.push_app_response(
&branch_id,
AppResponse::V0(AppResponseV0::TabInfo(tab_info)),
)
.await;
self.push_app_response(&branch_id, AppResponse::V0(AppResponseV0::State(state)))
.await;
@ -1096,7 +1183,7 @@ impl Verifier {
// pinning the repo on the server broker
let (pin_req, topic_id) = {
let repo = self.repos.get(repo_id).ok_or(NgError::RepoNotFound)?;
let topic_id = repo.branch(branch).unwrap().topic;
let topic_id = repo.branch(branch).unwrap().topic.unwrap();
//TODO: only pin the requested branch.
let pin_req = PinRepo::from_repo(repo, remote_broker.broker_peer_id());
(pin_req, topic_id)
@ -1135,7 +1222,7 @@ impl Verifier {
// checking that the branch is subscribed as publisher
let repo = self.repos.get(repo_id).ok_or(NgError::RepoNotFound)?;
let branch_info = repo.branch(branch)?;
let topic_id = &branch_info.topic;
let topic_id = branch_info.topic.as_ref().unwrap();
// log_info!(
// "as_publisher {} {}",
// as_publisher,
@ -1266,7 +1353,12 @@ impl Verifier {
.ok_or(VerifierError::BranchNotOpened)?;
let branch = repo.branch(&branch_id)?;
let commit = event.open(&repo.store, &repo_id, &branch_id, &branch.read_cap.key)?;
let commit = event.open(
&repo.store,
&repo_id,
&branch_id,
&branch.read_cap.as_ref().unwrap().key,
)?;
self.verify_commit(&commit, &branch_id, &repo_id, Arc::clone(&repo.store))
.await?;
@ -1353,11 +1445,15 @@ impl Verifier {
fn user_storage_if_persistent(&self) -> Option<Arc<Box<dyn UserStorage>>> {
if self.is_persistent() {
if let Some(us) = self.user_storage.as_ref() {
Some(Arc::clone(us))
} else {
None
}
self.user_storage()
} else {
None
}
}
fn user_storage(&self) -> Option<Arc<Box<dyn UserStorage>>> {
if let Some(us) = self.user_storage.as_ref() {
Some(Arc::clone(us))
} else {
None
}
@ -1373,7 +1469,7 @@ impl Verifier {
user_storage.add_branch(repo_id, &branch_info)?;
}
let branch_id = branch_info.id.clone();
let topic_id = branch_info.topic.clone();
let topic_id = branch_info.topic.clone().unwrap();
let repo = self.get_repo_mut(repo_id, store_repo)?;
let res = repo.branches.insert(branch_info.id.clone(), branch_info);
assert!(res.is_none());
@ -1527,13 +1623,17 @@ impl Verifier {
};
let msg = TopicSyncReq::V0(TopicSyncReqV0 {
topic: branch_info.topic,
topic: branch_info.topic.unwrap(),
known_heads: ours_set.union(&theirs_found).into_iter().cloned().collect(),
target_heads: theirs_not_found,
known_commits,
overlay: Some(store.overlay_for_read_on_client_protocol()),
});
(store, msg, branch_info.read_cap.key.clone())
(
store,
msg,
branch_info.read_cap.as_ref().unwrap().key.clone(),
)
};
match broker
@ -1640,8 +1740,8 @@ impl Verifier {
.map(|(branch_id, branch)| {
(
branch_id.clone(),
branch.topic.clone(),
branch.read_cap.key.clone(),
branch.topic.clone().unwrap(),
branch.read_cap.as_ref().unwrap().key.clone(),
)
})
.collect();
@ -1831,7 +1931,7 @@ impl Verifier {
.get(&(e.overlay, *e.event.topic_id()))
.ok_or(VerifierError::TopicNotFound)?;
let branch = repo.branch(branch_id)?;
(branch_id, &branch.read_cap.key)
(branch_id, &branch.read_cap.as_ref().unwrap().key)
}
};
@ -1888,7 +1988,7 @@ impl Verifier {
.get(&(e.overlay, *e.event.topic_id()))
.ok_or(VerifierError::TopicNotFound)?;
let branch = repo.branch(branch_id)?;
(branch_id, &branch.read_cap.key)
(branch_id, &branch.read_cap.as_ref().unwrap().key)
}
};
@ -2028,7 +2128,7 @@ impl Verifier {
&repo.store,
&repo_id,
&branch_id,
&branch.read_cap.key,
&branch.read_cap.as_ref().unwrap().key,
)?;
let store_repo = repo.store.get_store_repo().clone();
@ -2206,7 +2306,7 @@ impl Verifier {
pub(crate) fn populate_topics(&mut self, repo: &Repo) {
for (branch_id, info) in repo.branches.iter() {
let overlay_id: OverlayId = repo.store.inner_overlay();
let topic_id = info.topic.clone();
let topic_id = info.topic.clone().unwrap();
let repo_id = repo.id.clone();
let branch_id = branch_id.clone();
let _res = self

@ -134,6 +134,8 @@ pub struct SessionInfoString {
pub session_id: u64,
pub user: String,
pub private_store_id: String,
pub protected_store_id: String,
pub public_store_id: String,
}
impl From<SessionInfo> for SessionInfoString {
@ -141,6 +143,8 @@ impl From<SessionInfo> for SessionInfoString {
SessionInfoString {
session_id: f.session_id,
private_store_id: f.private_store_id,
protected_store_id: f.protected_store_id,
public_store_id: f.public_store_id,
user: f.user.to_string(),
}
}
@ -151,6 +155,8 @@ pub struct SessionInfo {
pub session_id: u64,
pub user: UserId,
pub private_store_id: String,
pub protected_store_id: String,
pub public_store_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]

@ -250,7 +250,7 @@
/>
</svg>
<span
>All the data you exchange with us while using the broker is
>All the data you exchange with us while using this broker is
end-to-end encrypted and we do not have access to your
decryption keys, meaning that we cannot see the content of your
documents.</span
@ -279,7 +279,7 @@
to the broker. We collect general purpose information about your
device (OS version, browser version, and if you use the app, the
version and date of last update). We do not have access to any
unique tracking identifier of your device (like Android MAID or
unique tracking identifier of your device (like Android MAID/AAID or
iPhone IDFA). We could nevertheless be asked by law enforcement
authorities, depending on the jurisdiction of the server, to log
the IP you use when connecting to the broker, and/or to provide
@ -305,12 +305,18 @@
/>
</svg>
<span>
You can delete your account with us at any time by going to the
link <span
style="font-weight: 500;
color: #646cff;">account.{domain}/#/delete</span
> or by entering in your NextGraph application and selecting the
menu, then Accounts, then under broker "delete registration"</span
You can delete your account with us at any time by opening your NextGraph application, logging in,
and entering in the User Panel, then Accounts, then under Brokers "delete registration". Before deleting your account with us, make sure you register to another broker before, otherwise you will not be able to sync or share your data with any other users or devices</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z"></path>
</svg>
<span>
Q: I heard that with NextGraph there will be no more ToS to accept. Why then are there some ToS here? <br/> A: You are right that with NextGraph we can do without ToS, when the user is connecting to a self-hosted broker or to an NGbox. As those 2 options are not available yet, we only offer our public Broker Service Provider for now, which needs some ToS to be accepted. Very soon those 2 additional options will be available, but some users might still prefer using our brokers. In any case, the ToS here are minimal and with end-to-end encryption, your data is unreadable to us anyway.</span
>
</li>
<li class="flex space-x-3">
@ -363,11 +369,11 @@
<span
>By agreeing to those terms, you allow this software to store
some personal data locally in localStorage, the equivalent of
a cookie. This cookie contains your wallet and is never sent
to us. If you delete this cookie without keeping a copy of
your wallet somewhere else, then you will permanently loose
your wallet.
some personal data locally on this device, in localStorage.
This data contains your wallet and is never sent
to us. It only stays on this device or any other device where you will import your wallet.
If you delete this data without keeping a copy of
your wallet somewhere else, then access to your wallet and all its documents will be permanently lost.
</span>
</li>
{/if}

@ -11,6 +11,7 @@
<script>
import { Alert } from "flowbite-svelte";
export let params;
</script>
<div class="p-8">

@ -12,6 +12,8 @@ importers:
ng-app:
specifiers:
'@codemirror/language': ^6.10.2
'@codemirror/legacy-modes': ^6.4.0
'@popperjs/core': ^2.11.8
'@sveltejs/vite-plugin-svelte': ^2.0.0
'@tauri-apps/api': 2.0.0-alpha.8
@ -23,6 +25,7 @@ importers:
async-proxy: ^0.4.1
autoprefixer: ^10.4.14
classnames: ^2.3.2
codemirror: ^6.0.0
cross-env: ^7.0.3
dayjs: ^1.11.10
flowbite: ^1.6.5
@ -34,10 +37,13 @@ importers:
postcss: ^8.4.23
postcss-load-config: ^4.0.1
shx: ^0.3.4
sparql: link:@codemirror/legacy-modes/mode/sparql
svelte: ^3.54.0
svelte-check: ^3.0.0
svelte-codemirror-editor: ^1.4.0
svelte-heros-v2: ^0.10.12
svelte-i18n: ^4.0.0
svelte-inview: ^4.0.2
svelte-preprocess: ^5.0.3
svelte-spa-router: ^3.3.0
svelte-time: ^0.8.0
@ -50,17 +56,23 @@ importers:
vite-plugin-top-level-await: ^1.3.1
vite-plugin-wasm: ^3.2.2
dependencies:
'@codemirror/language': 6.10.2
'@codemirror/legacy-modes': 6.4.0
'@popperjs/core': 2.11.8
'@tauri-apps/api': 2.0.0-alpha.8
'@tauri-apps/plugin-barcode-scanner': 2.0.0-alpha.0
'@tauri-apps/plugin-window': 2.0.0-alpha.1
async-proxy: 0.4.1
classnames: 2.3.2
codemirror: 6.0.1
flowbite: 1.6.5
flowbite-svelte: 0.43.3_svelte@3.59.1
html5-qrcode: 2.3.8
ng-sdk-js: link:../ng-sdk-js/pkg
sparql: link:@codemirror/legacy-modes/mode/sparql
svelte-codemirror-editor: 1.4.0_5sa7ksvb6ejctmkumffbkxbvpi
svelte-i18n: 4.0.0_svelte@3.59.1
svelte-inview: 4.0.2_svelte@3.59.1
svelte-spa-router: 3.3.0
vite-plugin-top-level-await: 1.3.1_vite@4.3.9
devDependencies:
@ -128,6 +140,71 @@ packages:
engines: {node: '>=10'}
dev: true
/@codemirror/autocomplete/6.17.0_gts4chaxc3fh6p7x3saco74w5y:
resolution: {integrity: sha512-fdfj6e6ZxZf8yrkMHUSJJir7OJkHkZKaOZGzLWIYp2PZ3jd+d+UjG8zVPqJF6d3bKxkhvXTPan/UZ1t7Bqm0gA==}
peerDependencies:
'@codemirror/language': ^6.0.0
dependencies:
'@codemirror/language': 6.10.2
'@codemirror/state': 6.4.1
'@codemirror/view': 6.28.6
'@lezer/common': 1.2.1
dev: false
/@codemirror/commands/6.6.0:
resolution: {integrity: sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==}
dependencies:
'@codemirror/language': 6.10.2
'@codemirror/state': 6.4.1
'@codemirror/view': 6.28.6
'@lezer/common': 1.2.1
dev: false
/@codemirror/language/6.10.2:
resolution: {integrity: sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==}
dependencies:
'@codemirror/state': 6.4.1
'@codemirror/view': 6.28.6
'@lezer/common': 1.2.1
'@lezer/highlight': 1.2.0
'@lezer/lr': 1.4.1
style-mod: 4.1.2
dev: false
/@codemirror/legacy-modes/6.4.0:
resolution: {integrity: sha512-5m/K+1A6gYR0e+h/dEde7LoGimMjRtWXZFg4Lo70cc8HzjSdHe3fLwjWMR0VRl5KFT1SxalSap7uMgPKF28wBA==}
dependencies:
'@codemirror/language': 6.10.2
dev: false
/@codemirror/lint/6.8.1:
resolution: {integrity: sha512-IZ0Y7S4/bpaunwggW2jYqwLuHj0QtESf5xcROewY6+lDNwZ/NzvR4t+vpYgg9m7V8UXLPYqG+lu3DF470E5Oxg==}
dependencies:
'@codemirror/state': 6.4.1
'@codemirror/view': 6.28.6
crelt: 1.0.6
dev: false
/@codemirror/search/6.5.6:
resolution: {integrity: sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==}
dependencies:
'@codemirror/state': 6.4.1
'@codemirror/view': 6.28.6
crelt: 1.0.6
dev: false
/@codemirror/state/6.4.1:
resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==}
dev: false
/@codemirror/view/6.28.6:
resolution: {integrity: sha512-bhwB1AZ6zU4M3dNKm8Aa2BXwj5mWDqE9IWpqxYKJoLCnx+AcwcMuLO01tLWgc1mx4vT1IVYVqx86YoqUsATrqQ==}
dependencies:
'@codemirror/state': 6.4.1
style-mod: 4.1.2
w3c-keyname: 2.2.8
dev: false
/@esbuild/aix-ppc64/0.19.12:
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
engines: {node: '>=12'}
@ -596,6 +673,22 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@lezer/common/1.2.1:
resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==}
dev: false
/@lezer/highlight/1.2.0:
resolution: {integrity: sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==}
dependencies:
'@lezer/common': 1.2.1
dev: false
/@lezer/lr/1.4.1:
resolution: {integrity: sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw==}
dependencies:
'@lezer/common': 1.2.1
dev: false
/@nodelib/fs.scandir/2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@ -1048,6 +1141,18 @@ packages:
timers-ext: 0.1.8
dev: false
/codemirror/6.0.1:
resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
dependencies:
'@codemirror/autocomplete': 6.17.0_gts4chaxc3fh6p7x3saco74w5y
'@codemirror/commands': 6.6.0
'@codemirror/language': 6.10.2
'@codemirror/lint': 6.8.1
'@codemirror/search': 6.5.6
'@codemirror/state': 6.4.1
'@codemirror/view': 6.28.6
dev: false
/commander/4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
@ -1062,6 +1167,10 @@ packages:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
/crelt/1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
dev: false
/cross-env/7.0.3:
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
@ -2086,6 +2195,10 @@ packages:
min-indent: 1.0.1
dev: true
/style-mod/4.1.2:
resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==}
dev: false
/sucrase/3.32.0:
resolution: {integrity: sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==}
engines: {node: '>=8'}
@ -2132,6 +2245,16 @@ packages:
- sugarss
dev: true
/svelte-codemirror-editor/1.4.0_5sa7ksvb6ejctmkumffbkxbvpi:
resolution: {integrity: sha512-1Izqz48OfyzyHiloG9RAG6gDHHzZ9+P9SfnYkL8I+QJ1nOMjcftOI1HSds5bStZzCN5EGY1iBLYwOl4A+Xg8fA==}
peerDependencies:
codemirror: ^6.0.0
svelte: ^3.0.0 || ^4.0.0
dependencies:
codemirror: 6.0.1
svelte: 3.59.1
dev: false
/svelte-heros-v2/0.10.12_svelte@3.59.1:
resolution: {integrity: sha512-0wspy0z9UFS9f/iPKQQ1JDHlNY6e7h+LVW+wJ0qJnuWDpvsJllmoCX2g0frYbMPDWZJEwh2pkO25Dp3lDGCxGQ==}
peerDependencies:
@ -2166,6 +2289,14 @@ packages:
tiny-glob: 0.2.9
dev: false
/svelte-inview/4.0.2_svelte@3.59.1:
resolution: {integrity: sha512-saJp2QRCUEBletGPnI3utxROHqruDGRPfPhtdpNYQwD97P2oCKiVIlZE3tFNEiC7h3nCvU+8czaKWcH1o/axrA==}
peerDependencies:
svelte: ^3.0.0 || ^4.0.0
dependencies:
svelte: 3.59.1
dev: false
/svelte-preprocess/5.0.4_4klotfyqh3bzvqmua74kcysa7a:
resolution: {integrity: sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==}
engines: {node: '>= 14.10.0'}
@ -2577,6 +2708,10 @@ packages:
vite: 4.3.9_@types+node@18.16.16
dev: true
/w3c-keyname/2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
dev: false
/which/2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}

Loading…
Cancel
Save