navigation between docs and tabs, sparql update editor

pull/37/head
Niko PLP 5 months 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. 351
      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. 357
      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-repo",
"ng-wallet", "ng-wallet",
"serde", "serde",
"serde_bare",
"serde_bytes", "serde_bytes",
"serde_json", "serde_json",
"sys-locale", "sys-locale",
@ -3440,6 +3441,7 @@ dependencies = [
"libc", "libc",
"md-5", "md-5",
"memchr", "memchr",
"ng-repo",
"ng-rocksdb", "ng-rocksdb",
"oxilangtag", "oxilangtag",
"oxiri", "oxiri",
@ -3523,7 +3525,6 @@ dependencies = [
"serde-wasm-bindgen", "serde-wasm-bindgen",
"serde_bare", "serde_bare",
"serde_bytes", "serde_bytes",
"serde_json",
"sys-locale", "sys-locale",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
@ -3577,6 +3578,7 @@ dependencies = [
"serde", "serde",
"serde_bare", "serde_bare",
"serde_bytes", "serde_bytes",
"serde_json",
"web-time", "web-time",
"yrs", "yrs",
] ]

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

@ -13,9 +13,9 @@ use std::fs::{read, remove_file, write};
use std::path::PathBuf; use std::path::PathBuf;
use async_once_cell::OnceCell; 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::channel::mpsc;
use futures::SinkExt; use futures::{SinkExt, StreamExt};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use pdf_writer::{Content, Finish, Name, Pdf, Rect, Ref, Str}; 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::broker::*;
use ng_net::connection::{AppConfig, ClientConfig, IConnect, NoiseFSM, StartConfig}; use ng_net::connection::{AppConfig, ClientConfig, IConnect, NoiseFSM, StartConfig};
use ng_net::types::*; 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_net::{actor::*, actors::admin::*};
use ng_verifier::types::*; use ng_verifier::types::*;
@ -552,6 +552,7 @@ struct LocalBroker {
disconnections_sender: Sender<String>, disconnections_sender: Sender<String>,
disconnections_receiver: Option<Receiver<String>>, disconnections_receiver: Option<Receiver<String>>,
pump_cond: Option<Arc<(Mutex<bool>, Condvar)>>,
} }
impl fmt::Debug for LocalBroker { 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. // used to deliver events to the verifier on Clients, or Core that have Verifiers attached.
#[async_trait::async_trait] #[async_trait::async_trait]
impl ILocalBroker for LocalBroker { 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 { 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> { // fn storage_path_for_user(&self, user_id: &UserId) -> Option<PathBuf> {
// match &self.config { // match &self.config {
// LocalBrokerConfig::InMemory | LocalBrokerConfig::JsStorage(_) => None, // LocalBrokerConfig::InMemory | LocalBrokerConfig::JsStorage(_) => None,
@ -914,12 +972,12 @@ impl LocalBroker {
} }
fn get_wallet_and_session( fn get_wallet_and_session(
&mut self, &self,
user_id: &UserId, 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_idx = self.user_to_local_session_id_for_mut(user_id)?;
let session = self.opened_sessions_list[session_idx] let session = self.opened_sessions_list[session_idx]
.as_mut() .as_ref()
.ok_or(NgError::SessionNotFound)?; .ok_or(NgError::SessionNotFound)?;
let wallet = &match &session.config { let wallet = &match &session.config {
SessionConfig::WithCredentialsV0(_) | SessionConfig::HeadlessV0(_) => { SessionConfig::WithCredentialsV0(_) | SessionConfig::HeadlessV0(_) => {
@ -934,6 +992,14 @@ impl LocalBroker {
Ok((wallet, session)) 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> { async fn disconnect_session(&mut self, user_id: &PubKey) -> Result<(), NgError> {
match self.opened_sessions.get(user_id) { match self.opened_sessions.get(user_id) {
Some(session) => { Some(session) => {
@ -1022,6 +1088,12 @@ impl LocalBroker {
let private_store_id = self let private_store_id = self
.get_site_store_of_session(&session, SiteStoreType::Private)? .get_site_store_of_session(&session, SiteStoreType::Private)?
.to_string(); .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(); let user_id = session.config.user_id();
@ -1035,6 +1107,8 @@ impl LocalBroker {
session_id: idx as u64, session_id: idx as u64,
user: user_id, user: user_id,
private_store_id, private_store_id,
protected_store_id,
public_store_id,
}) })
} }
@ -1059,7 +1133,9 @@ impl LocalBroker {
Ok(SessionInfo { Ok(SessionInfo {
session_id: first_available, session_id: first_available,
user: user_id, 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 (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, config,
wallets, wallets,
opened_wallets: HashMap::new(), opened_wallets: HashMap::new(),
@ -1373,7 +1452,10 @@ async fn init_(config: LocalBrokerConfig) -> Result<Arc<RwLock<LocalBroker>>, Ng
disconnections_sender, disconnections_sender,
disconnections_receiver: Some(disconnections_receiver), disconnections_receiver: Some(disconnections_receiver),
headless_connected_to_remote_broker: false, headless_connected_to_remote_broker: false,
pump_cond: None,
}; };
local_broker.init_pump(broker_pump_receiver);
//log_debug!("{:?}", &local_broker); //log_debug!("{:?}", &local_broker);
let broker = Arc::new(RwLock::new(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 BROKER
.write() .write()
.await .await
.set_local_broker(Arc::clone(&broker) as Arc<RwLock<dyn ILocalBroker>>); .set_local_broker(localbroker_pump_sender);
Ok(broker) 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 { if let Ok(AppResponse::V0(AppResponseV0::SessionStart(AppSessionStartResponse::V0(response)))) = res {
session_info.private_store_id = response.private_store.to_string(); 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) 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. // 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 private_store_id: broker
.get_site_store_of_session(sess, SiteStoreType::Private)? .get_site_store_of_session(sess, SiteStoreType::Private)?
.to_string(), .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)] #[doc(hidden)]
pub async fn user_connect_with_device_info( pub async fn user_connect_with_device_info(
info: ClientInfo, info: ClientInfo,
user_id: &UserId, original_user_id: &UserId,
location: Option<String>, location: Option<String>,
) -> Result<Vec<(String, String, String, Option<String>, f64)>, NgError> { ) -> 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 ? //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()?; 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 mut result: Vec<(String, String, String, Option<String>, f64)> = Vec::new();
let arc_cnx: Arc<Box<dyn IConnect>> = Arc::new(Box::new(ConnectionWebSocket {})); let arc_cnx: Arc<Box<dyn IConnect>> = Arc::new(Box::new(ConnectionWebSocket {}));
match wallet { let client_priv = &client.sensitive_client_storage.priv_key;
SensitiveWallet::V0(wallet) => { let client_name = &client.name;
let client = wallet.client.as_ref().unwrap(); let auto_open = &client.auto_open;
let client_priv = &client.sensitive_client_storage.priv_key; // log_info!(
let client_name = &client.name; // "XXXX {} name={:?} auto_open={:?} {:?}",
let auto_open = &client.auto_open; // client_id.to_string(),
// log_info!( // client_name,
// "XXXX {} name={:?} auto_open={:?} {:?}", // auto_open,
// client_id.to_string(), // wallet
// client_name, // );
// auto_open, for user in auto_open {
// wallet let user_id = user.to_string();
// ); let peer_id = peer_key.to_pub();
for user in auto_open { log_info!(
let user_id = user.to_string(); "connecting with local peer_id {} for user {}",
let peer_key = &session.peer_key; peer_id,
let peer_id = peer_key.to_pub(); user_id
log_info!( );
"connecting with local peer_id {} for user {}", let site = sites.get(&user_id);
peer_id, if site.is_none() {
user_id result.push((
); user_id,
let site = wallet.sites.get(&user_id); "".into(),
if site.is_none() { "".into(),
result.push(( Some("Site is missing".into()),
user_id, get_unix_time(),
"".into(), ));
"".into(), continue;
Some("Site is missing".into()), }
get_unix_time(), let site = site.unwrap();
)); let user_priv = site.get_individual_user_priv_key().unwrap();
continue; let core = site.cores[0]; //TODO: cycle the other cores if failure to connect (failover)
} let server_key = core.0;
let site = site.unwrap(); let broker = brokers.get(&core.0.to_string());
let user_priv = site.get_individual_user_priv_key().unwrap(); if broker.is_none() {
let core = site.cores[0]; //TODO: cycle the other cores if failure to connect (failover) result.push((
let server_key = core.0; user_id,
let broker = wallet.brokers.get(&core.0.to_string()); core.0.to_string(),
if broker.is_none() { "".into(),
result.push(( Some("Broker is missing".into()),
user_id, get_unix_time(),
core.0.to_string(), ));
"".into(), continue;
Some("Broker is missing".into()), }
get_unix_time(), let brokers = broker.unwrap();
)); let mut tried: Option<(String, String, String, Option<String>, f64)> = None;
continue; //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.
let brokers = broker.unwrap(); local_broker.stop_pump().await;
let mut tried: Option<(String, String, String, Option<String>, f64)> = None; for broker_info in brokers {
//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 match broker_info {
//TODO: use site.bootstraps to order the list of brokerInfo. BrokerInfoV0::ServerV0(server) => {
for broker_info in brokers { let url = server.get_ws_url(&location).await;
match broker_info { log_debug!("URL {:?}", url);
BrokerInfoV0::ServerV0(server) => { //Option<(String, Vec<BindAddress>)>
let url = server.get_ws_url(&location).await; if url.is_some() {
log_debug!("URL {:?}", url); let url = url.unwrap();
//Option<(String, Vec<BindAddress>)> if url.1.is_empty() {
if url.is_some() { // 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 url = url.unwrap(); let res = BROKER
if url.1.is_empty() { .write()
// TODO deal with Box(Dyn)Public -> tunnel, and on tauri/forward/CLIs, deal with all Box -> direct connections (when url.1.len is > 0) .await
let res = BROKER .connect(
.write() arc_cnx.clone(),
.await peer_key.clone(),
.connect( peer_id,
arc_cnx.clone(), server_key,
peer_key.clone(), StartConfig::Client(ClientConfig {
peer_id, url: url.0.clone(),
server_key, name: client_name.clone(),
StartConfig::Client(ClientConfig { user_priv: user_priv.clone(),
url: url.0.clone(), client_priv: client_priv.clone(),
name: client_name.clone(), info: info.clone(),
user_priv: user_priv.clone(), registration: Some(core.1),
client_priv: client_priv.clone(), }),
info: info.clone(), )
registration: Some(core.1), .await;
}), log_debug!("broker.connect : {:?}", res);
)
.await; tried = Some((
log_debug!("broker.connect : {:?}", res); user_id.clone(),
core.0.to_string(),
tried = Some(( url.0.into(),
user_id.clone(), match &res {
core.0.to_string(), Ok(_) => None,
url.0.into(), Err(e) => Some(e.to_string()),
match &res { },
Ok(_) => None, get_unix_time(),
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)?;
if tried.is_some() && tried.as_ref().unwrap().3.is_none() { session.verifier.connection_opened(server_key).await
if let Err(e) = };
session.verifier.connection_opened(server_key).await if res.is_err() {
{ let e = res.unwrap_err();
log_err!( log_err!("got error while processing opened connection {:?}", e);
"got error while processing opened connection {:?}", Broker::close_all_connections().await;
e tried.as_mut().unwrap().3 = Some(e.to_string());
); } else {
Broker::close_all_connections().await; local_broker.start_pump().await;
tried.as_mut().unwrap().3 = Some(e.to_string());
}
break;
} else {
log_debug!("Failed connection {:?}", tried);
}
} }
break;
} else {
log_debug!("Failed connection {:?}", tried);
} }
// Core information is discarded
_ => {}
} }
} }
if tried.is_none() { // Core information is discarded
tried = Some(( _ => {}
user_id,
core.0.to_string(),
"".into(),
Some("No broker found".into()),
get_unix_time(),
));
}
result.push(tried.unwrap());
} }
} }
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) Ok(result)
} }

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

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

@ -364,6 +364,7 @@ async fn app_request_stream(
main_window: tauri::Window, main_window: tauri::Window,
) -> ResultSend<()> { ) -> ResultSend<()> {
while let Some(app_response) = reader.next().await { 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(); 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")] #[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( let request = AppRequest::new(
AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)), 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, None,
); );
Ok(request) Ok(request)

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

@ -42,7 +42,7 @@ const mapping = {
"test": [ ], "test": [ ],
"get_device_name": [], "get_device_name": [],
"doc_fetch_private_subscribe": [], "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") { // } else if (path[0] === "wallet_create") {
// let res = await Reflect.apply(sdk[path], caller, args); // let res = await Reflect.apply(sdk[path], caller, args);
// return res; // 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 { } else {
return Reflect.apply(sdk[path], caller, args) return Reflect.apply(sdk[path], caller, args)
} }
@ -156,10 +172,24 @@ const handler = {
if (event.payload.V0.FileBinary) { if (event.payload.V0.FileBinary) {
event.payload.V0.FileBinary = Uint8Array.from(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(()=> {}) 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 () => { return () => {
unlisten(); unlisten();
tauri.invoke("cancel_stream", {stream_id}); 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 // notice may not be copied, modified, or distributed except
// according to those terms. // 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:", // "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", // "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", // "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) // "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", // "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", // "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", // "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: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" // "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", "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 // application/vnd.api+json
@ -27,12 +27,12 @@
// animation: snap, lottie, smil editor: https://github.com/HaikuTeam/animator/ // animation: snap, lottie, smil editor: https://github.com/HaikuTeam/animator/
export const has_toc = (class_name) => { 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" 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" || 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 = { export const official_classes = {
"post/rich": { "post:rich": {
"ng:crdt": "YXml", "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: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", "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"], "ng:compat": ["as:Article"],
}, },
"post/md": { "post:md": {
"ng:crdt": "YXml", "ng:crdt": "YXml",
"ng:n": "Post - MarkDown", // editor y-MilkDown, viewer: https://github.com/wooorm/markdown-rs "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", "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": { "ng:x": {
"as":true, "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:crdt": "YText",
"ng:n": "Post - Plain Text", "ng:n": "Post - Plain Text",
"ng:a": "A Post with Plain Text", "ng:a": "A Post with Plain Text",
@ -63,9 +63,9 @@ export const official_classes = {
"ng:x": { "ng:x": {
"as":true, "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:crdt": "YXml",
"ng:n": "Post - TinyMCE", "ng:n": "Post - TinyMCE",
"ng:x": { "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:a": "A Post based on TinyMCE, including images, links, formatted text, and embeds of other content",
"ng:compat": ["as:Article"], "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:crdt": "YText",
"ng:n": "Post - AsciiDoc", "ng:n": "Post - AsciiDoc",
"ng:a": "A Post based on AsciiDoc format", "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:a": "A Source Code file. many languages supported",
"ng:o": "n:g:z:pre", "ng:o": "n:g:z:pre",
"ng:w": "n:g:z:code_editor", "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", "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"], "file:iana:application:yaml", "file:iana:text:xml", "file:iana:application:xhtml+xml"],
}, },
"app": { "app": {
"ng:n": "Official App", "ng:n": "Official App",
"ng:a": "App provided by NextGraph platform", "ng:a": "App provided by NextGraph platform",
}, },
"app/z": { "app:z": {
"ng:crdt": "Elmer", "ng:crdt": "Elmer",
"ng:n": "Application", // Editor: Monaco "ng:n": "Application", // Editor: Monaco
"ng:a": "Create an Application based on NextGraph Framework", "ng:a": "Create an Application based on NextGraph Framework",
"ng:o": "n:g:z:app_store", "ng:o": "n:g:z:app_store",
"ng:w": "n:g:z:app_editor", "ng:w": "n:g:z:app_editor",
"ng:include": ["schema/*","service/*","code","file"], "ng:include": ["schema:*","service:*","code","file"],
"ng:compat": ["code/svelte"], "ng:compat": ["code:svelte"],
}, },
"class": { "class": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
@ -120,7 +120,7 @@ export const official_classes = {
}, },
"ng:compat": ["rdfs:Class"], "ng:compat": ["rdfs:Class"],
}, },
"schema/rdfs": { "schema:rdfs": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Schema - RDFS", "ng:n": "Schema - RDFS",
"ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with 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": { "ng:x": {
"rdfs":true, "rdfs":true,
}, },
"ng:include": ["data/graph"], "ng:include": ["data:graph"],
"ng:compat": ["rdfs:*","class"], "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:crdt": "Graph",
"ng:n": "Schema - OWL", "ng:n": "Schema - OWL",
"ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with 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": { "ng:x": {
"owl":true, "owl":true,
}, },
"ng:include": ["data/graph"], "ng:include": ["data:graph"],
"ng:compat": ["owl:Ontology"], "ng:compat": ["owl:Ontology"],
}, },
"schema/shacl": { "schema:shacl": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Schema - SHACL", "ng:n": "Schema - SHACL",
"ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with 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": { "ng:x": {
"sh":true, "sh":true,
}, },
"ng:include": ["data/graph"], "ng:include": ["data:graph"],
"ng:compat": ["sh:Shape", "file/iana/text/shaclc" ], "ng:compat": ["sh:Shape", "file:iana:text:shaclc" ],
}, },
"schema/shex": { "schema:shex": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Schema - SHEX", "ng:n": "Schema - SHEX",
"ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with 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": { "ng:x": {
"shex":true, "shex":true,
}, },
"ng:include": ["data/graph"], "ng:include": ["data:graph"],
"ng:compat": ["shex:*", "file/iana/text/shex", "code/shexc" ], "ng:compat": ["shex:*", "file:iana:text:shex", "code:shexc" ],
}, },
"service": { "service": {
"ng:n": "Internal Service", "ng:n": "Internal Service",
"ng:a": "Service provided by NextGraph framework", "ng:a": "Service provided by NextGraph framework",
"ng:o": "n:g:z:service_invoke", // default viewer "ng:o": "n:g:z:service_invoke", // default viewer
}, },
"service/rust": { "service:rust": {
"ng:crdt": "YText", "ng:crdt": "YText",
"ng:n": "Service - Rust", // edited with CodeMirror, displayed with highlight.js "ng:n": "Service - Rust", // edited with CodeMirror, displayed with highlight.js
"ng:a": "Service written in Rust and compiled to WASM", "ng:a": "Service written in Rust and compiled to WASM",
"ng:o": "external_service_invoke", // default viewer "ng:o": "external_service_invoke", // default viewer
"ng:w": "n:g:z:service_editor", // default editor "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:crdt": "YText",
"ng:n": "Service - Deno/JS", // edited with CodeMirror, displayed with highlight.js "ng:n": "Service - Deno/JS", // edited with CodeMirror, displayed with highlight.js
"ng:a": "Service written in JS/TS for Deno or NodeJS", "ng:a": "Service written in JS/TS for Deno or NodeJS",
"ng:o": "external_service_invoke", // default viewer "ng:o": "external_service_invoke", // default viewer
"ng:w": "n:g:z:service_editor", // default editor "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": { "contract": {
"ng:crdt": "YText", "ng:crdt": "YText",
"ng:n": "Contract", // edited with CodeMirror, displayed with highlight.js "ng:n": "Contract", // edited with CodeMirror, displayed with highlight.js
"ng:a": "Smart Contract with Rust or JS code", "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: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: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:a": "Saved SPARQL Query that can be invoked",
"ng:o": "n:g:z:sparql:invoke", "ng:o": "n:g:z:sparql:invoke",
"ng:w": "n:g:z:sparql_query:yasgui", "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: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: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:a": "Saved SPARQL Update that can be invoked",
"ng:o": "n:g:z:sparql:invoke", "ng:o": "n:g:z:sparql:invoke",
"ng:w": "n:g:z:sparql_update:yasgui", "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: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: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:a": "Saved GraphQL Query that can be invoked",
"ng:o": "n:g:z:graphql:invoke", "ng:o": "n:g:z:graphql:invoke",
"ng:w": "n:g:z:graphql_query", "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:crdt": "Graph",
"ng:n": "Text Search", "ng:n": "Text Search",
"ng:a": "Saved Text Search and its results", "ng:a": "Saved Text Search and its results",
"ng:compat": [], "ng:compat": [],
}, },
"query/web": { "query:web": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Web Search", "ng:n": "Web Search",
"ng:a": "Saved Web Search and its results", "ng:a": "Saved Web Search and its results",
"ng:compat": [], "ng:compat": [],
}, },
"data/graph": { "data:graph": {
"ng:crdt": "Graph", // https://github.com/highlightjs/highlightjs-turtle/tree/master "ng:crdt": "Graph", // https://github.com/highlightjs/highlightjs-turtle/tree/master
"ng:n": "Graph", "ng:n": "Graph",
"ng:a": "Define the Graph of your data with Semantic Web / Linked Data", "ng:a": "Define the Graph of your data with Semantic Web / Linked Data",
@ -241,47 +241,47 @@ export const official_classes = {
"rdf":true, "rdf":true,
"xsd":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", "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"], "file:iana:application:rdf+xml", "file:iana:application:ld+json"],
}, },
"data/json": { "data:json": {
"ng:crdt": "Automerge", "ng:crdt": "Automerge",
"ng:n": "JSON", "ng:n": "JSON",
"ng:a": "JSON Data CRDT", "ng:a": "JSON Data CRDT",
"ng:o": "n:g:z:json_viewer", // default viewer "ng:o": "n:g:z:json_viewer", // default viewer
"ng:w": "n:g:z:json_editor", // default editor "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:crdt": "YArray",
"ng:n": "JSON Array", "ng:n": "JSON Array",
"ng:a": "JSON Array CRDT", "ng:a": "JSON Array CRDT",
"ng:o": "n:g:z:json_viewer", // default viewer "ng:o": "n:g:z:json_viewer", // default viewer
"ng:w": "n:g:z:json_editor", // default editor "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:crdt": "YMap",
"ng:n": "JSON Map", "ng:n": "JSON Map",
"ng:a": "JSON Map CRDT", "ng:a": "JSON Map CRDT",
"ng:o": "n:g:z:json_viewer", // default viewer "ng:o": "n:g:z:json_viewer", // default viewer
"ng:w": "n:g:z:json_editor", // default editor "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:crdt": "YXml",
"ng:n": "XML", "ng:n": "XML",
"ng:a": "XML Data CRDT", "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: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: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:a": "Data in a Table (columns and rows)",
"ng:o": "n:g:z:pivot", "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:crdt": "Graph",
"ng:n": "Collection", "ng:n": "Collection",
"ng:a": "An ordered list of items", "ng:a": "An ordered list of items",
@ -292,10 +292,10 @@ export const official_classes = {
}, },
"ng:compat": ["as:Collection","rdf:List","rdf:Seq"], "ng:compat": ["as:Collection","rdf:List","rdf:Seq"],
}, },
"data/container": { "data:container": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Container", "ng:n": "Container",
"ng:a": "An unordered list of items", "ng:a": "An unordered set of items",
"ng:o": "n:g:z:list", "ng:o": "n:g:z:list",
"ng:x": { "ng:x": {
"rdf": true, "rdf": true,
@ -304,14 +304,14 @@ export const official_classes = {
}, },
"ng:compat": ["rdfs:member","ldp:contains","rdf:Bag","rdf:Alt"], "ng:compat": ["rdfs:member","ldp:contains","rdf:Bag","rdf:Alt"],
}, },
"data/plato": { "data:plato": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Plato", "ng:n": "Plato",
"ng:a": "A tree of files and folders", "ng:a": "A tree of files and folders",
"ng:o": "n:g:z:tree", "ng:o": "n:g:z:tree",
"ng:compat": ["ng:plato","ng:has_plato"], "ng:compat": ["ng:plato","ng:has_plato"],
}, },
"data/board": { "data:board": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Board", "ng:n": "Board",
"ng:a": "Whiteboard, infinite canvas to arrange your content in 2D", "ng:a": "Whiteboard, infinite canvas to arrange your content in 2D",
@ -319,15 +319,15 @@ export const official_classes = {
"ng:include": [], "ng:include": [],
"ng:compat": [], //https://jsoncanvas.org/ https://www.canvasprotocol.org/ https://github.com/orgs/ocwg/discussions/25 https://infinitecanvas.tools/gallery/ "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:crdt": "Graph",
"ng:n": "Grid", "ng:n": "Grid",
"ng:a": "Grid representation of a collection or container", "ng:a": "Grid representation of a collection or container",
"ng:o": "n:g:z:grid", "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": [], "ng:compat": [],
}, },
"data/geomap": { // https://github.com/leaflet/leaflet "data:geomap": { // https://github.com/leaflet/leaflet
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Geo Map", "ng:n": "Geo Map",
"ng:a": "Geographical Map", "ng:a": "Geographical Map",
@ -336,36 +336,36 @@ export const official_classes = {
"gn": true, "gn": true,
"as": 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:crdt": "Graph",
"ng:n": "Email", "ng:n": "Email",
"ng:a": "Email content and headers", "ng:a": "Email content and headers",
"ng:x": { "ng:x": {
"email": "http://www.invincea.com/ontologies/icas/1.0/email#" //https://raw.githubusercontent.com/twosixlabs/icas-ontology/master/ontology/email.ttl "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", "ng:crdt": "Graph",
//https://www.npmjs.com/package/warcio https://github.com/N0taN3rd/node-warc //https://www.npmjs.com/package/warcio https://github.com/N0taN3rd/node-warc
"ng:n": "Web Archive", "ng:n": "Web Archive",
"ng:a": "Archive the content of a web page", "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:crdt": "Graph",
"ng:n": "RDF Archive", "ng:n": "RDF Archive",
"ng:a": "Archive the triples of an RDF resource dereferenced with HTTP", "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:crdt": "Graph",
"ng:n": "Text Selection", "ng:n": "Text Selection",
"ng:a": "Text Selection copied into Magic Carpet", "ng:a": "Text Selection copied into Magic Carpet",
}, },
"mc/link": { "mc:link": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Link", "ng:n": "Link",
"ng:a": "Link to a document kept in Magic Carpet", "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:a": "Pad representation of a document",
"ng:o": "n:g:z:pad", "ng:o": "n:g:z:pad",
}, },
"doc/compose" : { "doc:compose" : {
"ng:crdt": "YArray", "ng:crdt": "YArray",
"ng:n": "Composition", "ng:n": "Composition",
"ng:a": "Compose several blocks into a single document", "ng:a": "Compose several blocks into a single document",
"ng:o": "n:g:z:compose", "ng:o": "n:g:z:compose",
}, },
"doc/diagram/mermaid" : { "doc:diagram:mermaid" : {
"ng:crdt": "YText", "ng:crdt": "YText",
"ng:n": "Diagram - Mermaid", "ng:n": "Diagram - Mermaid",
"ng:a": "Describe Diagrams with 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:crdt": "YXml",
"ng:n": "Diagram - DrawIo", "ng:n": "Diagram - DrawIo",
"ng:a": "Draw Diagrams with 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:crdt": "YText",
"ng:n": "Diagram - Graphviz", "ng:n": "Diagram - Graphviz",
"ng:a": "Describe Diagrams with 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:crdt": "Automerge",
"ng:n": "Diagram - Excalidraw", "ng:n": "Diagram - Excalidraw",
"ng:a": "Collaborate on Diagrams with 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:crdt": "Automerge",
"ng:n": "Diagram - Gantt", "ng:n": "Diagram - Gantt",
"ng:a": "Interactive gantt chart", "ng:a": "Interactive gantt chart",
"ng:compat": [] "ng:compat": []
}, },
"doc/diagram/flowchart" : { //https://github.com/adrai/flowchart.js "doc:diagram:flowchart" : { //https://github.com/adrai/flowchart.js
"ng:crdt": "YText", "ng:crdt": "YText",
"ng:n": "Diagram - Flowchart", "ng:n": "Diagram - Flowchart",
"ng:a": "flow chart diagrams", "ng:a": "flow chart diagrams",
"ng:compat": [] "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:crdt": "YText",
"ng:n": "Diagram - Sequence", "ng:n": "Diagram - Sequence",
"ng:a": "sequence diagrams", "ng:a": "sequence diagrams",
"ng:compat": [] "ng:compat": []
}, },
// checkout https://www.mindmaps.app/ but it is AGPL // 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:crdt": "YText",
"ng:n": "Diagram - Markmap", "ng:n": "Diagram - Markmap",
"ng:a": "mindmaps with markmap", "ng:a": "mindmaps with markmap",
"ng:compat": [] "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:crdt": "YText", // see MyMind format, MindMup JSON, FreeMind XML and MindMap Architect XML
"ng:n": "Diagram - Mymind", "ng:n": "Diagram - Mymind",
"ng:a": "mindmaps with mymind", "ng:a": "mindmaps with mymind",
"ng:compat": [] // https://github.com/ondras/my-mind/wiki/Saving-and-loading#file-formats "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:crdt": "Automerge",
"ng:n": "Diagram - jsmind", "ng:n": "Diagram - jsmind",
"ng:a": "mindmaps with jsmind", "ng:a": "mindmaps with jsmind",
@ -457,137 +457,137 @@ export const official_classes = {
// https://github.com/Rich-Harris/pancake // https://github.com/Rich-Harris/pancake
// https://github.com/williamngan/pts // https://github.com/williamngan/pts
// https://visjs.org/ // https://visjs.org/
"doc/viz/cytoscape" : { "doc:viz:cytoscape" : {
"ng:crdt": "Automerge", "ng:crdt": "Automerge",
"ng:n": "Viz - Cytoscape", "ng:n": "Viz - Cytoscape",
"ng:a": "Graph theory (network) visualization", "ng:a": "Graph theory (network) visualization",
"ng:compat": [] // https://github.com/cytoscape/cytoscape.js "ng:compat": [] // https://github.com/cytoscape/cytoscape.js
}, },
"doc/viz/vega" : { "doc:viz:vega" : {
"ng:crdt": "Automerge", "ng:crdt": "Automerge",
"ng:n": "Viz - Vega", "ng:n": "Viz - Vega",
"ng:a": "Grammar for interactive graphics", "ng:a": "Grammar for interactive graphics",
"ng:compat": [] // https://vega.github.io/vega-lite/docs/ https://github.com/vega/editor "ng:compat": [] // https://vega.github.io/vega-lite/docs/ https://github.com/vega/editor
}, },
"doc/viz/vizzu" : { "doc:viz:vizzu" : {
"ng:crdt": "Automerge", "ng:crdt": "Automerge",
"ng:n": "Viz - Vizzu", "ng:n": "Viz - Vizzu",
"ng:a": "Animated data visualizations and data stories", "ng:a": "Animated data visualizations and data stories",
"ng:compat": [] // https://github.com/vizzuhq/vizzu-lib "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:crdt": "Automerge",
"ng:n": "Viz - Plotly", "ng:n": "Viz - Plotly",
"ng:a": "Declarative charts", "ng:a": "Declarative charts",
"ng:compat": [] // https://github.com/cytoscape/cytoscape.js "ng:compat": [] // https://github.com/cytoscape/cytoscape.js
}, },
"doc/viz/avail" : { "doc:viz:avail" : {
"ng:crdt": "Automerge", "ng:crdt": "Automerge",
"ng:n": "Viz - Avail", "ng:n": "Viz - Avail",
"ng:a": "Time Data Availability Visualization", "ng:a": "Time Data Availability Visualization",
"ng:compat": [] // https://github.com/flrs/visavail "ng:compat": [] // https://github.com/flrs/visavail
}, },
"doc/chart/frappecharts" : { "doc:chart:frappecharts" : {
"ng:crdt": "Automerge", "ng:crdt": "Automerge",
"ng:n": "Charts - Frappe", "ng:n": "Charts - Frappe",
"ng:a": "GitHub-inspired responsive charts", "ng:a": "GitHub-inspired responsive charts",
"ng:compat": [] // https://github.com/frappe/charts "ng:compat": [] // https://github.com/frappe/charts
}, },
"doc/chart/financial" : { "doc:chart:financial" : {
"ng:crdt": "Automerge", "ng:crdt": "Automerge",
"ng:n": "Charts - Financial", "ng:n": "Charts - Financial",
"ng:a": "Financial charts", "ng:a": "Financial charts",
"ng:compat": [] //https://github.com/tradingview/lightweight-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 // 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:crdt": "Automerge",
"ng:n": "Charts - ApexCharts", "ng:n": "Charts - ApexCharts",
"ng:a": "Interactive data visualizations", "ng:a": "Interactive data visualizations",
"ng:compat": [] // https://github.com/apexcharts/apexcharts.js "ng:compat": [] // https://github.com/apexcharts/apexcharts.js
}, },
//realtime data with https://github.com/square/cubism //realtime data with https://github.com/square/cubism
"doc/chart/billboard" : { "doc:chart:billboard" : {
"ng:crdt": "Automerge", "ng:crdt": "Automerge",
"ng:n": "Charts - BillBoard", "ng:n": "Charts - BillBoard",
"ng:a": "Interactive data visualizations based on D3", "ng:a": "Interactive data visualizations based on D3",
"ng:compat": [] // https://github.com/naver/billboard.js "ng:compat": [] // https://github.com/naver/billboard.js
}, },
"doc/chart/echarts" : { "doc:chart:echarts" : {
"ng:crdt": "Automerge", "ng:crdt": "Automerge",
"ng:n": "Charts - ECharts", "ng:n": "Charts - ECharts",
"ng:a": "Interactive charting and data visualization with Apache ECharts", "ng:a": "Interactive charting and data visualization with Apache ECharts",
"ng:compat": [] // https://github.com/apache/echarts "ng:compat": [] // https://github.com/apache/echarts
}, },
"doc/chart/chartjs" : { "doc:chart:chartjs" : {
"ng:crdt": "Automerge", "ng:crdt": "Automerge",
"ng:n": "Charts - Chart.js", "ng:n": "Charts - Chart.js",
"ng:a": "Simple yet flexible charting for designers & developers with Chart.js", "ng:a": "Simple yet flexible charting for designers & developers with Chart.js",
"ng:compat": [] // https://github.com/chartjs/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 // 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:crdt": "Graph",
"ng:n": "PDF", "ng:n": "PDF",
"ng:a": "upload and display a PDF file", "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:crdt": "Graph",
"ng:n": "OpenDocumentFormat (ODF)", "ng:n": "OpenDocumentFormat (ODF)",
"ng:a": "upload and display an ODF file", "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 // see also https://github.com/Mathpix/mathpix-markdown-it
"doc/latex": { "doc:latex": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Latex", "ng:n": "Latex",
"ng:a": "upload and display a Latex or Tex file", "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:crdt": "Graph",
"ng:n": "Postscript", "ng:n": "Postscript",
"ng:a": "upload and display a PostScript file", "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:crdt": "YText",
"ng:n": "Music ABC", "ng:n": "Music ABC",
"ng:a": "sheet music notation", "ng:a": "sheet music notation",
"ng:compat": [] "ng:compat": []
}, },
"doc/music/guitar": { //https://github.com/birdca/fretboard "doc:music:guitar": { //https://github.com/birdca/fretboard
"ng:crdt": "YText", "ng:crdt": "YText",
"ng:n": "Music - Guitar", "ng:n": "Music - Guitar",
"ng:a": "charts for guitar chords and scales", "ng:a": "charts for guitar chords and scales",
"ng:compat": [] "ng:compat": []
}, },
"doc/maths": { //https://github.com/KaTeX/KaTeX "doc:maths": { //https://github.com/KaTeX/KaTeX
"ng:crdt": "YText", "ng:crdt": "YText",
"ng:n": "Maths", "ng:n": "Maths",
"ng:a": "TeX math rendering", "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:crdt": "YText",
"ng:n": "Chemical", "ng:n": "Chemical",
"ng:a": "simplified molecular-input line-entry system (SMILES)", "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:crdt": "YText", // use Unicode and special fonts
"ng:n": "Ancient Script", "ng:n": "Ancient Script",
"ng:a": "Ancient Script", "ng:a": "Ancient Script",
"ng:compat": [] "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:crdt": "YText", // use Unicode and special fonts
"ng:n": "Braille Patterns", "ng:n": "Braille Patterns",
"ng:a": "Braille Patterns", "ng:a": "Braille Patterns",
"ng:compat": [] "ng:compat": []
}, },
"media/image": { "media:image": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Image", "ng:n": "Image",
"ng:a": "upload and display an image", "ng:a": "upload and display an image",
@ -595,16 +595,16 @@ export const official_classes = {
"ng:x": { "ng:x": {
"as":true, "as":true,
}, },
"ng:compat": ["file/iana/image*","as:Image"] "ng:compat": ["file:iana:image*","as:Image"]
}, },
"media/reel": { "media:reel": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Reel", "ng:n": "Reel",
"ng:a": "upload and display a Reel (video from mobile)", "ng:a": "upload and display a Reel (video from mobile)",
"ng:o": "n:g:z:media", "ng:o": "n:g:z:media",
"ng:compat": ["file/iana/video*"] "ng:compat": ["file:iana:video*"]
}, },
"media/video": { "media:video": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Video", "ng:n": "Video",
"ng:a": "upload and display a Video (and film)", "ng:a": "upload and display a Video (and film)",
@ -612,17 +612,17 @@ export const official_classes = {
"ng:x": { "ng:x": {
"as":true, "as":true,
}, },
"ng:compat": ["file/iana/video*","as:Video"] "ng:compat": ["file:iana:video*","as:Video"]
}, },
"media/album": { "media:album": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Album", "ng:n": "Album",
"ng:a": "Assemble several images and/or videos into an ordered Album", "ng:a": "Assemble several images and/or videos into an ordered Album",
"ng:o": "n:g:z:gallery", "ng:o": "n:g:z:gallery",
"ng:include": ["data/collection"], "ng:include": ["data:collection"],
"ng:compat": [] "ng:compat": []
}, },
"media/audio": { "media:audio": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Audio", "ng:n": "Audio",
"ng:a": "upload and play an Audio file, Audio note or Voice message", "ng:a": "upload and play an Audio file, Audio note or Voice message",
@ -630,9 +630,9 @@ export const official_classes = {
"ng:x": { "ng:x": {
"as":true, "as":true,
}, },
"ng:compat": ["file/iana/audio*","as:Audio"] "ng:compat": ["file:iana:audio*","as:Audio"]
}, },
"media/song": { "media:song": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Song", "ng:n": "Song",
"ng:a": "A song from an artist,album and/or lyrics", "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 // 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 // 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:crdt": "YText",
"ng:n": "Subtitles", "ng:n": "Subtitles",
"ng:a": "Subtitles", "ng:a": "Subtitles",
"ng:compat": [] // TBD "ng:compat": [] // TBD
}, },
"media/overlay": { "media:overlay": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Overlay", "ng:n": "Overlay",
"ng:a": "Composition of an image, reel, text, icon, link, mention or other content into a layered content", "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:o": "n:g:z:media",
"ng:compat": [] "ng:compat": []
}, },
"social/activity": { "social:activity": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Activity", "ng:n": "Activity",
"ng:a": "Activity sent in a Stream", "ng:a": "Activity sent in a Stream",
@ -666,19 +666,19 @@ export const official_classes = {
}, },
"ng:compat": ["as:Activity"] "ng:compat": ["as:Activity"]
}, },
"social/channel": { "social:channel": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Channel", "ng:n": "Channel",
"ng:a": "Broadcast channel with subscribers", "ng:a": "Broadcast channel with subscribers",
"ng:compat": [] "ng:compat": []
}, },
"social/stream": { "social:stream": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Stream", "ng:n": "Stream",
"ng:a": "A document or store's stream branch", "ng:a": "A document or store's stream branch",
"ng:compat": [] "ng:compat": []
}, },
"social/contact": { "social:contact": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Contact", "ng:n": "Contact",
"ng:a": "Contact: an Individual, Organization or Group", "ng:a": "Contact: an Individual, Organization or Group",
@ -686,20 +686,20 @@ export const official_classes = {
"vcard":true, "vcard":true,
"foaf": true, "foaf": true,
}, },
"ng:include": ["data/graph"], "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: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:crdt": "Graph",
"ng:n": "Event", "ng:n": "Event",
"ng:a": "An event occuring in specific location and time", "ng:a": "An event occuring in specific location and time",
"ng:x": { "ng:x": {
"as":true, "as":true,
}, },
"ng:include": ["post/*"], "ng:include": ["post:*"],
"ng:compat": ["as:Event"] "ng:compat": ["as:Event"]
}, },
"social/calendar": { "social:calendar": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Calendar", "ng:n": "Calendar",
"ng:a": "A calendar where events are gathered", "ng:a": "A calendar where events are gathered",
@ -707,10 +707,10 @@ export const official_classes = {
"as":true, "as":true,
"time": true, "time": true,
}, },
"ng:include": ["data/collection"], "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 "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": { "social:scheduler": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Scheduler", "ng:n": "Scheduler",
"ng:a": "Helps finding a common time slot for several participants to a future event", "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"] "ng:compat": ["as:Invite","as:Reject","as:Accept","as:TentativeAccept","as:TentativeReject"]
}, },
"social/reaction": { "social:reaction": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Reaction", "ng:n": "Reaction",
"ng:a": "A reaction by user to some content", "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"] "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:crdt": "Graph",
"ng:n": "Task", "ng:n": "Task",
"ng:a": "A task to be done", "ng:a": "A task to be done",
@ -736,12 +741,12 @@ export const official_classes = {
"as":true, "as":true,
"pair": "http://virtual-assembly.org/ontologies/pair#", "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 "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 // 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 // https://framagit.org/framasoft/mobilizon/-/blob/main/lib/federation/activity_stream/converter/todo_list.ex
}, },
"prod/project": { "prod:project": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Project", "ng:n": "Project",
"ng:a": "A project management / KanBan", "ng:a": "A project management / KanBan",
@ -749,7 +754,7 @@ export const official_classes = {
"as":true, "as":true,
"pair": "http://virtual-assembly.org/ontologies/pair#", "pair": "http://virtual-assembly.org/ontologies/pair#",
}, },
"ng:include": ["post/*"], "ng:include": ["post:*"],
"ng:compat": ["pair:Project"] "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 // 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 /// svelte: https://github.com/V-Py/svelte-kanban
// https://github.com/supabase-community/svelte-kanban // https://github.com/supabase-community/svelte-kanban
// https://github.com/therosbif/kanban // https://github.com/therosbif/kanban
"prod/issue": { "prod:issue": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Issue", "ng:n": "Issue",
"ng:a": "An issue to be solved", "ng:a": "An issue to be solved",
@ -777,19 +782,19 @@ export const official_classes = {
"as":true, "as":true,
"pair": "http://virtual-assembly.org/ontologies/pair#", "pair": "http://virtual-assembly.org/ontologies/pair#",
}, },
"ng:include": ["prod/task"], "ng:include": ["prod:task"],
"ng:compat": ["pair:Challenge"] "ng:compat": ["pair:Challenge"]
}, },
//https://github.com/go-gitea/gitea/issues/20232 //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 // 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:crdt": "Graph",
"ng:n": "Form", "ng:n": "Form",
"ng:a": "A form to be filled-in", "ng:a": "A form to be filled-in",
"ng:x": { "ng:x": {
"form" : "http://rdf.danielbeeke.nl/form/form-dev.ttl#", "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://jsonforms.io/docs/
// https://github.com/jsonform/jsonform // https://github.com/jsonform/jsonform
@ -804,57 +809,57 @@ export const official_classes = {
// https://www.mediawiki.org/wiki/Extension:Page_Forms // https://www.mediawiki.org/wiki/Extension:Page_Forms
// https://rdf-form.danielbeeke.nl/ // https://rdf-form.danielbeeke.nl/
// consider using Shapes // consider using Shapes
"prod/filling": { "prod:filling": {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Form filling", "ng:n": "Form filling",
"ng:a": "A form that has been filled-in", "ng:a": "A form that has been filled-in",
"ng:compat": [] "ng:compat": []
}, },
"prod/cad": { // https://mattferraro.dev/posts/cadmium "prod:cad": { // https://mattferraro.dev/posts/cadmium
"ng:crdt": "Automerge", "ng:crdt": "Automerge",
"ng:n": "CAD", "ng:n": "CAD",
"ng:a": "CADmium", "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 //https://pandoc.org/MANUAL.html#slide-shows
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Slides", "ng:n": "Slides",
"ng:a": "Slides and presentations", "ng:a": "Slides and presentations",
"ng:include": ["post/*"], "ng:include": ["post:*"],
"ng:compat": [] "ng:compat": []
}, },
"prod/question" : { "prod:question" : {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Question", "ng:n": "Question",
"ng:a": "A question that needs answers", "ng:a": "A question that needs answers",
"ng:x": { "ng:x": {
"as":true, "as":true,
}, },
"ng:include": ["post/*"], "ng:include": ["post:*"],
"ng:compat": ["as:Question"] "ng:compat": ["as:Question"]
}, },
"prod/answer" :{ "prod:answer" :{
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Answer", "ng:n": "Answer",
"ng:a": "An answer to a question", "ng:a": "An answer to a question",
"ng:x": { "ng:x": {
"as":true, "as":true,
}, },
"ng:include": ["post/*"], "ng:include": ["post:*"],
"ng:compat": ["as:Note"] "ng:compat": ["as:Note"]
}, },
"prod/poll" : { "prod:poll" : {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Poll", "ng:n": "Poll",
"ng:a": "A poll where people will vote", "ng:a": "A poll where people will vote",
"ng:x": { "ng:x": {
"as":true, "as":true,
}, },
"ng:include": ["post/*"], "ng:include": ["post:*"],
"ng:compat": ["as:Question"] "ng:compat": ["as:Question"]
}, },
"prod/vote" : { "prod:vote" : {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "Vote", "ng:n": "Vote",
"ng:a": "A vote cast for a Poll", "ng:a": "A vote cast for a Poll",
@ -870,31 +875,31 @@ export const official_classes = {
"ng:o": "n:g:z:file_viewer", "ng:o": "n:g:z:file_viewer",
"ng:compat": [] "ng:compat": []
}, },
"file/ng/wallet" : { "file:ng:wallet" : {
"ng:n": "NextGraph Wallet File", "ng:n": "NextGraph Wallet File",
"ng:a": "NextGraph Wallet File (.ngw)", "ng:a": "NextGraph Wallet File (.ngw)",
"ng:compat": [] "ng:compat": []
}, },
"file/ng/doc" : { "file:ng:doc" : {
"ng:n": "NextGraph Document File", "ng:n": "NextGraph Document File",
"ng:a": "NextGraph Document File (.ngd)", "ng:a": "NextGraph Document File (.ngd)",
"ng:compat": [] "ng:compat": []
}, },
"file/ng/html" : { "file:ng:html" : {
"ng:n": "NextGraph Document Html", "ng:n": "NextGraph Document Html",
"ng:a": "NextGraph Document Html standalone file", "ng:a": "NextGraph Document Html standalone file",
"ng:compat": [] "ng:compat": []
}, },
"file/text" : { "file:text" : {
"ng:crdt": "Graph", "ng:crdt": "Graph",
"ng:n": "File", "ng:n": "File",
"ng:a": "Text file", "ng:a": "Text file",
"ng:o": "n:g:z:file_viewer", "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", "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: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: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: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" ] "file:iana:application:schema+json", "file:iana:application:geo+json", "file:iana:application:json" ]
}, },
}; };

@ -65,7 +65,8 @@
Bolt, Bolt,
Heart, Heart,
Cog, Cog,
Square3Stack3d Square3Stack3d,
ChatBubbleLeftRight,
} from "svelte-heros-v2"; } from "svelte-heros-v2";
export let config = {}; export let config = {};
@ -74,64 +75,65 @@
const exact_mapping = { const exact_mapping = {
page: Window, page: Window,
"app": Cog, "app": Cog,
"app/z": SquaresPlus, "app:z": SquaresPlus,
class: ViewfinderCircle, class: ViewfinderCircle,
contract: Briefcase, contract: Briefcase,
"query/text": MagnifyingGlass, "query:text": MagnifyingGlass,
"query/web": MagnifyingGlass, "query:web": MagnifyingGlass,
"data/graph": Sun, "data:graph": Sun,
"data/table": TableCells, "data:table": TableCells,
"data/collection": ListBullet, "data:collection": ListBullet,
"data/container": Square3Stack3d, "data:container": Square3Stack3d,
"data/board": RectangleGroup, "data:board": RectangleGroup,
"data/grid": Squares2x2, "data:grid": Squares2x2,
"data/geomap": MapPin, "data:geomap": MapPin,
"e/email": Envelope, "e:email": Envelope,
"mc/text": Bars3BottomLeft, "mc:text": Bars3BottomLeft,
"mc/link": Link, "mc:link": Link,
"plato/card": Clipboard, "plato/card": Clipboard,
"plato/pad": Square2Stack, "plato/pad": Square2Stack,
"media/image": Photo, "media:image": Photo,
"media/reel": Film, "media:reel": Film,
"media/video": Film, "media:video": Film,
"media/album": RectangleStack, "media:album": RectangleStack,
"media/audio": Microphone, "media:audio": Microphone,
"media/song": MusicalNote, "media:song": MusicalNote,
"media/subtitle": Ticket, "media:subtitle": Ticket,
"media/overlay": CursorArrowRays, "media:overlay": CursorArrowRays,
"social/channel": Megaphone, "social:channel": Megaphone,
"social/stream": Bolt, "social:stream": Bolt,
"social/contact": User, "social:contact": User,
"social/event": Clock, "social:event": Clock,
"social/calendar": CalendarDays, "social:calendar": CalendarDays,
"social/scheduler": Calendar, "social:scheduler": Calendar,
"social/reaction": Heart, "social:reaction": Heart,
"prod/task": Stop, "social:chatroom": ChatBubbleLeftRight,
"prod/project": Flag, "prod:task": Stop,
"prod/issue": HandRaised, "prod:project": Flag,
"prod/form": Newspaper, "prod:issue": HandRaised,
"prod/filling": PencilSquare, "prod:form": Newspaper,
"prod/cad": CubeTransparent, "prod:filling": PencilSquare,
"prod/slides": PresentationChartBar, "prod:cad": CubeTransparent,
"prod/question": QuestionMarkCircle, "prod:slides": PresentationChartBar,
"prod/answer": CheckCircle, "prod:question": QuestionMarkCircle,
"prod/poll": QuestionMarkCircle, "prod:answer": CheckCircle,
"prod/vote": CheckCircle, "prod:poll": QuestionMarkCircle,
"prod:vote": CheckCircle,
}; };
const prefix_mapping = { const prefix_mapping = {
"post/": DocumentText, "post:": DocumentText,
code: CodeBracket, code: CodeBracket,
schema: ArrowsPointingOut, schema: ArrowsPointingOut,
service: Cube, service: Cube,
"e/": GlobeAlt, "e:": GlobeAlt,
"app/": StopCircle, "app:": StopCircle,
"query/": RocketLaunch, "query:": RocketLaunch,
"data/": CircleStack, "data:": CircleStack,
"doc/diagram": DocumentChartBar, "doc:diagram": DocumentChartBar,
"doc/chart": ChartPie, "doc:chart": ChartPie,
"doc/viz": ChartPie, "doc:viz": ChartPie,
"doc/": ClipboardDocumentList, "doc:": ClipboardDocumentList,
file: Document, file: Document,
}; };
@ -147,7 +149,7 @@
<!-- <!--
did:ng:n:g:z:[official apps] 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 did:ng:n:g:x list of context used by nextgraph
rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns# rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#
rdfs: http://www.w3.org/2000/01/rdf-schema# 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# geo: http://www.opengis.net/ont/geosparql#
time: http://www.w3.org/2006/time# 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 did:ng:n:g:x:ng#post:rich
ng:class => shortcut for did:ng:n:g:ns#class ng:class => shortcut for did:ng:n:g:x:ng#class
a rdfs:Class a rdfs:Class
a ng:class a ng:class
did:ng:o:xxxx:yy:yy did:ng:o:xxxx:yy:yy
did:ng:n:xx.xx#name 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:k common list of things (keyword)
did:ng:n:c common data did:ng:n:g:a common data
did:ng:n:z: curated list of external apps and services did:ng:z: curated list of external apps and services (app store)
http://nextgraph.org/ns# => the ng: ontology (did:ng:n:g:ns#) 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) 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, Modal,
Toggle, Toggle,
} from "flowbite-svelte"; } from "flowbite-svelte";
import { link, location } from "svelte-spa-router"; import { link, location, push } from "svelte-spa-router";
import MobileBottomBarItem from "./MobileBottomBarItem.svelte"; import MobileBottomBarItem from "./MobileBottomBarItem.svelte";
import MobileBottomBar from "./MobileBottomBar.svelte"; import MobileBottomBar from "./MobileBottomBar.svelte";
import NavIcon from "./components/NavIcon.svelte"; import NavIcon from "./components/NavIcon.svelte";
@ -30,9 +30,12 @@
// @ts-ignore // @ts-ignore
import { t } from "svelte-i18n"; import { t } from "svelte-i18n";
import { onMount, tick } from "svelte"; 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, 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 ZeraIcon from "./ZeraIcon.svelte";
import { import {
@ -81,6 +84,7 @@
PaperClip, PaperClip,
XMark, XMark,
ArrowLeft, ArrowLeft,
ArchiveBox,
} from "svelte-heros-v2"; } from "svelte-heros-v2";
import NavBar from "./components/NavBar.svelte"; import NavBar from "./components/NavBar.svelte";
@ -100,7 +104,7 @@
mobile = true; mobile = true;
} }
let panes_available = 0; let panes_available;
$: if (width < 983) { $: if (width < 983) {
panes_available = 0; panes_available = 0;
} else if (width >= 983 && width < 1304) { } else if (width >= 983 && width < 1304) {
@ -128,11 +132,12 @@
pane_left2_used = false; pane_left2_used = false;
pane_right_used = false; pane_right_used = false;
if ($cur_tab.right_pane || $cur_tab.folders_pane || $cur_tab.toc_pane) { 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) { $: if (panes_available > 0) {
$cur_tab.show_modal_menu = false; if ($show_modal_menu && !$cur_tab.show_menu) {
$show_modal_menu = false;
} }
if (panes_available == 1) { if (panes_available == 1) {
if ($cur_tab.right_pane) { if ($cur_tab.right_pane) {
@ -208,18 +213,24 @@
let toolsMenu; let toolsMenu;
async function scrollToTop() { async function scrollToTop() {
await tick(); await tick();
top.scrollIntoView(); if (top) top.scrollIntoView();
} }
async function scrollToMenuShare() { async function scrollToMenuShare() {
await tick(); await tick();
shareMenu.scrollIntoView(); if (shareMenu) shareMenu.scrollIntoView();
} }
async function scrollToMenuTools() { async function scrollToMenuTools() {
await tick(); 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; $: activeUrl = "#" + $location;
@ -234,38 +245,41 @@
} }
const openPane = (pane:string) => { const openPane = (pane:string) => {
// TODO cur_tab_update((ct) => {
if ( pane == "folders") { if ( pane == "folders") {
$cur_tab.folders_pane = !$cur_tab.folders_pane; ct.folders_pane = !ct.folders_pane;
if ($cur_tab.folders_pane) { if (ct.folders_pane) {
if (panes_available <= 1 ) { if (panes_available <= 1 ) {
$cur_tab.right_pane = ""; ct.right_pane = "";
}
} }
} } else if ( pane == "toc") {
} else if ( pane == "toc") { ct.toc_pane = !ct.toc_pane;
$cur_tab.toc_pane = !$cur_tab.toc_pane; if (ct.toc_pane) {
if ($cur_tab.toc_pane) { if (panes_available <= 1 ) {
if (panes_available <= 1 ) { ct.folders_pane = false;
$cur_tab.folders_pane = false; ct.right_pane = "";
$cur_tab.right_pane = ""; } else if (panes_available == 2) {
} else if (panes_available == 2) { if (ct.folders_pane && ct.right_pane)
if ($cur_tab.folders_pane && $cur_tab.right_pane) ct.folders_pane = false;
$cur_tab.folders_pane = false; }
}
} else {
if (ct.right_pane == pane)
ct.right_pane = "";
else {
ct.right_pane = pane;
} }
} }
} else { if (panes_available > 0) {
if ($cur_tab.right_pane == pane) ct.show_menu = false;
$cur_tab.right_pane = ""; $show_modal_menu = false;
else { } else {
$cur_tab.right_pane = pane; $show_modal_menu = true;
ct.show_menu = false;
} }
} return ct;
if (panes_available) { });
hideMenu();
} else {
$cur_tab.show_modal_menu = true;
$cur_tab.show_menu = false;
}
} }
const openShare = (share:string) => { const openShare = (share:string) => {
@ -288,25 +302,30 @@
hideMenu(); hideMenu();
} }
const openArchive = (share:string) => {
// TODO
hideMenu();
}
const closeModal = () => { const closeModal = () => {
cur_tab.update(ct => { $show_modal_menu = false;
cur_tab_update(ct => {
ct.show_menu = false; ct.show_menu = false;
ct.show_modal_menu = false; if (panes_available === 0) {
if (!panes_available) { ct.right_pane = "";
$cur_tab.right_pane = ""; ct.folders_pane = false;
$cur_tab.folders_pane = false; ct.toc_pane = false;
$cur_tab.toc_pane = false;
} }
return ct; return ct;
}); });
} }
const closePaneInModal = () => { const closePaneInModal = () => {
cur_tab.update(ct => { cur_tab_update(ct => {
ct.show_menu = true; ct.show_menu = true;
$cur_tab.right_pane = ""; ct.right_pane = "";
$cur_tab.folders_pane = false; ct.folders_pane = false;
$cur_tab.toc_pane = false; ct.toc_pane = false;
return ct; return ct;
}); });
} }
@ -357,7 +376,7 @@
<Modal id="menu-modal" <Modal id="menu-modal"
outsideclose outsideclose
bind:open={$cur_tab.show_modal_menu} bind:open={$show_modal_menu}
size = 'xs' size = 'xs'
placement = 'top-right' placement = 'top-right'
backdropClass="bg-gray-900 bg-opacity-50 dark:bg-opacity-80 menu-bg-modal" 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"> <aside style="width:305px; padding:5px;" class="bg-white" aria-label="Sidebar">
<div class="bg-gray-60 overflow-y-auto dark:bg-gray-800"> <div class="bg-gray-60 overflow-y-auto dark:bg-gray-800">
<ul class="space-y-1 space-x-0 mb-10"> <ul class="space-y-1 space-x-0 mb-10">
{#if $cur_branch_has_discrete} {#if $cur_tab.branch.has_discrete}
<li> <li>
<div class="inline-flex graph-discrete-toggle mb-2 ml-2" role="group"> <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" > <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> </li>
{/if} {/if}
{:else} {:else}
<MenuItem clickable={()=>launchAppStore($cur_tab.cur_branch.class)}> <MenuItem clickable={()=>launchAppStore($cur_tab.branch.class)}>
<ZeraIcon <ZeraIcon
zera="app_store" zera="app_store"
config={{tabindex:"-1", config={{tabindex:"-1",
@ -478,123 +497,133 @@
</MenuItem> </MenuItem>
{/if} {/if}
<MenuItem title={$t("doc.menu.items.mc.desc")} selected={$cur_tab.right_pane == "mc"} clickable={ ()=> openPane("mc") }> {#if $cur_tab.branch.id}
<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"]} /> <MenuItem title={$t("doc.menu.items.folders.desc")} selected={$cur_tab.folders_pane} clickable={ ()=> openPane("folders") }>
<span class="ml-3">{$t("doc.menu.items.mc.label")}</span> <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"]} />
</MenuItem> <span class="ml-3">{$t("doc.menu.items.folders.label")}</span>
</MenuItem>
<MenuItem title={$t("doc.menu.items.folders.desc")} selected={$cur_tab.folders_pane} clickable={ ()=> openPane("folders") }> <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["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["toc"]} />
<span class="ml-3">{$t("doc.menu.items.folders.label")}</span> <span class="ml-3">{$t("doc.menu.items.toc.label")}</span>
</MenuItem> </MenuItem>
<MenuItem title={$t("doc.menu.items.toc.desc")} selected={$cur_tab.toc_pane} clickable={ ()=> openPane("toc") }> <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["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["files"]} />
<span class="ml-3">{$t("doc.menu.items.toc.label")}</span> <span class="ml-3">{$t("doc.menu.items.files.label")} {$all_files_count}</span>
</MenuItem> </MenuItem>
<MenuItem title={$t("doc.menu.items.files.desc")} selected={$cur_tab.right_pane == "files"} clickable={ ()=> openPane("files") }> <div style="padding:0;" bind:this={shareMenu}></div>
<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"]} /> <MenuItem title={$t("doc.menu.items.share.desc")} clickable={ () => { open_share = !open_share; scrollToMenuShare(); } }>
<span class="ml-3">{$t("doc.menu.items.files.label")} {$all_files_count}</span> <Share
</MenuItem> tabindex="-1"
<div style="padding:0;" bind:this={shareMenu}></div> class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
<MenuItem title={$t("doc.menu.items.share.desc")} clickable={ () => { open_share = !open_share; scrollToMenuShare(); } }> />
<Share <span class="ml-3">{$t("doc.menu.items.share.label")}</span>
tabindex="-1" </MenuItem>
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" {#if open_share }
/> {#each share_items as share}
<span class="ml-3">{$t("doc.menu.items.share.label")}</span> <MenuItem title={$t(`doc.menu.items.${share.n}.desc`)} extraClass="submenu" clickable={ () => openShare(share.n) }>
</MenuItem> <Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white " variation="outline" color="currentColor" icon={share.i} />
{#if open_share } <span class="ml-3">{$t(`doc.menu.items.${share.n}.label`)}</span>
{#each share_items as share} </MenuItem>
<MenuItem title={$t(`doc.menu.items.${share.n}.desc`)} extraClass="submenu" clickable={ () => openShare(share.n) }> {/each}
<Icon tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white " variation="outline" color="currentColor" icon={share.i} /> {/if}
<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") }> <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"]} /> <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> <span class="ml-3">{$t("doc.menu.items.comments.label")} {$all_comments_count}</span>
</MenuItem> </MenuItem>
<MenuItem title={$t("doc.menu.items.branches.desc")} selected={$cur_tab.right_pane == "branches"} clickable={ ()=> openPane("branches") }> {#if $cur_tab.doc.is_member}
<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"]} /> <MenuItem title={$t("doc.menu.items.branches.desc")} selected={$cur_tab.right_pane == "branches"} clickable={ ()=> openPane("branches") }>
<span class="ml-3">{$t("doc.menu.items.branches.label")}</span> <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"]} />
</MenuItem> <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") }> <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"]} /> <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> <span class="ml-3">{$t("doc.menu.items.history.label")}</span>
</MenuItem> </MenuItem>
<MenuItem title={$t("doc.menu.items.find.desc")} clickable={ find }> <MenuItem title={$t("doc.menu.items.find.desc")} clickable={ find }>
<MagnifyingGlass <MagnifyingGlass
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" 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> <span class="ml-3">{$t("doc.menu.items.find.label")}</span>
</MenuItem> </MenuItem>
<MenuItem title={$t("doc.menu.items.bookmark.desc")} clickable={ bookmark }> <MenuItem title={$t("doc.menu.items.bookmark.desc")} clickable={ bookmark }>
<Bookmark <Bookmark
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" 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> <span class="ml-3">{$t("doc.menu.items.bookmark.label")}</span>
</MenuItem> </MenuItem>
<MenuItem title={$t("doc.menu.items.annotate.desc")} clickable={ annotate }> <MenuItem title={$t("doc.menu.items.annotate.desc")} clickable={ annotate }>
<ChatBubbleLeftEllipsis <ChatBubbleLeftEllipsis
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" 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> <span class="ml-3">{$t("doc.menu.items.annotate.label")}</span>
</MenuItem> </MenuItem>
<MenuItem title={$t("doc.menu.items.info.desc")} selected={$cur_tab.right_pane == "info"} clickable={ ()=> openPane("info") }> <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"]} /> <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> <span class="ml-3">{$t("doc.menu.items.info.label")}</span>
</MenuItem> </MenuItem>
<MenuItem title={$t("doc.menu.items.notifs.desc")} clickable={ ()=> openAction("notifs") }> <MenuItem title={$t("doc.menu.items.notifs.desc")} clickable={ ()=> openAction("notifs") }>
<Bell <Bell
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" 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> <span class="ml-3">{$t("doc.menu.items.notifs.label")}</span>
</MenuItem> </MenuItem>
{#if $cur_tab.doc.is_member} {#if $cur_tab.doc.is_member}
<MenuItem title={$t("doc.menu.items.permissions.desc")} clickable={ ()=> openAction("permissions") }> <MenuItem title={$t("doc.menu.items.permissions.desc")} clickable={ ()=> openAction("permissions") }>
<LockOpen <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" tabindex="-1"
class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white" 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> </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} {/if}
<MenuItem title={$t("doc.menu.items.settings.desc")} clickable={ ()=> openAction("settings") }> <MenuItem title={$t("doc.menu.items.mc.desc")} selected={$cur_tab.right_pane == "mc"} clickable={ ()=> openPane("mc") }>
<Cog6Tooth <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"]} />
tabindex="-1" <span class="ml-3">{$t("doc.menu.items.mc.label")}</span>
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> </MenuItem>
<div style="padding:0;" bind:this={toolsMenu}></div> <MenuItem title={$t("doc.menu.items.archive.desc")} selected={$cur_tab.right_pane == "mc"} clickable={ ()=> openArchive() }>
<MenuItem title={$t("doc.menu.items.tools.desc")} clickable={ () => {open_tools = !open_tools; scrollToMenuTools();} } > <ArchiveBox
<WrenchScrewdriver tabindex="-1"
tabindex="-1" class="w-7 h-7 text-gray-700 focus:outline-none dark:text-white"
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>
<span class="ml-3">{$t("doc.menu.items.tools.label")}</span>
</MenuItem> </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> </ul>
</div> </div>
</aside> </aside>
@ -621,7 +650,7 @@
{#if mobile} {#if mobile}
<div class="full-layout"> <div class="full-layout">
{#if !withoutNavBar} {#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}/> <NavBar {scrollToTop}/>
</div> </div>
{/if} {/if}
@ -637,15 +666,14 @@
13 13
</span> </span>
</MobileBottomBarItem> </MobileBottomBarItem>
<MobileBottomBarItem href="#/stream" icon={Bolt} on:click={scrollToTop} /> <MobileBottomBarItem href="#/stream" icon={Bolt} />
<MobileBottomBarItem <MobileBottomBarItem
href="#/search" href="#/search"
icon={MagnifyingGlass} icon={MagnifyingGlass}
on:click={scrollToTop}
/> />
<MobileBottomBarItem href="#/create" icon={PlusCircle} /> <MobileBottomBarItem href="#/create" icon={PlusCircle} />
<MobileBottomBarItem href="#/site" icon={User} on:click={scrollToTop} /> <MobileBottomBarItem href="#/shared" icon={Users} on:click={scrollToTop} />
</MobileBottomBar> </MobileBottomBar>
</div> </div>
{:else} {:else}
@ -712,6 +740,7 @@
<SidebarItem <SidebarItem
label={$t("pages.full_layout.shared")} label={$t("pages.full_layout.shared")}
href="#/shared" href="#/shared"
on:click={scrollToTop} on:keypress={scrollToTop}
class="py-1 tall-xs:p-2" class="py-1 tall-xs:p-2"
> >
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
@ -724,6 +753,7 @@
<SidebarItem <SidebarItem
label={$t("pages.full_layout.site")} label={$t("pages.full_layout.site")}
href="#/site" href="#/site"
on:click={scrollToTop} on:keypress={scrollToTop}
class="py-1 tall-xs:p-2" class="py-1 tall-xs:p-2"
> >
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
@ -790,7 +820,7 @@
</div> </div>
{/if} {/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="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}/> <NavBar {scrollToTop}/>
</div> </div>
<div bind:this={top}></div> <div bind:this={top}></div>

@ -10,16 +10,28 @@
--> -->
<script lang="ts"> <script lang="ts">
import { onMount, tick } from "svelte";
import { t } from "svelte-i18n";
import FullLayout from "./FullLayout.svelte"; 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 { import {
PaperAirplane, PaperAirplane,
Bell, Bell,
ArrowRightOnRectangle, ArrowRightOnRectangle,
Users, User,
Bookmark,
Sparkles,
Square3Stack3d,
ArchiveBox,
} from "svelte-heros-v2"; } from "svelte-heros-v2";
import Logo from "./components/Logo.svelte"; import Logo from "./components/Logo.svelte";
import NavBar from "./components/NavBar.svelte"; import NavBar from "./components/NavBar.svelte";
let top; let top;
let width: number; let width: number;
@ -34,6 +46,12 @@
function scrollToTop() { function scrollToTop() {
top.scrollIntoView(); 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> </script>
<FullLayout withoutNavBar={true}> <FullLayout withoutNavBar={true}>
@ -52,8 +70,8 @@
> >
</a> </a>
<div class="w-auto flex row"> <div class="w-auto flex row">
<a href="#/shared" class="row items-center" on:click> <a href="#/site" class="row items-center" on:click={scrollToTop}>
<Users <User
tabindex="-1" 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" 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>
</div> </div>
</nav> </nav>
<div class="sticky top-0 w-full"> <div class="sticky top-0 w-full" style="z-index:39;">
<NavBar {scrollToTop}/> <NavBar {scrollToTop}/>
</div> </div>
{/if} {/if}
<div class="bg-gray-100 flex p-1 justify-around md:justify-start h-11 gap-0 xs:gap-3 text-gray-500">
<Test /> <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> </FullLayout>
<svelte:window bind:innerWidth={width} /> <svelte:window bind:innerWidth={width} />

@ -590,7 +590,7 @@
class:h-[160px]={!mobile} class:h-[160px]={!mobile}
class:h-[93px]={mobile} class:h-[93px]={mobile}
class:text-8xl={!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} disabled={pin_code.length >= 4}
> >
<span>{num}</span> <span>{num}</span>
@ -606,7 +606,7 @@
class:h-[160px]={!mobile} class:h-[160px]={!mobile}
class:h-[93px]={mobile} class:h-[93px]={mobile}
class:text-8xl={!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} disabled={pin_code.length >= 4}
> >
<span>{shuffle_pin[9]}</span> <span>{shuffle_pin[9]}</span>

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

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

@ -16,10 +16,15 @@
CheckCircle, CheckCircle,
CheckBadge, CheckBadge,
EllipsisVertical, EllipsisVertical,
ExclamationTriangle,
} from "svelte-heros-v2"; } from "svelte-heros-v2";
import NavIcon from "./NavIcon.svelte"; 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 = () => {}; export let scrollToTop = () => {};
@ -27,6 +32,11 @@
// going back // going back
window.history.go(-1); window.history.go(-1);
} }
const closeErrorPopup = () => {
window.document.getElementById("error_popover_btn").click();
}
</script> </script>
<div style="background-color: #fbfbfb;" class="h-11 pb-1 flex text-center text-gray-700 dark:text-white"> <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"/> <ArrowLeft tabindex="-1" class="w-8 h-8 focus:outline-none"/>
</div> </div>
{/if} {/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}> <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", tabindex:"-1",
class:"w-8 h-8 focus:outline-none" class:"w-8 h-8 focus:outline-none"
}}/> }}/>
</div> </div>
{/if} {/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> <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}>
{#if $nav_bar.newest} <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 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"> <div class="flex items-center grow pr-2">
<ChevronDoubleUp tabindex="-1" class="w-6 h-6 m-1 focus:outline-none"/> <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>
</div> </div>
{/if} {/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 } {#if $nav_bar.save }
<div tabindex="0" class="flex-none w-10" role="button" on:click={save} on:keypress={save} title="Save"> <div tabindex="0" class="flex-none w-10" role="button" on:click={save} on:keypress={save} title="Save">

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

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

@ -202,7 +202,7 @@
"pins_no_match": "Du hast nicht zweimal die gleiche PIN eingegeben", "pins_no_match": "Du hast nicht zweimal die gleiche PIN eingegeben",
"almost_done": "Wir sind fast fertig!", "almost_done": "Wir sind fast fertig!",
"save_wallet_options": { "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": "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_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?", "trust_toggle": "Mein Wallet auf diesem Gerät speichern?",

@ -1,5 +1,33 @@
{ {
"doc": { "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", "graph" : "Graph",
"discrete" : "Document", "discrete" : "Document",
"menu" : { "menu" : {
@ -152,6 +180,10 @@
"mc": { "mc": {
"label": "Magic Carpet", "label": "Magic Carpet",
"desc": "Opens the Magic Carpet (like a clipboard, but better)" "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.", "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.", "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.", "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.", "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 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.", "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.", "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!", "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>.", "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", "choose_broker": "Please choose one broker among the list",
"register_with_broker": "Register with {broker}", "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", "for_rest": "For the rest of the world",
"enter_invite_link": "Enter an invitation link", "enter_invite_link": "Enter an invitation link",
"scan_invite_qr": "Scan an invitation QR-code", "scan_invite_qr": "Scan an invitation QR-code",
@ -362,18 +394,18 @@
"pins_no_match": "You didn't enter the same PIN twice", "pins_no_match": "You didn't enter the same PIN twice",
"almost_done": "We are almost done!", "almost_done": "We are almost done!",
"save_wallet_options": { "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": "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_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.", "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": "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_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_toggle": "Save my wallet in the cloud?",
"cloud_tos": "Terms of Service of our cloud", "cloud_tos": "Terms of Service of our cloud",
"pdf": "Create a PDF file of your 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. 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_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": "Create a PDF of my wallet?", "pdf_toggle": "Yes, create a PDF of my wallet",
"link": "Create a link to access your wallet easily?", "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_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?" "link_toggle": "Create a link to my wallet?"

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

@ -12,24 +12,56 @@
import { onMount } from "svelte"; import { onMount } from "svelte";
import { push } from "svelte-spa-router"; import { push } from "svelte-spa-router";
import FullLayout from "../lib/FullLayout.svelte"; import FullLayout from "../lib/FullLayout.svelte";
import Document from "../lib/Document.svelte";
import { t } from "svelte-i18n"; import { t } from "svelte-i18n";
// The params prop contains values matched from the URL // The params prop contains values matched from the URL
export let params = {}; export let params = {};
import { import {
active_session, active_session,
} from "../store"; } from "../store";
import {
console.log(params); change_nav_bar, cur_tab, reset_in_memory
} from "../tab";
onMount(async () => { import {
if (!$active_session) { Square3Stack3d,
push("#/"); Megaphone,
} else { InboxArrowDown,
//await scrollToTop(); 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> </script>
<FullLayout> <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> </FullLayout>

@ -16,6 +16,7 @@
import { import {
ArrowLeft, ArrowLeft,
} from "svelte-heros-v2"; } from "svelte-heros-v2";
export let params;
</script> </script>
<CenteredLayout displayFooter={true}> <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> </button>
</div> </div>
<div class="row mt-5"> <!-- <div class="row mt-5">
<button <button
on:click|once={selectNET} 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" 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> </svg>
{$t("pages.wallet_create.for_rest")} {$t("pages.wallet_create.for_rest")}
</button> </button>
</div> </div> -->
{/if} {/if}
<div class="row mt-5"> <div class="row mt-5">
@ -1445,7 +1445,7 @@
type="text" type="text"
/> />
{/if} {/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" <span class="text-xl"
>{@html $t( >{@html $t(
"pages.wallet_create.save_wallet_options.cloud" "pages.wallet_create.save_wallet_options.cloud"
@ -1470,7 +1470,7 @@
"pages.wallet_create.save_wallet_options.cloud_toggle" "pages.wallet_create.save_wallet_options.cloud_toggle"
)}</Toggle )}</Toggle
> >
</p> </p> -->
<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" <span class="text-xl"
>{@html $t("pages.wallet_create.save_wallet_options.pdf")}</span >{@html $t("pages.wallet_create.save_wallet_options.pdf")}</span
@ -1486,7 +1486,7 @@
)}</Toggle )}</Toggle
> >
</p> </p>
{#if !options.cloud} <!-- {#if !options.cloud}
<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" <span class="text-xl"
>{@html $t("pages.wallet_create.save_wallet_options.link")} >{@html $t("pages.wallet_create.save_wallet_options.link")}
@ -1511,7 +1511,7 @@
)}</Toggle )}</Toggle
> >
</p> </p>
{/if} {/if} -->
<button <button
on:click|once={do_wallet} 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" 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, active_session,
set_active_session, set_active_session,
has_wallets, has_wallets,
scanned_qr_code,
display_error, display_error,
wallet_from_import, wallet_from_import,
redirect_after_login
} from "../store"; } from "../store";
import { CheckBadge, ExclamationTriangle, QrCode } from "svelte-heros-v2"; import { CheckBadge, ExclamationTriangle, QrCode } from "svelte-heros-v2";
@ -107,7 +107,14 @@
function loggedin() { function loggedin() {
step = "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() { function start_login_from_import() {
@ -364,15 +371,17 @@
<a href="/wallet/login-qr" use:link> <a href="/wallet/login-qr" use:link>
<button <button
style="justify-content: left;" 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" 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")} {$t("pages.wallet_login.import_qr")}
</button> </button>
</a> </a>
<a href="/wallet/login-text-code" use:link> <a href="/wallet/login-text-code" use:link>
<button <button
style="justify-content: left;" 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" 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 <svg

@ -17,8 +17,11 @@ import {
} from "svelte/store"; } from "svelte/store";
import { register, init, locale, format } from "svelte-i18n"; import { register, init, locale, format } from "svelte-i18n";
import ng from "./api"; 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 all_branches = {};
let retry_branches = {};
// Make sure that a file named `locales/<lang>.json` exists when adding it here. // Make sure that a file named `locales/<lang>.json` exists when adding it here.
export const available_languages = { export const available_languages = {
@ -82,7 +85,9 @@ export const connections: Writable<Record<string, any>> = writable({});
export const active_session = writable(undefined); 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; let next_reconnect: NodeJS.Timeout | null = null;
@ -117,7 +122,7 @@ export const check_has_camera = async () => {
return has_camera; return has_camera;
}; };
const updateConnectionStatus = ($connections: Record<string, any>) => { const updateConnectionStatus = async ($connections: Record<string, any>) => {
// Reset error state for PeerAlreadyConnected errors. // Reset error state for PeerAlreadyConnected errors.
Object.entries($connections).forEach(([cnx, connection]) => { Object.entries($connections).forEach(([cnx, connection]) => {
if (connection.error === "PeerAlreadyConnected") { if (connection.error === "PeerAlreadyConnected") {
@ -149,34 +154,50 @@ const updateConnectionStatus = ($connections: Record<string, any>) => {
if (is_connected) { if (is_connected) {
connection_status.set("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) { } else if (is_connecting) {
connection_status.set("connecting"); connection_status.set("connecting");
} else { } else if (Object.keys($connections).length) {
connection_status.set("disconnected"); 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) => { connections.subscribe(async ($connections) => {
updateConnectionStatus($connections); await updateConnectionStatus($connections);
}); });
export const online = derived(connection_status, ($connectionStatus) => $connectionStatus == "connected"); 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) { // if (get(connection_status) == "disconnected" && !import.meta.env.TAURI_PLATFORM) {
cannot_load_offline.set(true); // cannot_load_offline.set(true);
let unsubscribe = connection_status.subscribe(async (value) => { // let unsubscribe = connection_status.subscribe(async (value) => {
if (value != "disconnected") { // if (value != "disconnected") {
cannot_load_offline.set(false); // cannot_load_offline.set(false);
if (value == "connected") { // if (value == "connected") {
unsubscribe(); // unsubscribe();
} // }
} else { // } else {
cannot_load_offline.set(true); // cannot_load_offline.set(true);
} // }
}); // });
} // }
export const has_wallets = derived(wallets, ($wallets) => Object.keys($wallets).length); 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]; let sub = all_branches[branch];
sub.unsubscribe(); sub.unsubscribe();
} }
retry_branches = {};
} }
@ -245,7 +267,7 @@ export const reconnect = async function() {
location.href location.href
)); ));
} catch (e) { } catch (e) {
console.error(e) //console.error(e)
} }
} }
// export const disconnections_subscribe = async function() { // export const disconnections_subscribe = async function() {
@ -286,13 +308,31 @@ readable(false, function start(set) {
can_connect.subscribe(async (value) => { can_connect.subscribe(async (value) => {
if (value[0] && value[0].wallet && value[1]) { if (value[0] && value[0].wallet && value[1]) {
//setTimeout(async()=> {await reconnect();},5000);
await reconnect(); await reconnect();
} }
}); });
export const branch_subs = function(nuri) { export const digest_to_string = function(digest) {
// console.log("branch_commits") 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 // const { subscribe, set, update } = writable([]); // create the underlying writable store
// let unsub = () => {}; // let unsub = () => {};
@ -320,6 +360,8 @@ export const branch_subs = function(nuri) {
// // update, // // update,
// }; // };
open_branch(nuri, in_tab);
return { return {
load: async () => { load: async () => {
@ -331,13 +373,16 @@ export const branch_subs = function(nuri) {
let loader = already_subscribed.load; let loader = already_subscribed.load;
already_subscribed.load = undefined; already_subscribed.load = undefined;
// already_subscribed.load2 = loader; // already_subscribed.load2 = loader;
await loader(); if (await loader()) {
retry_branches[nuri] = loader;
}
} }
}, },
subscribe: (run, invalid) => { subscribe: (run, invalid) => {
//console.log("sub");
let already_subscribed = all_branches[nuri]; let already_subscribed = all_branches[nuri];
if (!already_subscribed) { 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 count = 0;
let unsub = () => { }; let unsub = () => { };
already_subscribed = { already_subscribed = {
@ -351,23 +396,132 @@ export const branch_subs = function(nuri) {
} }
unsub(); unsub();
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; req.V0.session_id = session.session_id;
unsub = await ng.app_request_stream(req, unsub = await ng.app_request_stream(req,
async (commit) => { async (response) => {
//console.log("GOT APP RESPONSE", commit); console.log("GOT APP RESPONSE", response);
if (commit.V0.State) { if (response.V0.TabInfo) {
for (const file of commit.V0.State.files) { tab_update(nuri, ($cur_tab) => {
update((old) => { old.unshift(file); return old; }) if (response.V0.TabInfo.branch?.id) {
} //console.log("BRANCH ID",response.V0.TabInfo.branch?.id);
} else if (commit.V0.Patch.other?.FileAdd) { $cur_tab.branch.id = response.V0.TabInfo.branch.id;
update((old) => { old.unshift(commit.V0.Patch.other.FileAdd); return old; }) }
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 {
}
}
return old;
});
}); });
} }
catch (e) { 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. // this is in case decrease has been called before the load function returned.
if (count == 0) { unsub(); } if (count == 0) { unsub(); }

@ -41,6 +41,18 @@
.toggle * { .toggle * {
cursor: pointer; 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 { #scanner-div {
border: none !important; border: none !important;

@ -18,20 +18,23 @@ import {
import { official_classes } from "./classes"; import { official_classes } from "./classes";
import { official_apps, official_services } from "./zeras"; import { official_apps, official_services } from "./zeras";
import { format } from "svelte-i18n";
let loaded_external_apps = {}; let loaded_external_apps = {};
export const load_app = async (appName: string) => { export const load_app = async (appName: string) => {
if (appName.startsWith("n:g:z")) { let app = await get_app(appName);
let app = official_apps[appName]; return await import(`./apps/${app["ng:b"]}.svelte`);
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
}
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) => { 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) => { 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 //TODO: load external app from its repo
// cache it in loaded_external_apps // cache it in loaded_external_apps
// return the class // 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:o"]) graph_viewers.push(class_def["ng:o"]);
if (class_def["ng:w"]) graph_editors.push(class_def["ng:w"]); 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_viewers.push.apply(graph_viewers, find_viewers_for_class("data:graph"));
graph_editors.push.apply(graph_editors, find_editors_for_class("data/graph")); graph_editors.push.apply(graph_editors, find_editors_for_class("data:graph"));
let graph_viewer = graph_viewers[0]; let graph_viewer = graph_viewers[0];
let graph_editor = graph_editors[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) => { export const open_branch = (nuri: string, in_tab: boolean) => {
let class_name = "post/rich"; 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) => { export const show_modal_menu = writable(false);
//let class_name = "doc/viz/plotly";
let class_name = "post/md";
cur_tab.update(ct => { export const in_memory_graph = writable("");
return {...ct, ...class_to_viewers_editors(class_name)}; 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({ export const all_tabs = writable({
cur_store: { "":{
has_outer: { store: {
nuri_trail: ":v:l" 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: "",
},
plato: {
nuri: "",
can_edit: false,
}, },
type: "public", // "protected", "private", "group", "dialog", branch: {
favicon: "", nuri: "", // :o or :o:b
title: "Group B", readcap: "", // "r:"
is_member: true, comment_branch: "", // nuri
}, class: "",
cur_branch: { has_discrete: false,
b: "b:xxx", //branch id (can be null if not of type "branch") 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) c: "", //"c:xxx" commit(s) id
display: "c:X", // or main or stream or a:xx or branch:X (only 7 chars) type: "", // "main", "stream", "detached", "branch", "in_memory" (does not save)
attachments: 1, display: "", // or main or stream or a:xx or branch:X or c:X (only 7 chars)
files: 1, attachments: 0,
comments: 2, files: 0,
class: "post/rich", comments: 0,
title: false,
icon: false, title: "",
description: "", icon: "",
app: "n:g:z:json_ld_editor", // current app being used description: "",
},
view_or_edit: true, app: "", // current app being used
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: "",
}, },
live_editors: { 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,
folders_pane: false, toc_pane: false,
toc_pane: false, messenger_pane: false,
right_pane: "", // "branches", "files", "history", "comments", "info", "chat", "mc" 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", 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,
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 = () => { export const showMenu = () => {
cur_tab.update(ct => { show_modal_menu.set(true);
cur_tab_update(ct => {
ct.show_menu = true; ct.show_menu = true;
ct.show_modal_menu = true;
return ct; return ct;
}); });
} }
export const hideMenu = () => { export const hideMenu = () => {
cur_tab.update(ct => { show_modal_menu.set(false);
cur_tab_update(ct => {
ct.show_menu = false; ct.show_menu = false;
ct.show_modal_menu = false;
return ct; return ct;
}); });
} }
export const nav_bar = writable({ export const nav_bar = writable({
//icon: "class:post/rich", //icon: "class:post:rich",
icon: "nav:private", icon: "",
//icon: "blob:http://localhost:1421/be6f968f-ff51-4e8f-bd32-36c60b7af49a", //icon: "blob:http://localhost:1421/be6f968f-ff51-4e8f-bd32-36c60b7af49a",
title: "Private Store", title: "",
back: false, back: false,
newest: 0, 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 () => { export const save = async () => {
// saving the doc // saving the doc
} }
export const all_files_count = derived(cur_tab, ($cur_tab) => { 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})` : ""; return total ? `(${total})` : "";
}); });
export const all_comments_count = derived(cur_tab, ($cur_tab) => { 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})` : ""; return total ? `(${total})` : "";
}); });
export const has_editor_chat = derived(cur_tab, ($cur_tab) => { 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 = () => { export const toggle_live_edit = () => {
cur_tab.update(ct => { cur_tab_update(ct => {
ct.doc.live_edit = !ct.doc.live_edit; ct.doc.live_edit = !ct.doc.live_edit;
return ct; return ct;
}); });
@ -288,36 +446,36 @@ export const toggle_live_edit = () => {
export const set_viewer = (app_name: string) => { export const set_viewer = (app_name: string) => {
if (get(cur_tab).graph_or_discrete) { 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 { } 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) => { export const set_editor = (app_name: string) => {
if (get(cur_tab).graph_or_discrete) { 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 { } 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 = () => { export const toggle_graph_discrete = () => {
cur_tab.update(ct => { cur_tab_update(ct => {
ct.graph_or_discrete = !ct.graph_or_discrete; ct.graph_or_discrete = !ct.graph_or_discrete;
return ct; return ct;
}); });
} }
export const set_graph_discrete = (val:boolean) => { export const set_graph_discrete = (val:boolean) => {
cur_tab.update(ct => { cur_tab_update(ct => {
ct.graph_or_discrete = val; ct.graph_or_discrete = val;
return ct; return ct;
}); });
} }
export const set_view_or_edit = (val:boolean) => { export const set_view_or_edit = (val:boolean) => {
cur_tab.update(ct => { cur_tab_update(ct => {
ct.view_or_edit = val; ct.view_or_edit = val;
return ct; 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) => { export const available_viewers = derived(cur_tab, ($cur_tab) => {
let list = $cur_tab.graph_or_discrete ? $cur_tab.graph_viewers : $cur_tab.discrete_viewers; let list = $cur_tab.graph_or_discrete ? $cur_tab.graph_viewers : $cur_tab.discrete_viewers;
return list.map((viewer) => { 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:u": "json_ld_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:json_ld_editor", "ng:g": "n:g:z:json_ld_editor",
"ng:b": "JsonLdEditor", "ng:b": "JsonLdEditor",
"ng:w": ["data/graph"], "ng:w": ["data:graph"],
}, },
"n:g:z:json_editor": { "n:g:z:json_editor": {
"ng:n": "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:u": "json_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:json_editor", "ng:g": "n:g:z:json_editor",
"ng:b": "JsonEditor", "ng:b": "JsonEditor",
"ng:w": ["data/json","data/array","data/map"], "ng:w": ["data:json","data:array","data:map"],
}, },
"n:g:z:triple_editor": { "n:g:z:triple_editor": {
"ng:n": "Graph Triples 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:u": "triple_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:triple_editor", "ng:g": "n:g:z:triple_editor",
"ng:b": "TripleEditor", "ng:b": "TripleEditor",
"ng:w": ["data/graph"], "ng:w": ["data:graph"],
}, },
"n:g:z:json_ld_viewer": { "n:g:z:json_ld_viewer": {
"ng:n": "JSON-LD", "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:u": "json_ld_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:json_ld_viewer", "ng:g": "n:g:z:json_ld_viewer",
"ng:b": "JsonLdViewer", "ng:b": "JsonLdViewer",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
}, },
"n:g:z:rdf_viewer:graph": { "n:g:z:rdf_viewer:graph": {
"ng:n": "Graph Explorer", "ng:n": "Graph Explorer",
@ -64,7 +64,7 @@ export const official_apps = {
"ng:u": "graph_viewer",//favicon. can be a did:ng:j "ng:u": "graph_viewer",//favicon. can be a did:ng:j
"ng:g": "n:g:z:rdf_viewer:graph", "ng:g": "n:g:z:rdf_viewer:graph",
"ng:b": "GraphViewer", // GraphExplorer https://github.com/zazuko/graph-explorer !! AGPL "ng:b": "GraphViewer", // GraphExplorer https://github.com/zazuko/graph-explorer !! AGPL
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:json_viewer": { "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:u": "json_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:json_viewer", "ng:g": "n:g:z:json_viewer",
"ng:b": "JsonViewer", "ng:b": "JsonViewer",
"ng:o": ["data/json","data/array","data/map"], "ng:o": ["data:json","data:array","data:map"],
}, },
"n:g:z:triple_viewer": { "n:g:z:triple_viewer": {
"ng:n": "Graph Triples", "ng:n": "Graph Triples",
@ -83,37 +83,37 @@ export const official_apps = {
"ng:u": "triple_editor",//favicon. can be a did:ng:j "ng:u": "triple_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:triple_viewer", "ng:g": "n:g:z:triple_viewer",
"ng:b": "TripleViewer", "ng:b": "TripleViewer",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
}, },
"n:g:z:sparql_query:yasgui": { "n:g:z:sparql_query:yasgui": {
"ng:n": "SPARQL Query", "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:c": "app",
"ng:u": "sparql_query",//favicon. can be a did:ng:j "ng:u": "sparql_query",//favicon. can be a did:ng:j
"ng:g": "n:g:z:sparql_query:yasgui", "ng:g": "n:g:z:sparql_query:yasgui",
"ng:b": "SparqlQueryEditor", // YASGUI of Zazuko https://github.com/zazuko/trifid/tree/main/packages/yasgui "ng:b": "SparqlQueryEditor", // YASGUI of Zazuko https://github.com/zazuko/trifid/tree/main/packages/yasgui
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": ["query/sparql"], "ng:w": ["query:sparql"],
}, },
"n:g:z:sparql_query:sparnatural": { "n:g:z:sparql_query:sparnatural": {
"ng:n": "SPARNatural Query", "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:c": "app",
"ng:u": "sparnatural",//favicon. can be a did:ng:j "ng:u": "sparnatural",//favicon. can be a did:ng:j
"ng:g": "n:g:z:sparql_query:sparnatural", "ng:g": "n:g:z:sparql_query:sparnatural",
"ng:b": "SparNaturalEditor", "ng:b": "SparNaturalEditor",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": ["query/sparql"], "ng:w": ["query:sparql"],
}, },
"n:g:z:graphql_query": { "n:g:z:graphql_query": {
"ng:n": "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:c": "app",
"ng:u": "graphql",//favicon. can be a did:ng:j "ng:u": "graphql",//favicon. can be a did:ng:j
"ng:g": "n:g:z:graphql_query", "ng:g": "n:g:z:graphql_query",
"ng:b": "GraphqlEditor", "ng:b": "GraphqlEditor",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": ["query/graphql"], "ng:w": ["query:graphql"],
}, },
"n:g:z:sparql_update:yasgui": { "n:g:z:sparql_update:yasgui": {
"ng:n": "SPARQL Update", "ng:n": "SPARQL Update",
@ -123,7 +123,7 @@ export const official_apps = {
"ng:g": "n:g:z:sparql_update:yasgui", "ng:g": "n:g:z:sparql_update:yasgui",
"ng:b": "SparqlUpdateEditor", // YASGUI of Zazuko https://github.com/zazuko/trifid/tree/main/packages/yasgui "ng:b": "SparqlUpdateEditor", // YASGUI of Zazuko https://github.com/zazuko/trifid/tree/main/packages/yasgui
"ng:o": [], "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 "n:g:z:rdf_viewer:turtle": { // https://github.com/highlightjs/highlightjs-turtle/tree/master
"ng:n": "Turtle", "ng:n": "Turtle",
@ -132,7 +132,7 @@ export const official_apps = {
"ng:u": "rdf_viewer",//favicon. can be a did:ng:j "ng:u": "rdf_viewer",//favicon. can be a did:ng:j
"ng:g": "n:g:z:rdf_viewer:turtle", "ng:g": "n:g:z:rdf_viewer:turtle",
"ng:b": "TurtleViewer", "ng:b": "TurtleViewer",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:rdf_viewer:n3": { // ? "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:u": "rdf_viewer",//favicon. can be a did:ng:j
"ng:g": "n:g:z:rdf_viewer:n3", "ng:g": "n:g:z:rdf_viewer:n3",
"ng:b": "N3Viewer", "ng:b": "N3Viewer",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:rdf_viewer:json_ld": { // highlight.js JSON "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:u": "rdf_viewer",//favicon. can be a did:ng:j
"ng:g": "n:g:z:rdf_viewer:json_ld", "ng:g": "n:g:z:rdf_viewer:json_ld",
"ng:b": "JsonLdViewer", "ng:b": "JsonLdViewer",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:ontology_editor": { "n:g:z:ontology_editor": {
@ -163,7 +163,7 @@ export const official_apps = {
"ng:g": "n:g:z:ontology_editor", "ng:g": "n:g:z:ontology_editor",
"ng:b": "JsonLdEditor", "ng:b": "JsonLdEditor",
"ng:o": [], "ng:o": [],
"ng:w": ["schema/*"], "ng:w": ["schema:*"],
}, },
"n:g:z:owl_viewer": { "n:g:z:owl_viewer": {
"ng:n": "OWL Ontology", "ng:n": "OWL Ontology",
@ -172,7 +172,7 @@ export const official_apps = {
"ng:u": "ontology_viewer",//favicon. can be a did:ng:j "ng:u": "ontology_viewer",//favicon. can be a did:ng:j
"ng:g": "n:g:z:owl_viewer", "ng:g": "n:g:z:owl_viewer",
"ng:b": "OwlViewer", // display with https://github.com/VisualDataWeb/WebVOWL "ng:b": "OwlViewer", // display with https://github.com/VisualDataWeb/WebVOWL
"ng:o": ["schema/owl"], "ng:o": ["schema:owl"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:sparql:invoke": { // displayed with highlight.js https://github.com/highlightjs/highlightjs-turtle/tree/master "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:u": "invoke",//favicon. can be a did:ng:j
"ng:g": "n:g:z:sparql:invoke", "ng:g": "n:g:z:sparql:invoke",
"ng:b": "SparqlInvoker", "ng:b": "SparqlInvoker",
"ng:o": ["query/sparql","query/sparql_update"], "ng:o": ["query:sparql","query:sparql_update"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:graphql:invoke": { "n:g:z:graphql:invoke": {
@ -192,7 +192,7 @@ export const official_apps = {
"ng:u": "invoke",//favicon. can be a did:ng:j "ng:u": "invoke",//favicon. can be a did:ng:j
"ng:g": "n:g:z:graphql:invoke", "ng:g": "n:g:z:graphql:invoke",
"ng:b": "GraphqlInvoker", "ng:b": "GraphqlInvoker",
"ng:o": ["query/graphql"], "ng:o": ["query:graphql"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:dump_download": { "n:g:z:dump_download": {
@ -202,7 +202,7 @@ export const official_apps = {
"ng:u": "download",//favicon. can be a did:ng:j "ng:u": "download",//favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_download", "ng:g": "n:g:z:dump_download",
"ng:b": "Downloader", "ng:b": "Downloader",
"ng:o": ["data/graph","file*","data/*"], "ng:o": ["data:graph","file*","data:*"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:post_rich_editor": { "n:g:z:post_rich_editor": {
@ -213,7 +213,7 @@ export const official_apps = {
"ng:g": "n:g:z:post_rich_editor", "ng:g": "n:g:z:post_rich_editor",
"ng:b": "ProseMirrorEditor", "ng:b": "ProseMirrorEditor",
"ng:o": [], "ng:o": [],
"ng:w": ["post/rich"], "ng:w": ["post:rich"],
}, },
"n:g:z:post_md_editor": { "n:g:z:post_md_editor": {
"ng:n": "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:g": "n:g:z:post_md_editor",
"ng:b": "MilkDownEditor", "ng:b": "MilkDownEditor",
"ng:o": [], "ng:o": [],
"ng:w": ["post/md"], "ng:w": ["post:md"],
}, },
"n:g:z:code_editor": { "n:g:z:code_editor": {
"ng:n": "Code and Text Editor", "ng:n": "Code and Text Editor",
@ -233,7 +233,7 @@ export const official_apps = {
"ng:g": "n:g:z:code_editor", "ng:g": "n:g:z:code_editor",
"ng:b": "CodeMirrorEditor", "ng:b": "CodeMirrorEditor",
"ng:o": [], "ng:o": [],
"ng:w": ["code*","post/text"], "ng:w": ["code*","post:text"],
}, },
"n:g:z:file_viewer": { "n:g:z:file_viewer": {
"ng:n": "File details", "ng:n": "File details",
@ -252,7 +252,7 @@ export const official_apps = {
"ng:u": "source",//favicon. can be a did:ng:j "ng:u": "source",//favicon. can be a did:ng:j
"ng:g": "n:g:z:file_source", "ng:g": "n:g:z:file_source",
"ng:b": "FileSource", "ng:b": "FileSource",
"ng:o": ["file/text"], "ng:o": ["file:text"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:crdt_source_viewer:xml": { "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:u": "source",//favicon. can be a did:ng:j
"ng:g": "n:g:z:crdt_source_viewer:xml", "ng:g": "n:g:z:crdt_source_viewer:xml",
"ng:b": "XmlSource", // displayed with highlight.js , with option to download "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": [], "ng:w": [],
}, },
"n:g:z:viewer:md": { "n:g:z:viewer:md": {
@ -272,7 +272,7 @@ export const official_apps = {
"ng:u": "source",//favicon. can be a did:ng:j "ng:u": "source",//favicon. can be a did:ng:j
"ng:g": "n:g:z:viewer:md", "ng:g": "n:g:z:viewer:md",
"ng:b": "MdSource", // displayed with highlight.js , with option to download "ng:b": "MdSource", // displayed with highlight.js , with option to download
"ng:o": ["post/md"], "ng:o": ["post:md"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:crdt_source_viewer:json": { "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:u": "source",//favicon. can be a did:ng:j
"ng:g": "n:g:z:crdt_source_viewer:json", "ng:g": "n:g:z:crdt_source_viewer:json",
"ng:b": "JsonSource", // displayed with highlight.js , with option to download "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": [], "ng:w": [],
}, },
"n:g:z:crdt_source_viewer:text": { "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:u": "source",//favicon. can be a did:ng:j
"ng:g": "n:g:z:crdt_source_viewer:text", "ng:g": "n:g:z:crdt_source_viewer:text",
"ng:b": "TextViewer", // displayed with highlight.js , with option to download "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", "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"], "doc:diagram:sequence","doc:diagram:markmap","doc:diagram:mymind","doc:music*", "doc:maths", "doc:chemistry", "doc:ancientscript", "doc:braille", "media:subtitle"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:crdt_source_viewer:rdf": { "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:u": "source",//favicon. can be a did:ng:j
"ng:g": "n:g:z:crdt_source_viewer:rdf", "ng:g": "n:g:z:crdt_source_viewer:rdf",
"ng:b": "TurtleViewer", //, with option to download "ng:b": "TurtleViewer", //, with option to download
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:post:rich": { "n:g:z:post:rich": {
@ -313,7 +313,7 @@ export const official_apps = {
"ng:u": "post",//favicon. can be a did:ng:j "ng:u": "post",//favicon. can be a did:ng:j
"ng:g": "n:g:z:post:rich", "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: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": [], "ng:w": [],
}, },
"n:g:z:post:md": { "n:g:z:post:md": {
@ -323,7 +323,7 @@ export const official_apps = {
"ng:u": "post",//favicon. can be a did:ng:j "ng:u": "post",//favicon. can be a did:ng:j
"ng:g": "n:g:z:post:md", "ng:g": "n:g:z:post:md",
"ng:b": "PostMdViewer", // https://github.com/wooorm/markdown-rs "ng:b": "PostMdViewer", // https://github.com/wooorm/markdown-rs
"ng:o": ["post/md"], "ng:o": ["post:md"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:post:text": { "n:g:z:post:text": {
@ -333,7 +333,7 @@ export const official_apps = {
"ng:u": "post",//favicon. can be a did:ng:j "ng:u": "post",//favicon. can be a did:ng:j
"ng:g": "n:g:z:post:text", "ng:g": "n:g:z:post:text",
"ng:b": "TextViewer", // displayed with a <p> "ng:b": "TextViewer", // displayed with a <p>
"ng:o": ["post/text"], "ng:o": ["post:text"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:pre": { "n:g:z:pre": {
@ -343,7 +343,7 @@ export const official_apps = {
"ng:u": "post",//favicon. can be a did:ng:j "ng:u": "post",//favicon. can be a did:ng:j
"ng:g": "n:g:z:pre", "ng:g": "n:g:z:pre",
"ng:b": "PreTextViewer", // displayed with highlight.js "ng:b": "PreTextViewer", // displayed with highlight.js
"ng:o": ["code*","post/text"], "ng:o": ["code*","post:text"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:pad": { "n:g:z:pad": {
@ -373,7 +373,7 @@ export const official_apps = {
"ng:u": "gallery",//favicon. can be a did:ng:j "ng:u": "gallery",//favicon. can be a did:ng:j
"ng:g": "n:g:z:gallery", "ng:g": "n:g:z:gallery",
"ng:b": "Gallery", "ng:b": "Gallery",
"ng:o": ["media/album","data/collection"], "ng:o": ["media:album","data:collection"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:app_store": { "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:u": "app_store",//favicon. can be a did:ng:j
"ng:g": "n:g:z:app_store", "ng:g": "n:g:z:app_store",
"ng:b": "AppStore", "ng:b": "AppStore",
"ng:o": ["app/z"], "ng:o": ["app:z"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:app_editor": { "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:u": "app_editor",//favicon. can be a did:ng:j
"ng:g": "n:g:z:app_editor", "ng:g": "n:g:z:app_editor",
"ng:b": "AppEditor", "ng:b": "AppEditor",
"ng:o": ["app/z"], "ng:o": ["app:z"],
"ng:w": ["app/z"], "ng:w": ["app:z"],
}, },
"n:g:z:list": { "n:g:z:list": {
"ng:n": "List view", "ng:n": "List",
"ng:a": "See the content of document as a list", "ng:a": "See the content of document as a list",
"ng:c": "app", "ng:c": "app",
"ng:u": "list",//favicon. can be a did:ng:j "ng:u": "list",//favicon. can be a did:ng:j
"ng:g": "n:g:z:list", "ng:g": "n:g:z:list",
"ng:b": "ListView", "ng:b": "ListView",
"ng:o": ["data/collection","data/container"], "ng:o": ["data:collection","data:container"],
"ng:w": ["data/collection","data/container"], "ng:w": ["data:collection","data:container"],
}, },
"n:g:z:grid": { "n:g:z:grid": {
"ng:n": "Grid view", "ng:n": "Grid",
"ng:a": "See the content of document as a grid", "ng:a": "See the content of document as a grid",
"ng:c": "app", "ng:c": "app",
"ng:u": "grid",//favicon. can be a did:ng:j "ng:u": "grid",//favicon. can be a did:ng:j
"ng:g": "n:g:z:grid", "ng:g": "n:g:z:grid",
"ng:b": "GridView", "ng:b": "GridView",
"ng:o": ["data/grid"], "ng:o": ["data:grid"],
"ng:w": ["data/grid"], "ng:w": ["data:grid"],
}, },
"n:g:z:media": { "n:g:z:media": {
"ng:n": "Media view", "ng:n": "Media",
"ng:a": "View media", "ng:a": "View media",
"ng:c": "app", "ng:c": "app",
"ng:u": "view",//favicon. can be a did:ng:j "ng:u": "view",//favicon. can be a did:ng:j
"ng:g": "n:g:z:media", "ng:g": "n:g:z:media",
"ng:b": "MediaView", "ng:b": "MediaView",
"ng:o": ["media/*"], "ng:o": ["media:*"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:service_editor": { "n:g:z:service_editor": {
@ -434,7 +434,7 @@ export const official_apps = {
"ng:g": "n:g:z:service_editor", "ng:g": "n:g:z:service_editor",
"ng:b": "CodeMirrorEditor", "ng:b": "CodeMirrorEditor",
"ng:o": [], "ng:o": [],
"ng:w": ["service/*"], "ng:w": ["service:*"],
}, },
"n:g:z:service_invoke": { "n:g:z:service_invoke": {
"ng:n": "Service Invoker", "ng:n": "Service Invoker",
@ -453,7 +453,7 @@ export const official_apps = {
"ng:u": "invoke",//favicon. can be a did:ng:j "ng:u": "invoke",//favicon. can be a did:ng:j
"ng:g": "n:g:z:external_service_invoke", "ng:g": "n:g:z:external_service_invoke",
"ng:b": "ExternalServiceInvoker", "ng:b": "ExternalServiceInvoker",
"ng:o": ["service/*"], "ng:o": ["service:*"],
"ng:w": [], "ng:w": [],
}, },
"n:g:z:upload_file": { "n:g:z:upload_file": {
@ -464,7 +464,7 @@ export const official_apps = {
"ng:g": "n:g:z:upload_file", "ng:g": "n:g:z:upload_file",
"ng:b": "UploadFile", "ng:b": "UploadFile",
"ng:o": [], "ng:o": [],
"ng:w": ["data/graph"], "ng:w": ["data:graph"],
}, },
"n:g:z:import_file": { "n:g:z:import_file": {
"ng:n": "Import from external file", "ng:n": "Import from external file",
@ -474,7 +474,7 @@ export const official_apps = {
"ng:g": "n:g:z:import_file", "ng:g": "n:g:z:import_file",
"ng:b": "UploadFile", "ng:b": "UploadFile",
"ng:o": [], "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: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", // 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:c": "service",
"ng:u": "data",// favicon. can be a did:ng:j "ng:u": "data",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_rdf:turtle", "ng:g": "n:g:z:dump_rdf:turtle",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": [], "ng:w": [],
"ng:result": ["file/iana/text/turtle"], "ng:result": ["file:iana:text:turtle"],
}, },
"n:g:z:dump_rdf:n3": { "n:g:z:dump_rdf:n3": {
"ng:n": "N3 export", "ng:n": "N3 export",
@ -506,9 +506,9 @@ export const official_services = {
"ng:c": "service", "ng:c": "service",
"ng:u": "data",// favicon. can be a did:ng:j "ng:u": "data",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_rdf:n3", "ng:g": "n:g:z:dump_rdf:n3",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": [], "ng:w": [],
"ng:result": ["file/iana/text/n3"], "ng:result": ["file:iana:text:n3"],
}, },
"n:g:z:dump_rdf:json_ld": { "n:g:z:dump_rdf:json_ld": {
"ng:n": "JSON-LD export", "ng:n": "JSON-LD export",
@ -516,9 +516,9 @@ export const official_services = {
"ng:c": "service", "ng:c": "service",
"ng:u": "data",// favicon. can be a did:ng:j "ng:u": "data",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_rdf:json_ld", "ng:g": "n:g:z:dump_rdf:json_ld",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": [], "ng:w": [],
"ng:result": ["file/iana/application/ld+json"], "ng:result": ["file:iana:application:ld+json"],
}, },
"n:g:z:load_rdf:turtle": { "n:g:z:load_rdf:turtle": {
"ng:n": "Import Turtle triples", "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:u": "load_graph",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_rdf:turtle", "ng:g": "n:g:z:load_rdf:turtle",
"ng:o": [], "ng:o": [],
"ng:w": ["data/graph"], "ng:w": ["data:graph"],
}, },
"n:g:z:load_rdf:n3": { "n:g:z:load_rdf:n3": {
"ng:n": "Import N3 triples", "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:u": "load_graph",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_rdf:n3", "ng:g": "n:g:z:load_rdf:n3",
"ng:o": [], "ng:o": [],
"ng:w": ["data/graph"], "ng:w": ["data:graph"],
}, },
"n:g:z:load_rdf:json_ld": { "n:g:z:load_rdf:json_ld": {
"ng:n": "Import JSON-LD triples", "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:u": "load_graph",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_rdf:json_ld", "ng:g": "n:g:z:load_rdf:json_ld",
"ng:o": [], "ng:o": [],
"ng:w": ["data/graph"], "ng:w": ["data:graph"],
}, },
"n:g:z:load_file": { "n:g:z:load_file": {
"ng:n": "Add file", "ng:n": "Add file",
@ -554,7 +554,7 @@ export const official_services = {
"ng:u": "load",// favicon. can be a did:ng:j "ng:u": "load",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_file", "ng:g": "n:g:z:load_file",
"ng:o": [], "ng:o": [],
"ng:w": ["data/graph"], "ng:w": ["data:graph"],
}, },
"n:g:z:dump_file": { "n:g:z:dump_file": {
"ng:n": "Export file", "ng:n": "Export file",
@ -563,7 +563,7 @@ export const official_services = {
"ng:u": "dump",// favicon. can be a did:ng:j "ng:u": "dump",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_file", "ng:g": "n:g:z:dump_file",
"ng:o": ["file*"], "ng:o": ["file*"],
"ng:result": ["file/iana/*"], "ng:result": ["file:iana:*"],
}, },
"n:g:z:dump_json": { "n:g:z:dump_json": {
"ng:n": "JSON export", "ng:n": "JSON export",
@ -571,9 +571,9 @@ export const official_services = {
"ng:c": "service", "ng:c": "service",
"ng:u": "data",// favicon. can be a did:ng:j "ng:u": "data",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_json", "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:w": [],
"ng:result": ["file/iana/application/json"], "ng:result": ["file:iana:application:json"],
}, },
"n:g:z:dump_xml": { "n:g:z:dump_xml": {
"ng:n": "XML export", "ng:n": "XML export",
@ -581,9 +581,9 @@ export const official_services = {
"ng:c": "service", "ng:c": "service",
"ng:u": "data",// favicon. can be a did:ng:j "ng:u": "data",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_xml", "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:w": [],
"ng:result": ["file/iana/text/xml"], "ng:result": ["file:iana:text:xml"],
}, },
"n:g:z:dump_text": { "n:g:z:dump_text": {
"ng:n": "Text export", "ng:n": "Text export",
@ -591,10 +591,10 @@ export const official_services = {
"ng:c": "service", "ng:c": "service",
"ng:u": "dump",// favicon. can be a did:ng:j "ng:u": "dump",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_text", "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", "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"], "doc:diagram:sequence","doc:diagram:markmap","doc:diagram:mymind","doc:music*", "doc:maths", "doc:chemistry", "doc:ancientscript", "doc:braille", "media:subtitle"],
"ng:w": [], "ng:w": [],
"ng:result": ["file/iana/text/plain"], "ng:result": ["file:iana:text:plain"],
}, },
"n:g:z:dump_ng_html_file": { "n:g:z:dump_ng_html_file": {
"ng:n": "NextGraph Standalone file", "ng:n": "NextGraph Standalone file",
@ -602,9 +602,9 @@ export const official_services = {
"ng:c": "service", "ng:c": "service",
"ng:u": "ext",// favicon. can be a did:ng:j "ng:u": "ext",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_ng_html_file", "ng:g": "n:g:z:dump_ng_html_file",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": [], "ng:w": [],
"ng:result": ["file/iana/text/html"], "ng:result": ["file:iana:text:html"],
}, },
"n:g:z:load_json": { "n:g:z:load_json": {
"ng:n": "Import JSON", "ng:n": "Import JSON",
@ -613,7 +613,7 @@ export const official_services = {
"ng:u": "load",// favicon. can be a did:ng:j "ng:u": "load",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_json", "ng:g": "n:g:z:load_json",
"ng:o": [], "ng:o": [],
"ng:w": ["data/json","data/map", "data/array"], "ng:w": ["data:json","data:map", "data:array"],
}, },
"n:g:z:load_xml": { "n:g:z:load_xml": {
"ng:n": "Import XML", "ng:n": "Import XML",
@ -622,7 +622,7 @@ export const official_services = {
"ng:u": "load",// favicon. can be a did:ng:j "ng:u": "load",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_xml", "ng:g": "n:g:z:load_xml",
"ng:o": [], "ng:o": [],
"ng:w": ["data/xml"], "ng:w": ["data:xml"],
}, },
"n:g:z:load_text": { "n:g:z:load_text": {
"ng:n": "Import Text", "ng:n": "Import Text",
@ -631,7 +631,7 @@ export const official_services = {
"ng:u": "load",// favicon. can be a did:ng:j "ng:u": "load",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_text", "ng:g": "n:g:z:load_text",
"ng:o": [], "ng:o": [],
"ng:w": ["post/text","post/rich","post/md"], "ng:w": ["post:text","post:rich","post:md"],
}, },
"n:g:z:load_md": { "n:g:z:load_md": {
"ng:n": "Import Markdown", "ng:n": "Import Markdown",
@ -640,7 +640,7 @@ export const official_services = {
"ng:u": "load",// favicon. can be a did:ng:j "ng:u": "load",// favicon. can be a did:ng:j
"ng:g": "n:g:z:load_md", "ng:g": "n:g:z:load_md",
"ng:o": [], "ng:o": [],
"ng:w": ["post/md"], "ng:w": ["post:md"],
}, },
"n:g:z:sparql_query": { "n:g:z:sparql_query": {
"ng:n": "SPARQL query", "ng:n": "SPARQL query",
@ -648,9 +648,9 @@ export const official_services = {
"ng:c": "service", "ng:c": "service",
"ng:u": "sparql_query",// favicon. can be a did:ng:j "ng:u": "sparql_query",// favicon. can be a did:ng:j
"ng:g": "n:g:z:sparql_query", "ng:g": "n:g:z:sparql_query",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": [], "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": { "n:g:z:sparql_update": {
"ng:n": "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:u": "sparql_query",// favicon. can be a did:ng:j
"ng:g": "n:g:z:sparql_update", "ng:g": "n:g:z:sparql_update",
"ng:o": [], "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 "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", "ng:n": "Export source",
@ -667,8 +667,8 @@ export const official_services = {
"ng:c": "service", "ng:c": "service",
"ng:u": "source",// favicon. can be a did:ng:j "ng:u": "source",// favicon. can be a did:ng:j
"ng:g": "n:g:z:dump_crdt_source", "ng:g": "n:g:z:dump_crdt_source",
"ng:o": ["data/graph"], "ng:o": ["data:graph"],
"ng:w": [], "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 () => { export default defineConfig(async () => {
const host = await internalIpV4() const host = await internalIpV4()
const config = { const config = {
// optimizeDeps: {
// exclude: ["codemirror", "@codemirror/legacy-modes/mode/sparql"]
// },
worker: { worker: {
format: 'es', format: 'es',
plugins : [ plugins : [

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

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

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

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

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

@ -46,6 +46,7 @@ quick-xml = ">=0.29, <0.32"
memchr = "2.5" memchr = "2.5"
peg = "0.8" peg = "0.8"
base64-url = "2.0.0" 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] [target.'cfg(all(not(target_family = "wasm"),not(docsrs)))'.dependencies]
libc = "0.2" libc = "0.2"

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

@ -300,18 +300,29 @@ impl Store {
graph_name: Option<GraphNameRef<'_>>, graph_name: Option<GraphNameRef<'_>>,
) -> QuadIter { ) -> QuadIter {
let reader = self.storage.snapshot(); 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 { QuadIter {
iter: reader.quads_for_pattern( iter: reader.quads_for_pattern(
subject.map(EncodedTerm::from).as_ref(), subject.map(EncodedTerm::from).as_ref(),
predicate.map(EncodedTerm::from).as_ref(), predicate.map(EncodedTerm::from).as_ref(),
object.map(EncodedTerm::from).as_ref(), object.map(EncodedTerm::from).as_ref(),
graph_name.map(|graph_name_ref| { match_by,
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");
}
}),
), ),
reader, reader,
} }
@ -1202,18 +1213,29 @@ impl<'a> Transaction<'a> {
graph_name: Option<GraphNameRef<'_>>, graph_name: Option<GraphNameRef<'_>>,
) -> QuadIter { ) -> QuadIter {
let reader = self.writer.reader(); 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 { QuadIter {
iter: reader.quads_for_pattern( iter: reader.quads_for_pattern(
subject.map(EncodedTerm::from).as_ref(), subject.map(EncodedTerm::from).as_ref(),
predicate.map(EncodedTerm::from).as_ref(), predicate.map(EncodedTerm::from).as_ref(),
object.map(EncodedTerm::from).as_ref(), object.map(EncodedTerm::from).as_ref(),
graph_name.map(|graph_name_ref| { match_by,
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");
}
}),
), ),
reader, 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`]. /// An iterator returning the graph names contained in a [`Store`].
pub struct GraphNameIter { pub struct GraphNameIter {
iter: DecodingGraphIterator, iter: DecodingGraphIterator,

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

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

@ -93,11 +93,17 @@ pub struct BranchInfo {
pub branch_type: BranchType, pub branch_type: BranchType,
pub topic: TopicId, pub crdt: BranchCrdt,
pub topic: Option<TopicId>,
pub topic_priv_key: Option<BranchWriteCapSecret>, 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>, pub current_heads: Vec<ObjectRef>,
@ -549,7 +555,7 @@ impl Repo {
pub fn overlay_branch_read_cap(&self) -> Option<&ReadCap> { pub fn overlay_branch_read_cap(&self) -> Option<&ReadCap> {
match self.overlay_branch() { 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 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( fn create_branch(
&self, &self,
branch_type: BranchType, branch_type: BranchType,
crdt: BranchCrdt,
creator: &UserId, creator: &UserId,
creator_priv_key: &PrivKey, creator_priv_key: &PrivKey,
repo_pub_key: BranchId, repo_pub_key: BranchId,
@ -193,7 +194,7 @@ impl Store {
let branch_commit_body = CommitBody::V0(CommitBodyV0::Branch(Branch::V0(BranchV0 { let branch_commit_body = CommitBody::V0(CommitBodyV0::Branch(Branch::V0(BranchV0 {
id: branch_pub_key, id: branch_pub_key,
content_type: BranchContentType::None, crdt: crdt.clone(),
repo: repository_commit_ref, repo: repository_commit_ref,
root_branch_readcap_id, root_branch_readcap_id,
topic: branch_topic_pub_key, topic: branch_topic_pub_key,
@ -227,9 +228,12 @@ impl Store {
let add_branch_commit_body = let add_branch_commit_body =
CommitBody::V0(CommitBodyV0::AddBranch(AddBranch::V0(AddBranchV0 { CommitBody::V0(CommitBodyV0::AddBranch(AddBranch::V0(AddBranchV0 {
branch_type: branch_type.clone(), branch_type: branch_type.clone(),
topic_id: branch_topic_pub_key,
branch_id: branch_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( let add_branch_commit = Commit::new_with_body_acks_deps_and_save(
@ -252,9 +256,12 @@ impl Store {
let branch_info = BranchInfo { let branch_info = BranchInfo {
id: branch_pub_key, id: branch_pub_key,
branch_type, branch_type,
topic: branch_topic_pub_key, topic: Some(branch_topic_pub_key),
topic_priv_key: Some(branch_topic_priv_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![], current_heads: vec![],
commits_nbr: 0, commits_nbr: 0,
}; };
@ -370,6 +377,7 @@ impl Store {
let (main_branch_commit, main_add_branch_commit, main_branch_info) = let (main_branch_commit, main_add_branch_commit, main_branch_info) =
self.as_ref().create_branch( self.as_ref().create_branch(
BranchType::Main, BranchType::Main,
BranchCrdt::Graph("data:container".to_string()),
creator, creator,
creator_priv_key, creator_priv_key,
repo_pub_key, repo_pub_key,
@ -390,6 +398,7 @@ impl Store {
let (store_branch_commit, store_add_branch_commit, store_branch_info) = let (store_branch_commit, store_add_branch_commit, store_branch_info) =
self.as_ref().create_branch( self.as_ref().create_branch(
BranchType::Store, BranchType::Store,
BranchCrdt::None,
creator, creator,
creator_priv_key, creator_priv_key,
repo_pub_key, repo_pub_key,
@ -413,6 +422,7 @@ impl Store {
} else { } else {
BranchType::Overlay BranchType::Overlay
}, },
BranchCrdt::None,
creator, creator,
creator_priv_key, creator_priv_key,
repo_pub_key, repo_pub_key,
@ -451,13 +461,13 @@ impl Store {
// creating signature for RootBranch, AddBranch and Branch commits // creating signature for RootBranch, AddBranch and Branch commits
// signed with owner threshold signature (threshold = 0) // 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 { if let Some((_, store_branch, oou_add_branch, oou_branch)) = &extra_branches {
signed_commits.append(&mut vec![ signed_commits.append(&mut vec![
oou_add_branch.id().unwrap(), oou_add_branch.id().unwrap(),
store_branch.read_cap.id, store_branch.read_cap.as_ref().unwrap().id,
oou_branch.read_cap.id, oou_branch.read_cap.as_ref().unwrap().id,
]); ]);
} else { } else {
signed_commits.push(main_add_branch_commit.id().unwrap()); signed_commits.push(main_add_branch_commit.id().unwrap());
@ -573,8 +583,8 @@ impl Store {
creator, creator,
*branch_id, *branch_id,
QuorumType::IamTheSignature, QuorumType::IamTheSignature,
vec![branch_info.read_cap.clone()], vec![branch_info.read_cap.as_ref().unwrap().clone()],
vec![branch_info.read_cap.clone()], vec![branch_info.read_cap.as_ref().unwrap().clone()],
sync_sig_commit_body.clone(), sync_sig_commit_body.clone(),
&self, &self,
)?; )?;
@ -606,9 +616,12 @@ impl Store {
let root_branch = BranchInfo { let root_branch = BranchInfo {
id: repo_pub_key.clone(), id: repo_pub_key.clone(),
branch_type: BranchType::Root, branch_type: BranchType::Root,
topic: topic_pub_key, topic: Some(topic_pub_key),
topic_priv_key: Some(topic_priv_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], current_heads: vec![sync_sig_on_root_branch_commit_ref],
commits_nbr: 0, commits_nbr: 0,
}; };

@ -463,8 +463,13 @@ impl BlockRef {
BlockRef { id, key } BlockRef { id, key }
} }
pub fn nuri(&self) -> String { pub fn object_nuri(&self) -> String {
format!(":j:{}:k:{}", self.id, self.key) 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 { pub fn tokenize(&self) -> Digest {
@ -627,33 +632,51 @@ pub enum StoreOverlayV0 {
Dialog(Digest), 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)] #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum StoreOverlay { pub enum StoreOverlay {
V0(StoreOverlayV0), 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 { impl fmt::Display for StoreOverlay {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::V0(v0) => { Self::V0(v0) => writeln!(f, "{}", v0),
write!(f, "StoreOverlay V0 ")?; Self::OwnV0(v0) => writeln!(f, "Own: {}", 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),
} }
} }
} }
impl StoreOverlay { impl StoreOverlay {
pub fn from_store_repo(overlay_branch: BranchId) -> StoreOverlay { pub fn from_store_repo(store_repo: &StoreRepo, overlay_branch: BranchId) -> StoreOverlay {
StoreOverlay::Own(overlay_branch) 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 { pub fn overlay_id_for_read_purpose(&self) -> OverlayId {
@ -663,7 +686,7 @@ impl StoreOverlay {
| StoreOverlay::V0(StoreOverlayV0::PrivateStore(id)) | StoreOverlay::V0(StoreOverlayV0::PrivateStore(id))
| StoreOverlay::V0(StoreOverlayV0::Group(id)) => OverlayId::outer(id), | StoreOverlay::V0(StoreOverlayV0::Group(id)) => OverlayId::outer(id),
StoreOverlay::V0(StoreOverlayV0::Dialog(d)) => OverlayId::Inner(d.clone().to_slice()), 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) OverlayId::inner(id, &store_overlay_branch_readcap_secret)
} }
StoreOverlay::V0(StoreOverlayV0::Dialog(d)) => OverlayId::Inner(d.clone().to_slice()), 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 { 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 { pub fn repo_id(&self) -> &RepoId {
match self { match self {
Self::V0(v0) => match v0 { Self::V0(v0) => match v0 {
@ -1292,17 +1328,59 @@ pub enum Quorum {
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum BranchContentType { pub enum BranchCrdt {
Graph, Graph(String),
YMap, YMap(String),
YXml, YArray(String),
YText, YXml(String),
Automerge, YText(String),
Automerge(String),
Elmer(String),
//Rdfs, //Rdfs,
//Owl, //Owl,
//Shacl, //Shacl,
//Shex, //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 /// Branch definition
@ -1318,7 +1396,7 @@ pub struct BranchV0 {
/// Branch public key ID /// Branch public key ID
pub id: PubKey, pub id: PubKey,
pub content_type: BranchContentType, pub crdt: BranchCrdt,
/// Reference to the repository commit /// Reference to the repository commit
pub repo: ObjectRef, pub repo: ObjectRef,
@ -1383,8 +1461,7 @@ pub enum BranchType {
Comments, Comments,
BackLinks, BackLinks,
Context, 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. 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 Root, // only used for BranchInfo
//Unknown, // only used temporarily when loading a branch info from commits (Branch commit, then AddBranch commit) //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::Comments => "Comments",
Self::BackLinks => "BackLinks", Self::BackLinks => "BackLinks",
Self::Context => "Context", Self::Context => "Context",
Self::Ontology => "Ontology", //Self::Ontology => "Ontology",
//Self::Unknown => "==unknown==", //Self::Unknown => "==unknown==",
} }
) )
@ -1428,17 +1505,24 @@ impl fmt::Display for BranchType {
/// DEPS: if update branch: previous AddBranch commit of the same branchId /// DEPS: if update branch: previous AddBranch commit of the same branchId
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct AddBranchV0 { pub struct AddBranchV0 {
/// the new topic_id (will be needed immediately by future readers // 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 // in order to subscribe to the pub/sub). should be identical to the one in the Branch definition.
pub topic_id: TopicId, // 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_id: BranchId,
pub branch_type: BranchType, pub branch_type: BranchType,
// the new branch definition commit pub fork_of: Option<BranchId>,
// (we need the ObjectKey in order to open the pub/sub Event)
pub branch_read_cap: ReadCap, pub merged_in: Option<BranchId>,
} }
impl fmt::Display for AddBranch { impl fmt::Display for AddBranch {
@ -1446,8 +1530,23 @@ impl fmt::Display for AddBranch {
match self { match self {
Self::V0(v0) => { Self::V0(v0) => {
writeln!(f, "V0 {}", v0.branch_type)?; writeln!(f, "V0 {}", v0.branch_type)?;
writeln!(f, "topic_id: {}", v0.topic_id)?; writeln!(f, "branch_id: {}", v0.branch_id)?;
writeln!(f, "branch_read_cap: {}", v0.branch_read_cap)?; 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(()) Ok(())
} }
} }

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

@ -21,10 +21,9 @@ use std::sync::Arc;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json;
// use js_sys::Reflect; // use js_sys::Reflect;
use async_std::stream::StreamExt; use async_std::stream::StreamExt;
use js_sys::Array; use js_sys::{Array, Object};
use oxrdf::Triple; use oxrdf::Triple;
use sys_locale::get_locales; use sys_locale::get_locales;
use wasm_bindgen::prelude::*; 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] #[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) let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Invalid session_id".to_string())?; .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 { let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_write_query(), command: AppRequestCommandV0::new_write_query(),
nuri: NuriV0::new_private_store_target(), nuri,
payload: Some(AppRequestPayload::new_sparql_query(sparql)), payload: Some(AppRequestPayload::new_sparql_query(sparql)),
session_id, session_id,
}); });
@ -765,7 +768,50 @@ pub async fn app_request_stream(
callback: js_sys::Function, callback: js_sys::Function,
) -> ResultSend<()> { ) -> ResultSend<()> {
while let Some(app_response) = reader.next().await { 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(); 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(); let this = JsValue::null();
match callback.call1(&this, &response_js) { match callback.call1(&this, &response_js) {
Ok(jsval) => { Ok(jsval) => {
@ -1024,7 +1070,7 @@ pub async fn file_put_to_private_store(
.map_err(|e| e.as_string().unwrap())?; .map_err(|e| e.as_string().unwrap())?;
let reference = serde_wasm_bindgen::from_value::<ObjectRef>(reference) let reference = serde_wasm_bindgen::from_value::<ObjectRef>(reference)
.map_err(|_| "Deserialization error of reference".to_string())?; .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) Ok(nuri)
} }
@ -1098,10 +1144,10 @@ pub async fn doc_fetch_private_subscribe() -> Result<JsValue, String> {
} }
#[wasm_bindgen] #[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( let request = AppRequest::new(
AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)), 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, None,
); );
Ok(serde_wasm_bindgen::to_value(&request).unwrap()) Ok(serde_wasm_bindgen::to_value(&request).unwrap())

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

@ -110,9 +110,12 @@ impl CommitVerifier for RootBranch {
let root_branch = BranchInfo { let root_branch = BranchInfo {
id: root_branch.id.clone(), id: root_branch.id.clone(),
branch_type: BranchType::Root, branch_type: BranchType::Root,
topic: root_branch.topic, topic: Some(root_branch.topic),
topic_priv_key, 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()], current_heads: vec![reference.clone()],
commits_nbr: 1, commits_nbr: 1,
}; };
@ -180,7 +183,7 @@ impl CommitVerifier for Branch {
let reference = commit.reference().unwrap(); let reference = commit.reference().unwrap();
let branch_info = repo.branch_mut(&branch.id)?; 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); return Err(VerifierError::InvalidBranch);
} }
branch_info.topic_priv_key = topic_priv_key; branch_info.topic_priv_key = topic_priv_key;
@ -248,12 +251,17 @@ impl CommitVerifier for AddBranch {
return Err(VerifierError::InvalidBranch); 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 { let branch_info = BranchInfo {
id: v0.branch_id, id: v0.branch_id,
branch_type: v0.branch_type.clone(), branch_type: v0.branch_type.clone(),
topic: v0.topic_id, topic: v0.topic_id,
topic_priv_key: None, topic_priv_key: None,
read_cap: v0.branch_read_cap.clone(), read_cap: v0.branch_read_cap.clone(),
fork_of: v0.fork_of,
merged_in: v0.merged_in,
crdt: v0.crdt.clone(),
current_heads: vec![], current_heads: vec![],
commits_nbr: 0, commits_nbr: 0,
}; };
@ -467,8 +475,8 @@ impl CommitVerifier for AddFile {
.push_app_response( .push_app_response(
branch_id, branch_id,
AppResponse::V0(AppResponseV0::Patch(AppPatch { AppResponse::V0(AppResponseV0::Patch(AppPatch {
commit_id, commit_id: commit_id.to_string(),
commit_info: commit.as_info(repo), commit_info: (&commit.as_info(repo)).into(),
graph: None, graph: None,
discrete: None, discrete: None,
other: Some(OtherPatch::FileAdd(filename)), 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 ng_oxigraph::oxigraph::storage_ng::*;
use serde::{Deserialize, Serialize}; 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_oxigraph::oxrdf::{GraphName, GraphNameRef, NamedNode, Quad, Triple, TripleRef};
use ng_repo::errors::VerifierError; use ng_repo::errors::VerifierError;
use ng_repo::log::*; use ng_repo::log::*;
@ -31,12 +31,23 @@ pub struct GraphTransaction {
pub removes: Vec<Triple>, 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)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DiscreteTransaction { pub enum DiscreteTransaction {
/// A yrs::Update /// A yrs::Update
#[serde(with = "serde_bytes")] #[serde(with = "serde_bytes")]
YMap(Vec<u8>), YMap(Vec<u8>),
#[serde(with = "serde_bytes")] #[serde(with = "serde_bytes")]
YArray(Vec<u8>),
#[serde(with = "serde_bytes")]
YXml(Vec<u8>), YXml(Vec<u8>),
#[serde(with = "serde_bytes")] #[serde(with = "serde_bytes")]
YText(Vec<u8>), YText(Vec<u8>),
@ -69,6 +80,7 @@ struct BranchUpdateInfo {
previous_heads: HashSet<ObjectId>, previous_heads: HashSet<ObjectId>,
commit_id: ObjectId, commit_id: ObjectId,
transaction: GraphTransaction, transaction: GraphTransaction,
commit_info: CommitInfoJs,
} }
impl Verifier { impl Verifier {
@ -210,24 +222,27 @@ impl Verifier {
let repo = self.get_repo(repo_id, store.get_store_repo())?; let repo = self.get_repo(repo_id, store.get_store_repo())?;
let branch = repo.branch(branch_id)?; 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() { if body.graph.is_some() {
let info = BranchUpdateInfo { let info = BranchUpdateInfo {
branch_id: *branch_id, branch_id: *branch_id,
branch_is_main: branch.branch_type.is_main(), branch_is_main: branch.branch_type.is_main(),
repo_id: *repo_id, repo_id: *repo_id,
topic_id: branch.topic, topic_id: branch.topic.clone().unwrap(),
token: branch.read_cap.tokenize(), token: branch.read_cap.as_ref().unwrap().tokenize(),
overlay_id: store.overlay_id, overlay_id: store.overlay_id,
previous_heads: commit.direct_causal_past_ids(), previous_heads: commit.direct_causal_past_ids(),
commit_id: commit.id().unwrap(), commit_id,
transaction: body.graph.take().unwrap(), transaction: body.graph.take().unwrap(),
commit_info,
}; };
self.update_graph(&[info]) self.update_graph(vec![info]).await?;
} else {
Ok(())
} }
//TODO: discrete update //TODO: discrete update
Ok(())
} }
fn find_branch_and_repo_for_quad( fn find_branch_and_repo_for_quad(
@ -258,8 +273,8 @@ impl Verifier {
b.id, b.id,
b.topic_priv_key.is_some(), b.topic_priv_key.is_some(),
true, true,
b.topic, b.topic.clone().unwrap(),
b.read_cap.tokenize(), b.read_cap.as_ref().unwrap().tokenize(),
) )
} }
Some(TargetBranchV0::BranchId(id)) => { Some(TargetBranchV0::BranchId(id)) => {
@ -269,8 +284,8 @@ impl Verifier {
id, id,
b.topic_priv_key.is_some(), b.topic_priv_key.is_some(),
false, false,
b.topic, b.topic.clone().unwrap(),
b.read_cap.tokenize(), b.read_cap.as_ref().unwrap().tokenize(),
) )
} }
// TODO: implement TargetBranchV0::Named // TODO: implement TargetBranchV0::Named
@ -367,6 +382,9 @@ impl Verifier {
) )
.await?; .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 graph_update = transac.graph.take().unwrap();
let info = BranchUpdateInfo { let info = BranchUpdateInfo {
@ -379,21 +397,24 @@ impl Verifier {
previous_heads: commit.direct_causal_past_ids(), previous_heads: commit.direct_causal_past_ids(),
commit_id: commit.id().unwrap(), commit_id: commit.id().unwrap(),
transaction: graph_update, transaction: graph_update,
commit_info,
}; };
updates.push(info); updates.push(info);
} }
self.update_graph(&updates) self.update_graph(updates).await
} }
fn update_graph(&mut self, updates: &[BranchUpdateInfo]) -> Result<(), VerifierError> { async fn update_graph(&mut self, updates: Vec<BranchUpdateInfo>) -> Result<(), VerifierError> {
self.graph_dataset let updates_ref = &updates;
let res = self
.graph_dataset
.as_ref() .as_ref()
.unwrap() .unwrap()
.ng_transaction( .ng_transaction(
move |mut transaction| -> Result<(), ng_oxigraph::oxigraph::store::StorageError> { move |mut transaction| -> Result<(), ng_oxigraph::oxigraph::store::StorageError> {
let reader = transaction.ng_get_reader(); let reader = transaction.ng_get_reader();
for update in updates { for update in updates_ref {
let commit_name = let commit_name =
NuriV0::commit_graph_name(&update.commit_id, &update.overlay_id); NuriV0::commit_graph_name(&update.commit_id, &update.overlay_id);
let commit_encoded = numeric_encoder::StrHash::new(&commit_name); 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 // 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 { let value = if update.branch_is_main {
REMOVED_IN_MAIN REMOVED_IN_MAIN
} else { } else {
@ -487,6 +508,7 @@ impl Verifier {
}; };
for remove in update.transaction.removes.iter() { for remove in update.transaction.removes.iter() {
log_info!("REMOVING {}", remove.to_string());
let encoded_subject = remove.subject.as_ref().into(); let encoded_subject = remove.subject.as_ref().into();
let encoded_predicate = remove.predicate.as_ref().into(); let encoded_predicate = remove.predicate.as_ref().into();
let encoded_object = remove.object.as_ref().into(); let encoded_object = remove.object.as_ref().into();
@ -498,6 +520,11 @@ impl Verifier {
&direct_causal_past_encoded, &direct_causal_past_encoded,
at_current_heads, at_current_heads,
)?; )?;
log_info!(
"direct_causal_past_encoded {:?}",
direct_causal_past_encoded
);
log_info!("observed_adds {:?}", observed_adds);
for removing in observed_adds { for removing in observed_adds {
let graph_encoded = EncodedTerm::NamedNode { iri_id: removing }; let graph_encoded = EncodedTerm::NamedNode { iri_id: removing };
let quad_encoded = EncodedQuad::new( let quad_encoded = EncodedQuad::new(
@ -521,6 +548,10 @@ impl Verifier {
)? )?
.is_empty() .is_empty()
}; };
log_info!(
"should_remove_ov_triples {}",
should_remove_ov_triples
);
if should_remove_ov_triples { if should_remove_ov_triples {
let ov_graphname_ref = let ov_graphname_ref =
GraphNameRef::NamedNode(ov_graphname.into()); GraphNameRef::NamedNode(ov_graphname.into());
@ -536,7 +567,24 @@ impl Verifier {
Ok(()) 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( pub(crate) async fn process_sparql_update(

@ -13,3 +13,41 @@ mod request_processor;
#[cfg(all(not(target_family = "wasm"), not(docsrs)))] #[cfg(all(not(target_family = "wasm"), not(docsrs)))]
mod rocksdb_user_storage; 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 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::block_storage::BlockStorage;
use ng_repo::log::*; use ng_repo::log::*;
use ng_repo::repo::{BranchInfo, Repo}; 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> { fn branch_get_all_files(&self, branch: &BranchId) -> Result<Vec<FileName>, StorageError> {
BranchStorage::get_all_files(&branch, &self.user_storage) 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 READ_CAP: u8 = b'r';
const TOPIC: u8 = b't'; const TOPIC: u8 = b't';
const COMMITS_NBR: u8 = b'n'; 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::TYPE,
Self::PUBLISHER, Self::PUBLISHER,
Self::READ_CAP, Self::READ_CAP,
Self::TOPIC, Self::TOPIC,
Self::COMMITS_NBR, Self::COMMITS_NBR,
Self::FORK_OF,
Self::MERGED_IN,
Self::CRDT,
Self::CLASS,
]; ];
const PREFIX_HEADS: u8 = b'h'; const PREFIX_HEADS: u8 = b'h';
@ -83,6 +91,9 @@ impl<'a> BranchStorage<'a> {
&info.read_cap, &info.read_cap,
&info.branch_type, &info.branch_type,
&info.topic, &info.topic,
&info.fork_of,
&info.merged_in,
&info.crdt,
info.topic_priv_key.as_ref(), info.topic_priv_key.as_ref(),
&info.current_heads, &info.current_heads,
storage, storage,
@ -93,9 +104,12 @@ impl<'a> BranchStorage<'a> {
pub fn create( pub fn create(
id: &BranchId, id: &BranchId,
read_cap: &ReadCap, read_cap: &Option<ReadCap>,
branch_type: &BranchType, branch_type: &BranchType,
topic: &TopicId, topic: &Option<TopicId>,
fork_of: &Option<BranchId>,
merged_in: &Option<BranchId>,
crdt: &BranchCrdt,
publisher: Option<&BranchWriteCapSecret>, publisher: Option<&BranchWriteCapSecret>,
current_heads: &Vec<ObjectRef>, current_heads: &Vec<ObjectRef>,
storage: &'a dyn KCVStorage, storage: &'a dyn KCVStorage,
@ -110,12 +124,31 @@ impl<'a> BranchStorage<'a> {
storage.write_transaction(&mut |tx| { storage.write_transaction(&mut |tx| {
let id_ser = to_vec(&id)?; let id_ser = to_vec(&id)?;
let value = to_vec(read_cap)?; if read_cap.is_some() {
tx.put(Self::PREFIX, &id_ser, Some(Self::READ_CAP), &value, &None)?; 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)?; let value = to_vec(branch_type)?;
tx.put(Self::PREFIX, &id_ser, Some(Self::TYPE), &value, &None)?; tx.put(Self::PREFIX, &id_ser, Some(Self::TYPE), &value, &None)?;
let value = to_vec(topic)?; if topic.is_some() {
tx.put(Self::PREFIX, &id_ser, Some(Self::TOPIC), &value, &None)?; 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 { if let Some(privkey) = publisher {
let value = to_vec(privkey)?; let value = to_vec(privkey)?;
tx.put(Self::PREFIX, &id_ser, Some(Self::PUBLISHER), &value, &None)?; tx.put(Self::PREFIX, &id_ser, Some(Self::PUBLISHER), &value, &None)?;
@ -140,11 +173,22 @@ impl<'a> BranchStorage<'a> {
&None, &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 { let bs = BranchInfo {
id: id.clone(), id: id.clone(),
branch_type: prop(Self::TYPE, &props)?, branch_type: prop(Self::TYPE, &props)?,
read_cap: prop(Self::READ_CAP, &props)?, read_cap: prop(Self::READ_CAP, &props).ok(),
topic: prop(Self::TOPIC, &props)?, 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(), topic_priv_key: prop(Self::PUBLISHER, &props).ok(),
current_heads: Self::get_all_heads(id, storage)?, current_heads: Self::get_all_heads(id, storage)?,
commits_nbr: prop(Self::COMMITS_NBR, &props).unwrap_or(0), commits_nbr: prop(Self::COMMITS_NBR, &props).unwrap_or(0),

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

@ -14,7 +14,7 @@ use std::{
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use ng_net::app_protocol::FileName; use ng_net::app_protocol::{AppTabInfo, FileName};
use ng_repo::{ use ng_repo::{
block_storage::BlockStorage, block_storage::BlockStorage,
errors::StorageError, 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_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( fn update_branch_current_heads(
&self, &self,
repo_id: &RepoId, 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> { fn get_all_store_and_repo_ids(&self) -> Result<HashMap<StoreRepo, Vec<RepoId>>, StorageError> {
unimplemented!(); unimplemented!();
} }

@ -26,7 +26,6 @@ use async_std::sync::{Mutex, RwLockReadGuard};
use bloomfilter::Bloom; use bloomfilter::Bloom;
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::SinkExt; use futures::SinkExt;
use ng_repo::object::Object;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use web_time::SystemTime; use web_time::SystemTime;
@ -35,6 +34,8 @@ use web_time::SystemTime;
//use ng_oxigraph::oxigraph::model::GroundQuad; //use ng_oxigraph::oxigraph::model::GroundQuad;
//use yrs::{StateVector, Update}; //use yrs::{StateVector, Update};
use ng_oxigraph::oxrdf::{GraphNameRef, NamedNode, Triple};
use ng_repo::file::ReadFile; use ng_repo::file::ReadFile;
use ng_repo::log::*; use ng_repo::log::*;
#[cfg(any(test, feature = "testing"))] #[cfg(any(test, feature = "testing"))]
@ -43,6 +44,7 @@ use ng_repo::{
block_storage::{store_max_value_size, BlockStorage, HashMapBlockStorage}, block_storage::{store_max_value_size, BlockStorage, HashMapBlockStorage},
errors::{NgError, ProtocolError, ServerError, StorageError, VerifierError}, errors::{NgError, ProtocolError, ServerError, StorageError, VerifierError},
file::RandomAccessFile, file::RandomAccessFile,
object::Object,
repo::{BranchInfo, Repo}, repo::{BranchInfo, Repo},
store::Store, store::Store,
types::*, 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( pub(crate) async fn create_branch_subscription(
&mut self, &mut self,
repo_id: RepoId, repo_id: RepoId,
@ -260,12 +305,54 @@ impl Verifier {
.as_ref() .as_ref()
.unwrap() .unwrap()
.branch_get_all_files(&branch_id)?; .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 { let state = AppState {
heads: branch.current_heads.iter().map(|h| h.id.clone()).collect(), 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, discrete: None,
files, 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))) self.push_app_response(&branch_id, AppResponse::V0(AppResponseV0::State(state)))
.await; .await;
@ -1096,7 +1183,7 @@ impl Verifier {
// pinning the repo on the server broker // pinning the repo on the server broker
let (pin_req, topic_id) = { let (pin_req, topic_id) = {
let repo = self.repos.get(repo_id).ok_or(NgError::RepoNotFound)?; 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. //TODO: only pin the requested branch.
let pin_req = PinRepo::from_repo(repo, remote_broker.broker_peer_id()); let pin_req = PinRepo::from_repo(repo, remote_broker.broker_peer_id());
(pin_req, topic_id) (pin_req, topic_id)
@ -1135,7 +1222,7 @@ impl Verifier {
// checking that the branch is subscribed as publisher // checking that the branch is subscribed as publisher
let repo = self.repos.get(repo_id).ok_or(NgError::RepoNotFound)?; let repo = self.repos.get(repo_id).ok_or(NgError::RepoNotFound)?;
let branch_info = repo.branch(branch)?; let branch_info = repo.branch(branch)?;
let topic_id = &branch_info.topic; let topic_id = branch_info.topic.as_ref().unwrap();
// log_info!( // log_info!(
// "as_publisher {} {}", // "as_publisher {} {}",
// as_publisher, // as_publisher,
@ -1266,7 +1353,12 @@ impl Verifier {
.ok_or(VerifierError::BranchNotOpened)?; .ok_or(VerifierError::BranchNotOpened)?;
let branch = repo.branch(&branch_id)?; 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)) self.verify_commit(&commit, &branch_id, &repo_id, Arc::clone(&repo.store))
.await?; .await?;
@ -1353,11 +1445,15 @@ impl Verifier {
fn user_storage_if_persistent(&self) -> Option<Arc<Box<dyn UserStorage>>> { fn user_storage_if_persistent(&self) -> Option<Arc<Box<dyn UserStorage>>> {
if self.is_persistent() { if self.is_persistent() {
if let Some(us) = self.user_storage.as_ref() { self.user_storage()
Some(Arc::clone(us)) } else {
} else { None
None }
} }
fn user_storage(&self) -> Option<Arc<Box<dyn UserStorage>>> {
if let Some(us) = self.user_storage.as_ref() {
Some(Arc::clone(us))
} else { } else {
None None
} }
@ -1373,7 +1469,7 @@ impl Verifier {
user_storage.add_branch(repo_id, &branch_info)?; user_storage.add_branch(repo_id, &branch_info)?;
} }
let branch_id = branch_info.id.clone(); 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 repo = self.get_repo_mut(repo_id, store_repo)?;
let res = repo.branches.insert(branch_info.id.clone(), branch_info); let res = repo.branches.insert(branch_info.id.clone(), branch_info);
assert!(res.is_none()); assert!(res.is_none());
@ -1527,13 +1623,17 @@ impl Verifier {
}; };
let msg = TopicSyncReq::V0(TopicSyncReqV0 { 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(), known_heads: ours_set.union(&theirs_found).into_iter().cloned().collect(),
target_heads: theirs_not_found, target_heads: theirs_not_found,
known_commits, known_commits,
overlay: Some(store.overlay_for_read_on_client_protocol()), 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 match broker
@ -1640,8 +1740,8 @@ impl Verifier {
.map(|(branch_id, branch)| { .map(|(branch_id, branch)| {
( (
branch_id.clone(), branch_id.clone(),
branch.topic.clone(), branch.topic.clone().unwrap(),
branch.read_cap.key.clone(), branch.read_cap.as_ref().unwrap().key.clone(),
) )
}) })
.collect(); .collect();
@ -1831,7 +1931,7 @@ impl Verifier {
.get(&(e.overlay, *e.event.topic_id())) .get(&(e.overlay, *e.event.topic_id()))
.ok_or(VerifierError::TopicNotFound)?; .ok_or(VerifierError::TopicNotFound)?;
let branch = repo.branch(branch_id)?; 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())) .get(&(e.overlay, *e.event.topic_id()))
.ok_or(VerifierError::TopicNotFound)?; .ok_or(VerifierError::TopicNotFound)?;
let branch = repo.branch(branch_id)?; 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.store,
&repo_id, &repo_id,
&branch_id, &branch_id,
&branch.read_cap.key, &branch.read_cap.as_ref().unwrap().key,
)?; )?;
let store_repo = repo.store.get_store_repo().clone(); let store_repo = repo.store.get_store_repo().clone();
@ -2206,7 +2306,7 @@ impl Verifier {
pub(crate) fn populate_topics(&mut self, repo: &Repo) { pub(crate) fn populate_topics(&mut self, repo: &Repo) {
for (branch_id, info) in repo.branches.iter() { for (branch_id, info) in repo.branches.iter() {
let overlay_id: OverlayId = repo.store.inner_overlay(); 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 repo_id = repo.id.clone();
let branch_id = branch_id.clone(); let branch_id = branch_id.clone();
let _res = self let _res = self

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

@ -250,7 +250,7 @@
/> />
</svg> </svg>
<span <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 end-to-end encrypted and we do not have access to your
decryption keys, meaning that we cannot see the content of your decryption keys, meaning that we cannot see the content of your
documents.</span documents.</span
@ -279,7 +279,7 @@
to the broker. We collect general purpose information about your to the broker. We collect general purpose information about your
device (OS version, browser version, and if you use the app, the 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 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 iPhone IDFA). We could nevertheless be asked by law enforcement
authorities, depending on the jurisdiction of the server, to log authorities, depending on the jurisdiction of the server, to log
the IP you use when connecting to the broker, and/or to provide the IP you use when connecting to the broker, and/or to provide
@ -305,12 +305,18 @@
/> />
</svg> </svg>
<span> <span>
You can delete your account with us at any time by going to the You can delete your account with us at any time by opening your NextGraph application, logging in,
link <span 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
style="font-weight: 500; >
color: #646cff;">account.{domain}/#/delete</span </li>
> or by entering in your NextGraph application and selecting the <li class="flex space-x-3">
menu, then Accounts, then under broker "delete registration"</span <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>
<li class="flex space-x-3"> <li class="flex space-x-3">
@ -363,11 +369,11 @@
<span <span
>By agreeing to those terms, you allow this software to store >By agreeing to those terms, you allow this software to store
some personal data locally in localStorage, the equivalent of some personal data locally on this device, in localStorage.
a cookie. This cookie contains your wallet and is never sent This data contains your wallet and is never sent
to us. If you delete this cookie without keeping a copy of to us. It only stays on this device or any other device where you will import your wallet.
your wallet somewhere else, then you will permanently loose If you delete this data without keeping a copy of
your wallet. your wallet somewhere else, then access to your wallet and all its documents will be permanently lost.
</span> </span>
</li> </li>
{/if} {/if}

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

@ -12,6 +12,8 @@ importers:
ng-app: ng-app:
specifiers: specifiers:
'@codemirror/language': ^6.10.2
'@codemirror/legacy-modes': ^6.4.0
'@popperjs/core': ^2.11.8 '@popperjs/core': ^2.11.8
'@sveltejs/vite-plugin-svelte': ^2.0.0 '@sveltejs/vite-plugin-svelte': ^2.0.0
'@tauri-apps/api': 2.0.0-alpha.8 '@tauri-apps/api': 2.0.0-alpha.8
@ -23,6 +25,7 @@ importers:
async-proxy: ^0.4.1 async-proxy: ^0.4.1
autoprefixer: ^10.4.14 autoprefixer: ^10.4.14
classnames: ^2.3.2 classnames: ^2.3.2
codemirror: ^6.0.0
cross-env: ^7.0.3 cross-env: ^7.0.3
dayjs: ^1.11.10 dayjs: ^1.11.10
flowbite: ^1.6.5 flowbite: ^1.6.5
@ -34,10 +37,13 @@ importers:
postcss: ^8.4.23 postcss: ^8.4.23
postcss-load-config: ^4.0.1 postcss-load-config: ^4.0.1
shx: ^0.3.4 shx: ^0.3.4
sparql: link:@codemirror/legacy-modes/mode/sparql
svelte: ^3.54.0 svelte: ^3.54.0
svelte-check: ^3.0.0 svelte-check: ^3.0.0
svelte-codemirror-editor: ^1.4.0
svelte-heros-v2: ^0.10.12 svelte-heros-v2: ^0.10.12
svelte-i18n: ^4.0.0 svelte-i18n: ^4.0.0
svelte-inview: ^4.0.2
svelte-preprocess: ^5.0.3 svelte-preprocess: ^5.0.3
svelte-spa-router: ^3.3.0 svelte-spa-router: ^3.3.0
svelte-time: ^0.8.0 svelte-time: ^0.8.0
@ -50,17 +56,23 @@ importers:
vite-plugin-top-level-await: ^1.3.1 vite-plugin-top-level-await: ^1.3.1
vite-plugin-wasm: ^3.2.2 vite-plugin-wasm: ^3.2.2
dependencies: dependencies:
'@codemirror/language': 6.10.2
'@codemirror/legacy-modes': 6.4.0
'@popperjs/core': 2.11.8 '@popperjs/core': 2.11.8
'@tauri-apps/api': 2.0.0-alpha.8 '@tauri-apps/api': 2.0.0-alpha.8
'@tauri-apps/plugin-barcode-scanner': 2.0.0-alpha.0 '@tauri-apps/plugin-barcode-scanner': 2.0.0-alpha.0
'@tauri-apps/plugin-window': 2.0.0-alpha.1 '@tauri-apps/plugin-window': 2.0.0-alpha.1
async-proxy: 0.4.1 async-proxy: 0.4.1
classnames: 2.3.2 classnames: 2.3.2
codemirror: 6.0.1
flowbite: 1.6.5 flowbite: 1.6.5
flowbite-svelte: 0.43.3_svelte@3.59.1 flowbite-svelte: 0.43.3_svelte@3.59.1
html5-qrcode: 2.3.8 html5-qrcode: 2.3.8
ng-sdk-js: link:../ng-sdk-js/pkg 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-i18n: 4.0.0_svelte@3.59.1
svelte-inview: 4.0.2_svelte@3.59.1
svelte-spa-router: 3.3.0 svelte-spa-router: 3.3.0
vite-plugin-top-level-await: 1.3.1_vite@4.3.9 vite-plugin-top-level-await: 1.3.1_vite@4.3.9
devDependencies: devDependencies:
@ -128,6 +140,71 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true 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: /@esbuild/aix-ppc64/0.19.12:
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -596,6 +673,22 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14 '@jridgewell/sourcemap-codec': 1.4.14
dev: true 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: /@nodelib/fs.scandir/2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -1048,6 +1141,18 @@ packages:
timers-ext: 0.1.8 timers-ext: 0.1.8
dev: false 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: /commander/4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -1062,6 +1167,10 @@ packages:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true dev: true
/crelt/1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
dev: false
/cross-env/7.0.3: /cross-env/7.0.3:
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
@ -2086,6 +2195,10 @@ packages:
min-indent: 1.0.1 min-indent: 1.0.1
dev: true dev: true
/style-mod/4.1.2:
resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==}
dev: false
/sucrase/3.32.0: /sucrase/3.32.0:
resolution: {integrity: sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==} resolution: {integrity: sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -2132,6 +2245,16 @@ packages:
- sugarss - sugarss
dev: true 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: /svelte-heros-v2/0.10.12_svelte@3.59.1:
resolution: {integrity: sha512-0wspy0z9UFS9f/iPKQQ1JDHlNY6e7h+LVW+wJ0qJnuWDpvsJllmoCX2g0frYbMPDWZJEwh2pkO25Dp3lDGCxGQ==} resolution: {integrity: sha512-0wspy0z9UFS9f/iPKQQ1JDHlNY6e7h+LVW+wJ0qJnuWDpvsJllmoCX2g0frYbMPDWZJEwh2pkO25Dp3lDGCxGQ==}
peerDependencies: peerDependencies:
@ -2166,6 +2289,14 @@ packages:
tiny-glob: 0.2.9 tiny-glob: 0.2.9
dev: false 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: /svelte-preprocess/5.0.4_4klotfyqh3bzvqmua74kcysa7a:
resolution: {integrity: sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==} resolution: {integrity: sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==}
engines: {node: '>= 14.10.0'} engines: {node: '>= 14.10.0'}
@ -2577,6 +2708,10 @@ packages:
vite: 4.3.9_@types+node@18.16.16 vite: 4.3.9_@types+node@18.16.16
dev: true dev: true
/w3c-keyname/2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
dev: false
/which/2.0.2: /which/2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'} engines: {node: '>= 8'}

Loading…
Cancel
Save