From 7c621509d2386ac2c24d1725ef2a1f5e77f5ef7a Mon Sep 17 00:00:00 2001 From: Niko PLP Date: Thu, 25 Jul 2024 16:31:03 +0300 Subject: [PATCH] navigation between docs and tabs, sparql update editor --- Cargo.lock | 4 +- nextgraph/src/lib.rs | 1 + nextgraph/src/local_broker.rs | 363 ++++++++++++------- ng-app/package.json | 6 + ng-app/src-tauri/Cargo.toml | 1 + ng-app/src-tauri/src/lib.rs | 5 +- ng-app/src/App.svelte | 8 +- ng-app/src/api.ts | 36 +- ng-app/src/apps/ListView.svelte | 49 +++ ng-app/src/apps/SparqlUpdateEditor.svelte | 61 ++++ ng-app/src/base64url.js | 59 ++++ ng-app/src/classes.ts | 371 ++++++++++---------- ng-app/src/lib/DataClassIcon.svelte | 116 +++--- ng-app/src/lib/Document.svelte | 111 ++++++ ng-app/src/lib/FullLayout.svelte | 350 +++++++++--------- ng-app/src/lib/Home.svelte | 44 ++- ng-app/src/lib/Login.svelte | 4 +- ng-app/src/lib/Test.svelte | 24 +- ng-app/src/lib/components/Logo.svelte | 2 +- ng-app/src/lib/components/NavBar.svelte | 36 +- ng-app/src/lib/components/NavIcon.svelte | 10 + ng-app/src/lib/components/PaneHeader.svelte | 19 +- ng-app/src/locales/de.json | 2 +- ng-app/src/locales/en.json | 50 ++- ng-app/src/routes/Home.svelte | 1 - ng-app/src/routes/NURI.svelte | 52 ++- ng-app/src/routes/NotFound.svelte | 1 + ng-app/src/routes/Shared.svelte | 54 +++ ng-app/src/routes/Site.svelte | 46 +++ ng-app/src/routes/WalletCreate.svelte | 12 +- ng-app/src/routes/WalletLogin.svelte | 15 +- ng-app/src/store.ts | 226 ++++++++++-- ng-app/src/styles.css | 12 + ng-app/src/tab.ts | 361 +++++++++++++------ ng-app/src/zeras.ts | 168 ++++----- ng-app/vite.config.ts | 3 + ng-net/src/actors/client/pin_repo.rs | 4 +- ng-net/src/actors/client/topic_sub.rs | 4 +- ng-net/src/app_protocol.rs | 141 ++++++-- ng-net/src/broker.rs | 42 ++- ng-net/src/connection.rs | 11 +- ng-oxigraph/Cargo.toml | 1 + ng-oxigraph/src/oxigraph/storage/mod.rs | 14 +- ng-oxigraph/src/oxigraph/store.rs | 69 +++- ng-repo/src/branch.rs | 2 +- ng-repo/src/event.rs | 11 +- ng-repo/src/repo.rs | 12 +- ng-repo/src/store.rs | 37 +- ng-repo/src/types.rs | 173 +++++++-- ng-sdk-js/Cargo.toml | 1 - ng-sdk-js/src/lib.rs | 62 +++- ng-verifier/Cargo.toml | 1 + ng-verifier/src/commits/mod.rs | 18 +- ng-verifier/src/commits/transaction.rs | 84 ++++- ng-verifier/src/lib.rs | 38 ++ ng-verifier/src/rocksdb_user_storage.rs | 55 ++- ng-verifier/src/user_storage/branch.rs | 62 +++- ng-verifier/src/user_storage/repo.rs | 6 +- ng-verifier/src/user_storage/storage.rs | 18 +- ng-verifier/src/verifier.rs | 138 +++++++- ng-wallet/src/types.rs | 6 + ngaccount/web/src/routes/Create.svelte | 32 +- ngaccount/web/src/routes/NotFound.svelte | 1 + pnpm-lock.yaml | 135 +++++++ 64 files changed, 2821 insertions(+), 1040 deletions(-) create mode 100644 ng-app/src/apps/ListView.svelte create mode 100644 ng-app/src/apps/SparqlUpdateEditor.svelte create mode 100644 ng-app/src/base64url.js create mode 100644 ng-app/src/lib/Document.svelte create mode 100644 ng-app/src/routes/Shared.svelte create mode 100644 ng-app/src/routes/Site.svelte diff --git a/Cargo.lock b/Cargo.lock index e49435b..493e5bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3327,6 +3327,7 @@ dependencies = [ "ng-repo", "ng-wallet", "serde", + "serde_bare", "serde_bytes", "serde_json", "sys-locale", @@ -3440,6 +3441,7 @@ dependencies = [ "libc", "md-5", "memchr", + "ng-repo", "ng-rocksdb", "oxilangtag", "oxiri", @@ -3523,7 +3525,6 @@ dependencies = [ "serde-wasm-bindgen", "serde_bare", "serde_bytes", - "serde_json", "sys-locale", "wasm-bindgen", "wasm-bindgen-futures", @@ -3577,6 +3578,7 @@ dependencies = [ "serde", "serde_bare", "serde_bytes", + "serde_json", "web-time", "yrs", ] diff --git a/nextgraph/src/lib.rs b/nextgraph/src/lib.rs index 1e31fe3..c0e9511 100644 --- a/nextgraph/src/lib.rs +++ b/nextgraph/src/lib.rs @@ -93,6 +93,7 @@ pub mod verifier { pub mod protocol { pub use ng_net::app_protocol::*; } + pub use ng_verifier::prepare_app_response_for_js; } pub mod wallet { diff --git a/nextgraph/src/local_broker.rs b/nextgraph/src/local_broker.rs index 23498e1..76ea850 100644 --- a/nextgraph/src/local_broker.rs +++ b/nextgraph/src/local_broker.rs @@ -13,9 +13,9 @@ use std::fs::{read, remove_file, write}; use std::path::PathBuf; use async_once_cell::OnceCell; -use async_std::sync::{Arc, Mutex, RwLock}; +use async_std::sync::{Arc, Condvar, Mutex, RwLock}; use futures::channel::mpsc; -use futures::SinkExt; +use futures::{SinkExt, StreamExt}; use lazy_static::lazy_static; use once_cell::sync::Lazy; use pdf_writer::{Content, Finish, Name, Pdf, Rect, Ref, Str}; @@ -37,7 +37,7 @@ use ng_net::app_protocol::*; use ng_net::broker::*; use ng_net::connection::{AppConfig, ClientConfig, IConnect, NoiseFSM, StartConfig}; use ng_net::types::*; -use ng_net::utils::{Receiver, Sender}; +use ng_net::utils::{spawn_and_log_error, Receiver, ResultSend, Sender}; use ng_net::{actor::*, actors::admin::*}; use ng_verifier::types::*; @@ -552,6 +552,7 @@ struct LocalBroker { disconnections_sender: Sender, disconnections_receiver: Option>, + pump_cond: Option, Condvar)>>, } impl fmt::Debug for LocalBroker { @@ -565,6 +566,14 @@ impl fmt::Debug for LocalBroker { } } +#[doc(hidden)] +#[async_trait::async_trait] +pub trait ILocalBroker: Send + Sync + EActor { + async fn deliver(&mut self, event: Event, overlay: OverlayId, user: UserId); + + async fn user_disconnected(&mut self, user_id: UserId); +} + // used to deliver events to the verifier on Clients, or Core that have Verifiers attached. #[async_trait::async_trait] impl ILocalBroker for LocalBroker { @@ -599,7 +608,56 @@ impl EActor for LocalBroker { } } +async fn pump( + mut reader: Receiver, + pair: Arc<(Mutex, Condvar)>, +) -> ResultSend<()> { + while let Some(message) = reader.next().await { + let (lock, cvar) = &*pair; + let mut running = lock.lock().await; + while !*running { + running = cvar.wait(running).await; + } + + let mut broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(Box::new(NgError::LocalBrokerNotInitialized)), + Some(Ok(broker)) => broker.write().await, + }; + match message { + LocalBrokerMessage::Deliver { + event, + overlay, + user, + } => broker.deliver(event, overlay, user).await, + LocalBrokerMessage::Disconnected { user_id } => broker.user_disconnected(user_id).await, + } + } + + log_debug!("END OF PUMP"); + Ok(()) +} + impl LocalBroker { + async fn stop_pump(&self) { + let (lock, cvar) = self.pump_cond.as_deref().as_ref().unwrap(); + let mut running = lock.lock().await; + *running = false; + cvar.notify_one(); + } + + async fn start_pump(&self) { + let (lock, cvar) = self.pump_cond.as_deref().as_ref().unwrap(); + let mut running = lock.lock().await; + *running = true; + cvar.notify_one(); + } + + fn init_pump(&mut self, broker_pump_receiver: Receiver) { + 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 { // match &self.config { // LocalBrokerConfig::InMemory | LocalBrokerConfig::JsStorage(_) => None, @@ -914,12 +972,12 @@ impl LocalBroker { } fn get_wallet_and_session( - &mut self, + &self, user_id: &UserId, - ) -> Result<(&SensitiveWallet, &mut Session), NgError> { + ) -> Result<(&SensitiveWallet, &Session), NgError> { let session_idx = self.user_to_local_session_id_for_mut(user_id)?; let session = self.opened_sessions_list[session_idx] - .as_mut() + .as_ref() .ok_or(NgError::SessionNotFound)?; let wallet = &match &session.config { SessionConfig::WithCredentialsV0(_) | SessionConfig::HeadlessV0(_) => { @@ -934,6 +992,14 @@ impl LocalBroker { Ok((wallet, session)) } + + fn get_session_mut(&mut self, user_id: &UserId) -> Result<&mut Session, NgError> { + let session_idx = self.user_to_local_session_id_for_mut(user_id)?; + self.opened_sessions_list[session_idx] + .as_mut() + .ok_or(NgError::SessionNotFound) + } + async fn disconnect_session(&mut self, user_id: &PubKey) -> Result<(), NgError> { match self.opened_sessions.get(user_id) { Some(session) => { @@ -1022,6 +1088,12 @@ impl LocalBroker { let private_store_id = self .get_site_store_of_session(&session, SiteStoreType::Private)? .to_string(); + let protected_store_id = self + .get_site_store_of_session(&session, SiteStoreType::Protected)? + .to_string(); + let public_store_id = self + .get_site_store_of_session(&session, SiteStoreType::Public)? + .to_string(); let user_id = session.config.user_id(); @@ -1035,6 +1107,8 @@ impl LocalBroker { session_id: idx as u64, user: user_id, private_store_id, + protected_store_id, + public_store_id, }) } @@ -1059,7 +1133,9 @@ impl LocalBroker { Ok(SessionInfo { session_id: first_available, user: user_id, - private_store_id: String::new(), // will be updated when the AppSessionStart replies arrive from broker + private_store_id: String::new(), // will be updated when the AppSessionStart reply arrives from broker + protected_store_id: String::new(), + public_store_id: String::new(), }) } @@ -1360,7 +1436,10 @@ async fn init_(config: LocalBrokerConfig) -> Result>, Ng } }; let (disconnections_sender, disconnections_receiver) = mpsc::unbounded::(); - let local_broker = LocalBroker { + + let (localbroker_pump_sender, broker_pump_receiver) = mpsc::unbounded::(); + + let mut local_broker = LocalBroker { config, wallets, opened_wallets: HashMap::new(), @@ -1373,7 +1452,10 @@ async fn init_(config: LocalBrokerConfig) -> Result>, Ng disconnections_sender, disconnections_receiver: Some(disconnections_receiver), headless_connected_to_remote_broker: false, + pump_cond: None, }; + + local_broker.init_pump(broker_pump_receiver); //log_debug!("{:?}", &local_broker); let broker = Arc::new(RwLock::new(local_broker)); @@ -1381,7 +1463,7 @@ async fn init_(config: LocalBrokerConfig) -> Result>, Ng BROKER .write() .await - .set_local_broker(Arc::clone(&broker) as Arc>); + .set_local_broker(localbroker_pump_sender); Ok(broker) } @@ -2135,11 +2217,13 @@ pub async fn session_start(config: SessionConfig) -> Result panic!("don't call session_start with a SessionConfig different from HeadlessV0 and a LocalBroker configured for Headless") + _ => panic!("don't call session_start with a SessionConfig different from HeadlessV0 when the LocalBroker is configured for Headless") } } // TODO: implement SessionConfig::WithCredentials . VerifierType::Remote => it needs to establish a connection to remote here, then send the AppSessionStart in it. @@ -2164,6 +2248,12 @@ pub async fn session_start(config: SessionConfig) -> Result ClientInfo { #[doc(hidden)] pub async fn user_connect_with_device_info( info: ClientInfo, - user_id: &UserId, + original_user_id: &UserId, location: Option, ) -> Result, f64)>, NgError> { //FIXME: release this write lock much sooner than at the end of the loop of all tries to connect to some servers ? @@ -2253,139 +2343,146 @@ pub async fn user_connect_with_device_info( local_broker.err_if_headless()?; - let (wallet, session) = local_broker.get_wallet_and_session(user_id)?; + let (client, sites, brokers, peer_key) = { + let (wallet, session) = local_broker.get_wallet_and_session(original_user_id)?; + match wallet { + SensitiveWallet::V0(wallet) => ( + wallet.client.clone().unwrap(), + wallet.sites.clone(), + wallet.brokers.clone(), + session.peer_key.clone(), + ), + } + }; let mut result: Vec<(String, String, String, Option, f64)> = Vec::new(); let arc_cnx: Arc> = Arc::new(Box::new(ConnectionWebSocket {})); - match wallet { - SensitiveWallet::V0(wallet) => { - let client = wallet.client.as_ref().unwrap(); - let client_priv = &client.sensitive_client_storage.priv_key; - let client_name = &client.name; - let auto_open = &client.auto_open; - // log_info!( - // "XXXX {} name={:?} auto_open={:?} {:?}", - // client_id.to_string(), - // client_name, - // auto_open, - // wallet - // ); - for user in auto_open { - let user_id = user.to_string(); - let peer_key = &session.peer_key; - let peer_id = peer_key.to_pub(); - log_info!( - "connecting with local peer_id {} for user {}", - peer_id, - user_id - ); - let site = wallet.sites.get(&user_id); - if site.is_none() { - result.push(( - user_id, - "".into(), - "".into(), - Some("Site is missing".into()), - get_unix_time(), - )); - continue; - } - let site = site.unwrap(); - let user_priv = site.get_individual_user_priv_key().unwrap(); - let core = site.cores[0]; //TODO: cycle the other cores if failure to connect (failover) - let server_key = core.0; - let broker = wallet.brokers.get(&core.0.to_string()); - if broker.is_none() { - result.push(( - user_id, - core.0.to_string(), - "".into(), - Some("Broker is missing".into()), - get_unix_time(), - )); - continue; - } - let brokers = broker.unwrap(); - let mut tried: Option<(String, String, String, Option, f64)> = None; - //TODO: on tauri (or forward in local broker, or CLI), prefer a Public to a Domain. Domain always comes first though, so we need to reorder the list - //TODO: use site.bootstraps to order the list of brokerInfo. - for broker_info in brokers { - match broker_info { - BrokerInfoV0::ServerV0(server) => { - let url = server.get_ws_url(&location).await; - log_debug!("URL {:?}", url); - //Option<(String, Vec)> - if url.is_some() { - let url = url.unwrap(); - if url.1.is_empty() { - // TODO deal with Box(Dyn)Public -> tunnel, and on tauri/forward/CLIs, deal with all Box -> direct connections (when url.1.len is > 0) - let res = BROKER - .write() - .await - .connect( - arc_cnx.clone(), - peer_key.clone(), - peer_id, - server_key, - StartConfig::Client(ClientConfig { - url: url.0.clone(), - name: client_name.clone(), - user_priv: user_priv.clone(), - client_priv: client_priv.clone(), - info: info.clone(), - registration: Some(core.1), - }), - ) - .await; - log_debug!("broker.connect : {:?}", res); - - tried = Some(( - user_id.clone(), - core.0.to_string(), - url.0.into(), - match &res { - Ok(_) => None, - Err(e) => Some(e.to_string()), - }, - get_unix_time(), - )); - } - if tried.is_some() && tried.as_ref().unwrap().3.is_none() { - if let Err(e) = - session.verifier.connection_opened(server_key).await - { - log_err!( - "got error while processing opened connection {:?}", - e - ); - Broker::close_all_connections().await; - tried.as_mut().unwrap().3 = Some(e.to_string()); - } - - break; - } else { - log_debug!("Failed connection {:?}", tried); - } + let client_priv = &client.sensitive_client_storage.priv_key; + let client_name = &client.name; + let auto_open = &client.auto_open; + // log_info!( + // "XXXX {} name={:?} auto_open={:?} {:?}", + // client_id.to_string(), + // client_name, + // auto_open, + // wallet + // ); + for user in auto_open { + let user_id = user.to_string(); + let peer_id = peer_key.to_pub(); + log_info!( + "connecting with local peer_id {} for user {}", + peer_id, + user_id + ); + let site = sites.get(&user_id); + if site.is_none() { + result.push(( + user_id, + "".into(), + "".into(), + Some("Site is missing".into()), + get_unix_time(), + )); + continue; + } + let site = site.unwrap(); + let user_priv = site.get_individual_user_priv_key().unwrap(); + let core = site.cores[0]; //TODO: cycle the other cores if failure to connect (failover) + let server_key = core.0; + let broker = brokers.get(&core.0.to_string()); + if broker.is_none() { + result.push(( + user_id, + core.0.to_string(), + "".into(), + Some("Broker is missing".into()), + get_unix_time(), + )); + continue; + } + let brokers = broker.unwrap(); + let mut tried: Option<(String, String, String, Option, f64)> = None; + //TODO: on tauri (or forward in local broker, or CLI), prefer a Public to a Domain. Domain always comes first though, so we need to reorder the list + //TODO: use site.bootstraps to order the list of brokerInfo. + local_broker.stop_pump().await; + for broker_info in brokers { + match broker_info { + BrokerInfoV0::ServerV0(server) => { + let url = server.get_ws_url(&location).await; + log_debug!("URL {:?}", url); + //Option<(String, Vec)> + if url.is_some() { + let url = url.unwrap(); + if url.1.is_empty() { + // TODO deal with Box(Dyn)Public -> tunnel, and on tauri/forward/CLIs, deal with all Box -> direct connections (when url.1.len is > 0) + let res = BROKER + .write() + .await + .connect( + arc_cnx.clone(), + peer_key.clone(), + peer_id, + server_key, + StartConfig::Client(ClientConfig { + url: url.0.clone(), + name: client_name.clone(), + user_priv: user_priv.clone(), + client_priv: client_priv.clone(), + info: info.clone(), + registration: Some(core.1), + }), + ) + .await; + log_debug!("broker.connect : {:?}", res); + + tried = Some(( + user_id.clone(), + core.0.to_string(), + url.0.into(), + match &res { + Ok(_) => None, + Err(e) => Some(e.to_string()), + }, + get_unix_time(), + )); + } + if tried.is_some() && tried.as_ref().unwrap().3.is_none() { + let res = { + let session = local_broker.get_session_mut(original_user_id)?; + session.verifier.connection_opened(server_key).await + }; + if res.is_err() { + let e = res.unwrap_err(); + log_err!("got error while processing opened connection {:?}", e); + Broker::close_all_connections().await; + tried.as_mut().unwrap().3 = Some(e.to_string()); + } else { + local_broker.start_pump().await; } + break; + } else { + log_debug!("Failed connection {:?}", tried); } - // Core information is discarded - _ => {} } } - if tried.is_none() { - tried = Some(( - user_id, - core.0.to_string(), - "".into(), - Some("No broker found".into()), - get_unix_time(), - )); - } - result.push(tried.unwrap()); + // Core information is discarded + _ => {} } } + if tried.is_none() { + tried = Some(( + user_id, + core.0.to_string(), + "".into(), + Some("No broker found".into()), + get_unix_time(), + )); + } + result.push(tried.unwrap()); } + Ok(result) } diff --git a/ng-app/package.json b/ng-app/package.json index 02f38c4..cd5c96d 100644 --- a/ng-app/package.json +++ b/ng-app/package.json @@ -16,17 +16,23 @@ "tauri": "tauri" }, "dependencies": { + "@codemirror/language": "^6.10.2", + "@codemirror/legacy-modes": "^6.4.0", "@popperjs/core": "^2.11.8", "@tauri-apps/api": "2.0.0-alpha.8", "@tauri-apps/plugin-barcode-scanner": "2.0.0-alpha.0", "@tauri-apps/plugin-window": "2.0.0-alpha.1", "async-proxy": "^0.4.1", "classnames": "^2.3.2", + "codemirror": "^6.0.0", "flowbite": "^1.6.5", "flowbite-svelte": "^0.43.3", "html5-qrcode": "^2.3.8", "ng-sdk-js": "workspace:^0.1.0-preview.1", + "sparql": "link:@codemirror/legacy-modes/mode/sparql", + "svelte-codemirror-editor": "^1.4.0", "svelte-i18n": "^4.0.0", + "svelte-inview": "^4.0.2", "svelte-spa-router": "^3.3.0", "vite-plugin-top-level-await": "^1.3.1" }, diff --git a/ng-app/src-tauri/Cargo.toml b/ng-app/src-tauri/Cargo.toml index 89b0715..5f09d44 100644 --- a/ng-app/src-tauri/Cargo.toml +++ b/ng-app/src-tauri/Cargo.toml @@ -25,6 +25,7 @@ tauri-utils = { version = "=2.0.0-alpha.7" } [dependencies] serde = { version = "1.0", features = ["derive"] } +serde_bare = "0.5.0" serde_json = "1.0" serde_bytes = "0.11.7" async-std = { version = "1.12.0", features = ["attributes", "unstable"] } diff --git a/ng-app/src-tauri/src/lib.rs b/ng-app/src-tauri/src/lib.rs index 873d008..892da77 100644 --- a/ng-app/src-tauri/src/lib.rs +++ b/ng-app/src-tauri/src/lib.rs @@ -364,6 +364,7 @@ async fn app_request_stream( main_window: tauri::Window, ) -> ResultSend<()> { while let Some(app_response) = reader.next().await { + let app_response = nextgraph::verifier::prepare_app_response_for_js(app_response)?; main_window.emit(&stream_id, app_response).unwrap(); } @@ -391,10 +392,10 @@ async fn doc_fetch_private_subscribe() -> Result { } #[tauri::command(rename_all = "snake_case")] -async fn doc_fetch_repo_subscribe(repo_id: String) -> Result { +async fn doc_fetch_repo_subscribe(repo_o: String) -> Result { let request = AppRequest::new( AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)), - NuriV0::new_repo_target_from_string(repo_id).map_err(|e| e.to_string())?, + NuriV0::new_from(&repo_o).map_err(|e| e.to_string())?, None, ); Ok(request) diff --git a/ng-app/src/App.svelte b/ng-app/src/App.svelte index 98c15d7..81efa4c 100644 --- a/ng-app/src/App.svelte +++ b/ng-app/src/App.svelte @@ -36,6 +36,8 @@ import UserRegistered from "./routes/UserRegistered.svelte"; import Install from "./routes/Install.svelte"; import ScanQR from "./routes/ScanQR.svelte"; + import Shared from "./routes/Shared.svelte"; + import Site from "./routes/Site.svelte"; import ng from "./api"; import AccountInfo from "./routes/AccountInfo.svelte"; @@ -56,7 +58,9 @@ routes.set("/user/accounts", AccountInfo); routes.set("/wallet/scanqr", ScanQR); if (import.meta.env.NG_APP_WEB) routes.set("/install", Install); - routes.set(/^\/did:ng(.*)/i, NURI); + routes.set("/shared", Shared); + routes.set("/site", Site); + routes.set(/^\/did:ng:(.*)/i, NURI); routes.set("*", NotFound); let unsubscribe = () => {}; @@ -74,7 +78,7 @@ await disconnections_subscribe(); await select_default_lang(); } catch (e) { - console.error(e); + console.warn(e); //console.log("called disconnections_subscribe twice"); } let tauri_platform = import.meta.env.TAURI_PLATFORM; diff --git a/ng-app/src/api.ts b/ng-app/src/api.ts index f6eaecc..97a2354 100644 --- a/ng-app/src/api.ts +++ b/ng-app/src/api.ts @@ -42,7 +42,7 @@ const mapping = { "test": [ ], "get_device_name": [], "doc_fetch_private_subscribe": [], - "doc_fetch_repo_subscribe": ["repo_id"], + "doc_fetch_repo_subscribe": ["repo_o"], } @@ -67,6 +67,22 @@ const handler = { // } else if (path[0] === "wallet_create") { // let res = await Reflect.apply(sdk[path], caller, args); // return res; + } else if (path[0] === "app_request_stream") { + let callback = args[1]; + let new_callback = (event) => { + if (event.V0.State?.graph?.triples) { + let json_str = new TextDecoder().decode(event.V0.State.graph.triples); + event.V0.State.graph.triples = JSON.parse(json_str); + } else if (event.V0.Patch?.graph) { + let inserts_json_str = new TextDecoder().decode(event.V0.Patch.graph.inserts); + event.V0.Patch.graph.inserts = JSON.parse(inserts_json_str); + let removes_json_str = new TextDecoder().decode(event.V0.Patch.graph.removes); + event.V0.Patch.graph.removes = JSON.parse(removes_json_str); + } + callback(event).then(()=> {}) + }; + args[1] = new_callback; + return Reflect.apply(sdk[path], caller, args) } else { return Reflect.apply(sdk[path], caller, args) } @@ -156,10 +172,24 @@ const handler = { if (event.payload.V0.FileBinary) { event.payload.V0.FileBinary = Uint8Array.from(event.payload.V0.FileBinary); } + if (event.payload.V0.State?.graph?.triples) { + let json_str = new TextDecoder().decode(Uint8Array.from(event.payload.V0.State.graph.triples)); + event.payload.V0.State.graph.triples = JSON.parse(json_str); + } else if (event.payload.V0.Patch?.graph) { + let inserts_json_str = new TextDecoder().decode(Uint8Array.from(event.payload.V0.Patch.graph.inserts)); + event.payload.V0.Patch.graph.inserts = JSON.parse(inserts_json_str); + let removes_json_str = new TextDecoder().decode(Uint8Array.from(event.payload.V0.Patch.graph.removes)); + event.payload.V0.Patch.graph.removes = JSON.parse(removes_json_str); + } callback(event.payload).then(()=> {}) }) - await tauri.invoke("app_request_stream",{stream_id, request}); - + try { + await tauri.invoke("app_request_stream",{stream_id, request}); + } catch (e) { + unlisten(); + tauri.invoke("cancel_stream", {stream_id}); + throw e; + } return () => { unlisten(); tauri.invoke("cancel_stream", {stream_id}); diff --git a/ng-app/src/apps/ListView.svelte b/ng-app/src/apps/ListView.svelte new file mode 100644 index 0000000..1e0948c --- /dev/null +++ b/ng-app/src/apps/ListView.svelte @@ -0,0 +1,49 @@ + + + +
+

ListView

+
+ HEADS: {#each commits.heads as head} {head} , {/each} +
+ TRIPLES: + {#each commits.graph as triple} +
{triple}
+ {/each} + + + {#each commits.files as file} +
+ {file.name} + + {#await get_blob(file)} +
+ +
+ {:then url} + {#if url} + {file.name} + {/if} + {/await} +
+ {/each} +
\ No newline at end of file diff --git a/ng-app/src/apps/SparqlUpdateEditor.svelte b/ng-app/src/apps/SparqlUpdateEditor.svelte new file mode 100644 index 0000000..b42eb72 --- /dev/null +++ b/ng-app/src/apps/SparqlUpdateEditor.svelte @@ -0,0 +1,61 @@ + + + +
+ + + + + +
\ No newline at end of file diff --git a/ng-app/src/base64url.js b/ng-app/src/base64url.js new file mode 100644 index 0000000..bf8a2b4 --- /dev/null +++ b/ng-app/src/base64url.js @@ -0,0 +1,59 @@ +/* + * Base64URL-ArrayBuffer + * https://github.com/herrjemand/Base64URL-ArrayBuffer + * + * Copyright (c) 2017 Yuriy Ackermann + * 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; +}; + diff --git a/ng-app/src/classes.ts b/ng-app/src/classes.ts index a070af3..acb3bfc 100644 --- a/ng-app/src/classes.ts +++ b/ng-app/src/classes.ts @@ -7,16 +7,16 @@ // notice may not be copied, modified, or distributed except // according to those terms. -// "post/rich", "post/md", "post/text", "post/html", "post/asciidoc", "page", "code", "code/*", "app", "app/z", "class", "schema", "schema/owl|rdfs|shacl|shex", "service", "service/rust", "service/deno", "contract", "app/n:xxx.xx.xx:", "app/o:", -// "query/sparql", "query/graphql", "query/text", "query/web", -// "data/graph", "data/json", "data/array", "data/map", "data/xml", "data/table", "data/collection", "data/board", "data/grid", "data/geomap", -// "e/email", "e/web", "e/http://[url of class in ontology]", "e/rdf" (read-only cache of RDF fetched from web2.0) -// "mc/text", "mc/link", "mc/card", "mc/pad", -// "doc/diagram","doc/chart", "doc/pdf", "doc/odf", "doc/latex", "doc/ps", "doc/music", "doc/maths", "doc/chemistry", "doc/braille", "doc/ancientscript", -// "media/image", "media/reel", "media/album", "media/video", "media/audio", "media/song", "media/subtitle", "media/overlay", -// "social/channel", "social/stream", "social/contact", "social/event", "social/calendar", "social/scheduler", "social/reaction" -// "prod/task", "prod/project", "prod/issue", "prod/form", "prod/filling", "prod/cad", "prod/slides", "prod/question", "prod/answer", "prod/poll", "prod/vote" -// "file", "file/iana/*", "file/gimp", "file/inkscape", "file/kdenlive", "file/blender", "file/openscad", "file/lyx", "file/scribus", "file/libreoffice", "file/audacity" +// "post:rich", "post:md", "post:text", "post:html", "post:asciidoc", "page", "code", "code:*", "app", "app:z", "class", "schema", "schema:owl|rdfs|shacl|shex", "service", "service:rust", "service:deno", "contract", "app:n:xxx.xx.xx:", "app:o:", +// "query:sparql", "query:graphql", "query:text", "query:web", +// "data:graph", "data:json", "data:array", "data:map", "data:xml", "data:table", "data:collection", "data:board", "data:grid", "data:geomap", +// "e:email", "e:web", "e:http://[url of class in ontology]", "e:rdf" (read-only cache of RDF fetched from web2.0) +// "mc:text", "mc:link", "mc:card", "mc:pad", +// "doc:diagram","doc:chart", "doc:pdf", "doc:odf", "doc:latex", "doc:ps", "doc:music", "doc:maths", "doc:chemistry", "doc:braille", "doc:ancientscript", +// "media:image", "media:reel", "media:album", "media:video", "media:audio", "media:song", "media:subtitle", "media:overlay", +// "social:channel", "social:stream", "social:contact", "social:event", "social:calendar", "social:scheduler", "social:reaction", "social:chatroom", +// "prod:task", "prod:project", "prod:issue", "prod:form", "prod:filling", "prod:cad", "prod:slides", "prod:question", "prod:answer", "prod:poll", "prod:vote" +// "file", "file:iana:*", "file:gimp", "file:inkscape", "file:kdenlive", "file:blender", "file:openscad", "file:lyx", "file:scribus", "file:libreoffice", "file:audacity", "file:godot" // application/vnd.api+json @@ -27,12 +27,12 @@ // animation: snap, lottie, smil editor: https://github.com/HaikuTeam/animator/ export const has_toc = (class_name) => { - return class_name === "post/rich" || class_name === "post/md" || class_name === "post/html" || class_name === "post/asciidoc" || class_name === "app/z" || class_name === "class" - || class_name.startsWith("schema") || class_name === "doc/pdf" || class_name === "doc/odf" || class_name === "doc/latex" || class_name === "doc/ps" || class_name === "prod/project" || class_name === "prod/slides" + return class_name === "post:rich" || class_name === "post:md" || class_name === "post:html" || class_name === "post:asciidoc" || class_name === "app:z" || class_name === "class" + || class_name.startsWith("schema") || class_name === "doc:pdf" || class_name === "doc:odf" || class_name === "doc:latex" || class_name === "doc:ps" || class_name === "prod:project" || class_name === "prod:slides" }; export const official_classes = { - "post/rich": { + "post:rich": { "ng:crdt": "YXml", "ng:n": "Post - Rich Text", // editor: y-ProseMirror, viewer: https://www.npmjs.com/package/prosemirror-to-html-js or https://prosemirror.net/docs/ref/version/0.4.0.html#toDOM https://prosemirror.net/docs/ref/version/0.4.0.html#toHTML "ng:a": "A Post with Rich Text, including images, links, formatted text, and embeds of other content", @@ -43,7 +43,7 @@ export const official_classes = { }, "ng:compat": ["as:Article"], }, - "post/md": { + "post:md": { "ng:crdt": "YXml", "ng:n": "Post - MarkDown", // editor y-MilkDown, viewer: https://github.com/wooorm/markdown-rs "ng:a": "A Post with MarkDown, including images, links, formatted text, and embeds of other content", @@ -52,9 +52,9 @@ export const official_classes = { "ng:x": { "as":true, }, - "ng:compat": ["file/iana/text/markdown", "code/markdown","as:Article"], + "ng:compat": ["file:iana:text:markdown", "code:markdown","as:Article"], }, - "post/text": { + "post:text": { "ng:crdt": "YText", "ng:n": "Post - Plain Text", "ng:a": "A Post with Plain Text", @@ -63,9 +63,9 @@ export const official_classes = { "ng:x": { "as":true, }, - "ng:compat": ["file/iana/text/plain", "code/plaintext","as:Article"], + "ng:compat": ["file:iana:text:plain", "code:plaintext","as:Article"], }, - "post/html": { + "post:html": { "ng:crdt": "YXml", "ng:n": "Post - TinyMCE", "ng:x": { @@ -74,7 +74,7 @@ export const official_classes = { "ng:a": "A Post based on TinyMCE, including images, links, formatted text, and embeds of other content", "ng:compat": ["as:Article"], }, - "post/asciidoc": { // display with https://github.com/asciidoctor/asciidoctor.js/ + "post:asciidoc": { // display with https://github.com/asciidoctor/asciidoctor.js/ "ng:crdt": "YText", "ng:n": "Post - AsciiDoc", "ng:a": "A Post based on AsciiDoc format", @@ -95,21 +95,21 @@ export const official_classes = { "ng:a": "A Source Code file. many languages supported", "ng:o": "n:g:z:pre", "ng:w": "n:g:z:code_editor", - "ng:compat": ["code/*","file/iana/text/javascript","file/iana/text/css","file/iana/text/html","file/iana/text/markdown", "file/iana/application/xml", - "file/iana/application/yaml", "file/iana/text/xml", "file/iana/application/xhtml+xml"], + "ng:compat": ["code:*","file:iana:text:javascript","file:iana:text:css","file:iana:text:html","file:iana:text:markdown", "file:iana:application:xml", + "file:iana:application:yaml", "file:iana:text:xml", "file:iana:application:xhtml+xml"], }, "app": { "ng:n": "Official App", "ng:a": "App provided by NextGraph platform", }, - "app/z": { + "app:z": { "ng:crdt": "Elmer", "ng:n": "Application", // Editor: Monaco "ng:a": "Create an Application based on NextGraph Framework", "ng:o": "n:g:z:app_store", "ng:w": "n:g:z:app_editor", - "ng:include": ["schema/*","service/*","code","file"], - "ng:compat": ["code/svelte"], + "ng:include": ["schema:*","service:*","code","file"], + "ng:compat": ["code:svelte"], }, "class": { "ng:crdt": "Graph", @@ -120,7 +120,7 @@ export const official_classes = { }, "ng:compat": ["rdfs:Class"], }, - "schema/rdfs": { + "schema:rdfs": { "ng:crdt": "Graph", "ng:n": "Schema - RDFS", "ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with RDFS", @@ -129,10 +129,10 @@ export const official_classes = { "ng:x": { "rdfs":true, }, - "ng:include": ["data/graph"], + "ng:include": ["data:graph"], "ng:compat": ["rdfs:*","class"], }, - "schema/owl": { // display with https://github.com/VisualDataWeb/WebVOWL + "schema:owl": { // display with https://github.com/VisualDataWeb/WebVOWL "ng:crdt": "Graph", "ng:n": "Schema - OWL", "ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with OWL", @@ -141,10 +141,10 @@ export const official_classes = { "ng:x": { "owl":true, }, - "ng:include": ["data/graph"], + "ng:include": ["data:graph"], "ng:compat": ["owl:Ontology"], }, - "schema/shacl": { + "schema:shacl": { "ng:crdt": "Graph", "ng:n": "Schema - SHACL", "ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with SHACL", @@ -153,10 +153,10 @@ export const official_classes = { "ng:x": { "sh":true, }, - "ng:include": ["data/graph"], - "ng:compat": ["sh:Shape", "file/iana/text/shaclc" ], + "ng:include": ["data:graph"], + "ng:compat": ["sh:Shape", "file:iana:text:shaclc" ], }, - "schema/shex": { + "schema:shex": { "ng:crdt": "Graph", "ng:n": "Schema - SHEX", "ng:a": "Define the Schema, Ontology or Vocabulary for your data and the relations between them, with SHEX", @@ -165,73 +165,73 @@ export const official_classes = { "ng:x": { "shex":true, }, - "ng:include": ["data/graph"], - "ng:compat": ["shex:*", "file/iana/text/shex", "code/shexc" ], + "ng:include": ["data:graph"], + "ng:compat": ["shex:*", "file:iana:text:shex", "code:shexc" ], }, "service": { "ng:n": "Internal Service", "ng:a": "Service provided by NextGraph framework", "ng:o": "n:g:z:service_invoke", // default viewer }, - "service/rust": { + "service:rust": { "ng:crdt": "YText", "ng:n": "Service - Rust", // edited with CodeMirror, displayed with highlight.js "ng:a": "Service written in Rust and compiled to WASM", "ng:o": "external_service_invoke", // default viewer "ng:w": "n:g:z:service_editor", // default editor - "ng:compat": ["code/rust", "file/iana/application/wasm"], + "ng:compat": ["code:rust", "file:iana:application:wasm"], }, - "service/deno": { + "service:deno": { "ng:crdt": "YText", "ng:n": "Service - Deno/JS", // edited with CodeMirror, displayed with highlight.js "ng:a": "Service written in JS/TS for Deno or NodeJS", "ng:o": "external_service_invoke", // default viewer "ng:w": "n:g:z:service_editor", // default editor - "ng:compat": ["code/javascript", "code/typescript", "file/iana/text/javascript", "file/iana/application/node"], + "ng:compat": ["code:javascript", "code:typescript", "file:iana:text:javascript", "file:iana:application:node"], }, "contract": { "ng:crdt": "YText", "ng:n": "Contract", // edited with CodeMirror, displayed with highlight.js "ng:a": "Smart Contract with Rust or JS code", - "ng:compat": ["code/rust", "file/iana/application/wasm", "code/javascript", "code/typescript", "file/iana/text/javascript", "file/iana/application/node"], + "ng:compat": ["code:rust", "file:iana:application:wasm", "code:javascript", "code:typescript", "file:iana:text:javascript", "file:iana:application:node"], }, - "query/sparql": { + "query:sparql": { "ng:crdt": "YText",// uses ng:default_graph and ng:named_graph predicates "ng:n": "SPARQL Query", // edited with YASGUI or Sparnatural, displayed with highlight.js https://github.com/highlightjs/highlightjs-turtle/tree/master "ng:a": "Saved SPARQL Query that can be invoked", "ng:o": "n:g:z:sparql:invoke", "ng:w": "n:g:z:sparql_query:yasgui", - "ng:compat": ["code/sparql", "file/iana/application/sparql-query"], + "ng:compat": ["code:sparql", "file:iana:application:sparql-query","file:iana:application:x-sparql-query"], }, - "query/sparql_update": { + "query:sparql_update": { "ng:crdt": "YText",// uses ng:default_graph and ng:named_graph predicates "ng:n": "SPARQL Update", // edited with YASGUI, displayed with highlight.js https://github.com/highlightjs/highlightjs-turtle/tree/master "ng:a": "Saved SPARQL Update that can be invoked", "ng:o": "n:g:z:sparql:invoke", "ng:w": "n:g:z:sparql_update:yasgui", - "ng:compat": ["code/sparql", "file/iana/application/sparql-update"], + "ng:compat": ["code:sparql", "file:iana:application:sparql-update"], }, - "query/graphql": { + "query:graphql": { "ng:crdt": "YText", // uses ng:default_graph predicate "ng:n": "GraphQL Query", // edited with https://github.com/graphql/graphiql or https://github.com/graphql-editor/graphql-editor, displayed with highlight.js "ng:a": "Saved GraphQL Query that can be invoked", "ng:o": "n:g:z:graphql:invoke", "ng:w": "n:g:z:graphql_query", - "ng:compat": ["code/graphql", "file/iana/application/graphql+json"], + "ng:compat": ["code:graphql", "file:iana:application:graphql+json"], }, - "query/text": { + "query:text": { "ng:crdt": "Graph", "ng:n": "Text Search", "ng:a": "Saved Text Search and its results", "ng:compat": [], }, - "query/web": { + "query:web": { "ng:crdt": "Graph", "ng:n": "Web Search", "ng:a": "Saved Web Search and its results", "ng:compat": [], }, - "data/graph": { + "data:graph": { "ng:crdt": "Graph", // https://github.com/highlightjs/highlightjs-turtle/tree/master "ng:n": "Graph", "ng:a": "Define the Graph of your data with Semantic Web / Linked Data", @@ -241,47 +241,47 @@ export const official_classes = { "rdf":true, "xsd":true, }, - "ng:compat": [ "rdf:*", "xsd:*", "file/iana/text/n3", "file/iana/text/rdf+n3", "file/iana/text/turtle", "file/iana/application/n-quads", "file/iana/application/trig", "file/iana/application/n-triples", - "file/iana/application/rdf+xml", "file/iana/application/ld+json"], + "ng:compat": [ "rdf:*", "xsd:*", "file:iana:text:n3", "file:iana:text:rdf+n3", "file:iana:text:turtle", "file:iana:application:n-quads", "file:iana:application:trig", "file:iana:application:n-triples", + "file:iana:application:rdf+xml", "file:iana:application:ld+json"], }, - "data/json": { + "data:json": { "ng:crdt": "Automerge", "ng:n": "JSON", "ng:a": "JSON Data CRDT", "ng:o": "n:g:z:json_viewer", // default viewer "ng:w": "n:g:z:json_editor", // default editor - "ng:compat": ["file/iana/application/json", "code:json"], + "ng:compat": ["file:iana:application:json", "code:json"], }, - "data/array": { + "data:array": { "ng:crdt": "YArray", "ng:n": "JSON Array", "ng:a": "JSON Array CRDT", "ng:o": "n:g:z:json_viewer", // default viewer "ng:w": "n:g:z:json_editor", // default editor - "ng:compat": ["file/iana/application/json", "code:json"], + "ng:compat": ["file:iana:application:json", "code:json"], }, - "data/map": { + "data:map": { "ng:crdt": "YMap", "ng:n": "JSON Map", "ng:a": "JSON Map CRDT", "ng:o": "n:g:z:json_viewer", // default viewer "ng:w": "n:g:z:json_editor", // default editor - "ng:compat": ["file/iana/application/json", "code:json"], + "ng:compat": ["file:iana:application:json", "code:json"], }, - "data/xml": { + "data:xml": { "ng:crdt": "YXml", "ng:n": "XML", "ng:a": "XML Data CRDT", - "ng:compat": ["file/iana/text/xml","file/iana/application/xml", "code:xml"], + "ng:compat": ["file:iana:text:xml","file:iana:application:xml", "code:xml"], }, - "data/table": { + "data:table": { "ng:crdt": "Automerge", // see https://github.com/frappe/datatable "ng:n": "Table", // see https://specs.frictionlessdata.io/table-schema displayed with pivot table see https://activetable.io/docs/data https://www.npmjs.com/package/pivottable https://socket.dev/npm/package/svelte-pivottable/alerts/0.2.0?tab=dependencies "ng:a": "Data in a Table (columns and rows)", "ng:o": "n:g:z:pivot", - "ng:compat": ["file/iana/application/sparql-results+json","file/iana/application/sparql-results+xml","file/iana/text/csv"], + "ng:compat": ["file:iana:application:sparql-results+json","file:iana:application:sparql-results+xml","file:iana:text:csv"], }, - "data/collection": { + "data:collection": { "ng:crdt": "Graph", "ng:n": "Collection", "ng:a": "An ordered list of items", @@ -292,10 +292,10 @@ export const official_classes = { }, "ng:compat": ["as:Collection","rdf:List","rdf:Seq"], }, - "data/container": { + "data:container": { "ng:crdt": "Graph", "ng:n": "Container", - "ng:a": "An unordered list of items", + "ng:a": "An unordered set of items", "ng:o": "n:g:z:list", "ng:x": { "rdf": true, @@ -304,14 +304,14 @@ export const official_classes = { }, "ng:compat": ["rdfs:member","ldp:contains","rdf:Bag","rdf:Alt"], }, - "data/plato": { + "data:plato": { "ng:crdt": "Graph", "ng:n": "Plato", "ng:a": "A tree of files and folders", "ng:o": "n:g:z:tree", "ng:compat": ["ng:plato","ng:has_plato"], }, - "data/board": { + "data:board": { "ng:crdt": "Graph", "ng:n": "Board", "ng:a": "Whiteboard, infinite canvas to arrange your content in 2D", @@ -319,15 +319,15 @@ export const official_classes = { "ng:include": [], "ng:compat": [], //https://jsoncanvas.org/ https://www.canvasprotocol.org/ https://github.com/orgs/ocwg/discussions/25 https://infinitecanvas.tools/gallery/ }, - "data/grid": { + "data:grid": { "ng:crdt": "Graph", "ng:n": "Grid", "ng:a": "Grid representation of a collection or container", "ng:o": "n:g:z:grid", - "ng:include": ["data/container","data/collection","data/table","media/album"], + "ng:include": ["data:container","data:collection","data:table","media:album"], "ng:compat": [], }, - "data/geomap": { // https://github.com/leaflet/leaflet + "data:geomap": { // https://github.com/leaflet/leaflet "ng:crdt": "Graph", "ng:n": "Geo Map", "ng:a": "Geographical Map", @@ -336,36 +336,36 @@ export const official_classes = { "gn": true, "as": true, }, - "ng:compat": ["as:Place","wgs:*","gn:*", "file/iana/application/geo+json", "file/iana/application/vnd.mapbox-vector-tile"], // see also https://github.com/topojson/topojson + "ng:compat": ["as:Place","wgs:*","gn:*", "file:iana:application:geo+json", "file:iana:application:vnd.mapbox-vector-tile"], // see also https://github.com/topojson/topojson }, - "e/email": { + "e:email": { "ng:crdt": "Graph", "ng:n": "Email", "ng:a": "Email content and headers", "ng:x": { "email": "http://www.invincea.com/ontologies/icas/1.0/email#" //https://raw.githubusercontent.com/twosixlabs/icas-ontology/master/ontology/email.ttl }, - "ng:compat": ["file/iana/message/rfc822","file/iana/multipart/related"], + "ng:compat": ["file:iana:message:rfc822","file:iana:multipart:related"], }, - "e/web": { + "e:web": { "ng:crdt": "Graph", //https://www.npmjs.com/package/warcio https://github.com/N0taN3rd/node-warc "ng:n": "Web Archive", "ng:a": "Archive the content of a web page", - "ng:compat": ["file/iana/application/warc","file/iana/multipart/related"], + "ng:compat": ["file:iana:application:warc","file:iana:multipart:related"], }, - "e/rdf": { + "e:rdf": { "ng:crdt": "Graph", "ng:n": "RDF Archive", "ng:a": "Archive the triples of an RDF resource dereferenced with HTTP", - "ng:include": ["data/graph"], + "ng:include": ["data:graph"], }, - "mc/text": { + "mc:text": { "ng:crdt": "Graph", "ng:n": "Text Selection", "ng:a": "Text Selection copied into Magic Carpet", }, - "mc/link": { + "mc:link": { "ng:crdt": "Graph", "ng:n": "Link", "ng:a": "Link to a document kept in Magic Carpet", @@ -382,68 +382,68 @@ export const official_classes = { "ng:a": "Pad representation of a document", "ng:o": "n:g:z:pad", }, - "doc/compose" : { + "doc:compose" : { "ng:crdt": "YArray", "ng:n": "Composition", "ng:a": "Compose several blocks into a single document", "ng:o": "n:g:z:compose", }, - "doc/diagram/mermaid" : { + "doc:diagram:mermaid" : { "ng:crdt": "YText", "ng:n": "Diagram - Mermaid", "ng:a": "Describe Diagrams with Mermaid", - "ng:compat": ["file/iana/application/vnd.mermaid"] + "ng:compat": ["file:iana:application:vnd.mermaid"] }, - "doc/diagram/drawio" : { + "doc:diagram:drawio" : { "ng:crdt": "YXml", "ng:n": "Diagram - DrawIo", "ng:a": "Draw Diagrams with DrawIo", - "ng:compat": ["file/iana/application/vnd.jgraph.mxfile","file/iana/application/x-drawio"] + "ng:compat": ["file:iana:application:vnd.jgraph.mxfile","file:iana:application:x-drawio"] }, - "doc/diagram/graphviz" : { + "doc:diagram:graphviz" : { "ng:crdt": "YText", "ng:n": "Diagram - Graphviz", "ng:a": "Describe Diagrams with Graphviz", - "ng:compat": ["file/iana/text/vnd.graphviz"] + "ng:compat": ["file:iana:text:vnd.graphviz"] }, - "doc/diagram/excalidraw" : { + "doc:diagram:excalidraw" : { "ng:crdt": "Automerge", "ng:n": "Diagram - Excalidraw", "ng:a": "Collaborate on Diagrams with Excalidraw", - "ng:compat": ["file/iana/application/vnd.excalidraw+json"] + "ng:compat": ["file:iana:application:vnd.excalidraw+json"] }, - "doc/diagram/gantt" : { //https://github.com/frappe/gantt + "doc:diagram:gantt" : { //https://github.com/frappe/gantt "ng:crdt": "Automerge", "ng:n": "Diagram - Gantt", "ng:a": "Interactive gantt chart", "ng:compat": [] }, - "doc/diagram/flowchart" : { //https://github.com/adrai/flowchart.js + "doc:diagram:flowchart" : { //https://github.com/adrai/flowchart.js "ng:crdt": "YText", "ng:n": "Diagram - Flowchart", "ng:a": "flow chart diagrams", "ng:compat": [] }, - "doc/diagram/sequence" : { //https://github.com/bramp/js-sequence-diagrams + "doc:diagram:sequence" : { //https://github.com/bramp/js-sequence-diagrams "ng:crdt": "YText", "ng:n": "Diagram - Sequence", "ng:a": "sequence diagrams", "ng:compat": [] }, // checkout https://www.mindmaps.app/ but it is AGPL - "doc/diagram/markmap" : { //https://github.com/markmap/markmap + "doc:diagram:markmap" : { //https://github.com/markmap/markmap "ng:crdt": "YText", "ng:n": "Diagram - Markmap", "ng:a": "mindmaps with markmap", "ng:compat": [] }, - "doc/diagram/mymind" : { //https://github.com/markmap/markmap + "doc:diagram:mymind" : { //https://github.com/markmap/markmap "ng:crdt": "YText", // see MyMind format, MindMup JSON, FreeMind XML and MindMap Architect XML "ng:n": "Diagram - Mymind", "ng:a": "mindmaps with mymind", "ng:compat": [] // https://github.com/ondras/my-mind/wiki/Saving-and-loading#file-formats }, - "doc/diagram/jsmind" : { //https://github.com/hizzgdev/jsmind + "doc:diagram:jsmind" : { //https://github.com/hizzgdev/jsmind "ng:crdt": "Automerge", "ng:n": "Diagram - jsmind", "ng:a": "mindmaps with jsmind", @@ -457,137 +457,137 @@ export const official_classes = { // https://github.com/Rich-Harris/pancake // https://github.com/williamngan/pts // https://visjs.org/ - "doc/viz/cytoscape" : { + "doc:viz:cytoscape" : { "ng:crdt": "Automerge", "ng:n": "Viz - Cytoscape", "ng:a": "Graph theory (network) visualization", "ng:compat": [] // https://github.com/cytoscape/cytoscape.js }, - "doc/viz/vega" : { + "doc:viz:vega" : { "ng:crdt": "Automerge", "ng:n": "Viz - Vega", "ng:a": "Grammar for interactive graphics", "ng:compat": [] // https://vega.github.io/vega-lite/docs/ https://github.com/vega/editor }, - "doc/viz/vizzu" : { + "doc:viz:vizzu" : { "ng:crdt": "Automerge", "ng:n": "Viz - Vizzu", "ng:a": "Animated data visualizations and data stories", "ng:compat": [] // https://github.com/vizzuhq/vizzu-lib }, - "doc/viz/plotly" : { //https://github.com/plotly/plotly.js + "doc:viz:plotly" : { //https://github.com/plotly/plotly.js "ng:crdt": "Automerge", "ng:n": "Viz - Plotly", "ng:a": "Declarative charts", "ng:compat": [] // https://github.com/cytoscape/cytoscape.js }, - "doc/viz/avail" : { + "doc:viz:avail" : { "ng:crdt": "Automerge", "ng:n": "Viz - Avail", "ng:a": "Time Data Availability Visualization", "ng:compat": [] // https://github.com/flrs/visavail }, - "doc/chart/frappecharts" : { + "doc:chart:frappecharts" : { "ng:crdt": "Automerge", "ng:n": "Charts - Frappe", "ng:a": "GitHub-inspired responsive charts", "ng:compat": [] // https://github.com/frappe/charts }, - "doc/chart/financial" : { + "doc:chart:financial" : { "ng:crdt": "Automerge", "ng:n": "Charts - Financial", "ng:a": "Financial charts", "ng:compat": [] //https://github.com/tradingview/lightweight-charts }, // have a look at https://github.com/cube-js/cube and https://awesome.cube.dev/ and https://frappe.io/products - "doc/chart/apexcharts" : { + "doc:chart:apexcharts" : { "ng:crdt": "Automerge", "ng:n": "Charts - ApexCharts", "ng:a": "Interactive data visualizations", "ng:compat": [] // https://github.com/apexcharts/apexcharts.js }, //realtime data with https://github.com/square/cubism - "doc/chart/billboard" : { + "doc:chart:billboard" : { "ng:crdt": "Automerge", "ng:n": "Charts - BillBoard", "ng:a": "Interactive data visualizations based on D3", "ng:compat": [] // https://github.com/naver/billboard.js }, - "doc/chart/echarts" : { + "doc:chart:echarts" : { "ng:crdt": "Automerge", "ng:n": "Charts - ECharts", "ng:a": "Interactive charting and data visualization with Apache ECharts", "ng:compat": [] // https://github.com/apache/echarts }, - "doc/chart/chartjs" : { + "doc:chart:chartjs" : { "ng:crdt": "Automerge", "ng:n": "Charts - Chart.js", "ng:a": "Simple yet flexible charting for designers & developers with Chart.js", "ng:compat": [] // https://github.com/chartjs/Chart.js }, // see if to provide plain D3, and also all the https://github.com/antvis libraries: G2, G6, L7, S2, X6. Have a look at AVA - "doc/pdf": { + "doc:pdf": { "ng:crdt": "Graph", "ng:n": "PDF", "ng:a": "upload and display a PDF file", - "ng:compat": ["file/iana/application/pdf"] // https://github.com/mozilla/pdf.js https://viewerjs.org/ + "ng:compat": ["file:iana:application:pdf"] // https://github.com/mozilla/pdf.js https://viewerjs.org/ }, - "doc/odf": { //!!! becareful: AGPL + "doc:odf": { //!!! becareful: AGPL "ng:crdt": "Graph", "ng:n": "OpenDocumentFormat (ODF)", "ng:a": "upload and display an ODF file", - "ng:compat": ["file/iana/application/vnd.oasis.opendocument*"] // https://webodf.org/ https://github.com/webodf/WebODF https://viewerjs.org/ + "ng:compat": ["file:iana:application:vnd.oasis.opendocument*"] // https://webodf.org/ https://github.com/webodf/WebODF https://viewerjs.org/ }, // see also https://github.com/Mathpix/mathpix-markdown-it - "doc/latex": { + "doc:latex": { "ng:crdt": "Graph", "ng:n": "Latex", "ng:a": "upload and display a Latex or Tex file", - "ng:compat": ["file/iana/application/x-tex","file/iana/text/x-tex"] // https://github.com/michael-brade/LaTeX.js https://github.com/mathjax/MathJax + "ng:compat": ["file:iana:application:x-tex","file:iana:text:x-tex"] // https://github.com/michael-brade/LaTeX.js https://github.com/mathjax/MathJax }, - "doc/ps": { //!!! becareful: AGPL https://github.com/ochachacha/ps-wasm + "doc:ps": { //!!! becareful: AGPL https://github.com/ochachacha/ps-wasm "ng:crdt": "Graph", "ng:n": "Postscript", "ng:a": "upload and display a PostScript file", - "ng:compat": ["file/iana/application/postscript"] // https://www.npmjs.com/package/ghostscript4js + "ng:compat": ["file:iana:application:postscript"] // https://www.npmjs.com/package/ghostscript4js }, - "doc/music/abc": { //https://github.com/paulrosen/abcjs + "doc:music:abc": { //https://github.com/paulrosen/abcjs "ng:crdt": "YText", "ng:n": "Music ABC", "ng:a": "sheet music notation", "ng:compat": [] }, - "doc/music/guitar": { //https://github.com/birdca/fretboard + "doc:music:guitar": { //https://github.com/birdca/fretboard "ng:crdt": "YText", "ng:n": "Music - Guitar", "ng:a": "charts for guitar chords and scales", "ng:compat": [] }, - "doc/maths": { //https://github.com/KaTeX/KaTeX + "doc:maths": { //https://github.com/KaTeX/KaTeX "ng:crdt": "YText", "ng:n": "Maths", "ng:a": "TeX math rendering", - "ng:compat": ["file/iana/application/x-tex","file/iana/text/x-tex"] + "ng:compat": ["file:iana:application:x-tex","file:iana:text:x-tex"] }, - "doc/chemistry": { //GPL!! https://github.com/aeris-data/ChemDoodle/tree/master/ChemDoodleWeb-8.0.0 or https://github.com/aseevia/smiles-3d-vue + "doc:chemistry": { //GPL!! https://github.com/aeris-data/ChemDoodle/tree/master/ChemDoodleWeb-8.0.0 or https://github.com/aseevia/smiles-3d-vue "ng:crdt": "YText", "ng:n": "Chemical", "ng:a": "simplified molecular-input line-entry system (SMILES)", - "ng:compat": ["file/iana/chemical/x-daylight-smiles"] // https://en.wikipedia.org/wiki/SYBYL_line_notation and http://fileformats.archiveteam.org/wiki/Chemical_data + "ng:compat": ["file:iana:chemical:x-daylight-smiles"] // https://en.wikipedia.org/wiki/SYBYL_line_notation and http://fileformats.archiveteam.org/wiki/Chemical_data }, - "doc/ancientscript": { //https://dn-works.com/ufas/ + "doc:ancientscript": { //https://dn-works.com/ufas/ "ng:crdt": "YText", // use Unicode and special fonts "ng:n": "Ancient Script", "ng:a": "Ancient Script", "ng:compat": [] }, - "doc/braille": { //https://en.wikipedia.org/wiki/Braille_Patterns + "doc:braille": { //https://en.wikipedia.org/wiki/Braille_Patterns "ng:crdt": "YText", // use Unicode and special fonts "ng:n": "Braille Patterns", "ng:a": "Braille Patterns", "ng:compat": [] }, - "media/image": { + "media:image": { "ng:crdt": "Graph", "ng:n": "Image", "ng:a": "upload and display an image", @@ -595,16 +595,16 @@ export const official_classes = { "ng:x": { "as":true, }, - "ng:compat": ["file/iana/image*","as:Image"] + "ng:compat": ["file:iana:image*","as:Image"] }, - "media/reel": { + "media:reel": { "ng:crdt": "Graph", "ng:n": "Reel", "ng:a": "upload and display a Reel (video from mobile)", "ng:o": "n:g:z:media", - "ng:compat": ["file/iana/video*"] + "ng:compat": ["file:iana:video*"] }, - "media/video": { + "media:video": { "ng:crdt": "Graph", "ng:n": "Video", "ng:a": "upload and display a Video (and film)", @@ -612,17 +612,17 @@ export const official_classes = { "ng:x": { "as":true, }, - "ng:compat": ["file/iana/video*","as:Video"] + "ng:compat": ["file:iana:video*","as:Video"] }, - "media/album": { + "media:album": { "ng:crdt": "Graph", "ng:n": "Album", "ng:a": "Assemble several images and/or videos into an ordered Album", "ng:o": "n:g:z:gallery", - "ng:include": ["data/collection"], + "ng:include": ["data:collection"], "ng:compat": [] }, - "media/audio": { + "media:audio": { "ng:crdt": "Graph", "ng:n": "Audio", "ng:a": "upload and play an Audio file, Audio note or Voice message", @@ -630,9 +630,9 @@ export const official_classes = { "ng:x": { "as":true, }, - "ng:compat": ["file/iana/audio*","as:Audio"] + "ng:compat": ["file:iana:audio*","as:Audio"] }, - "media/song": { + "media:song": { "ng:crdt": "Graph", "ng:n": "Song", "ng:a": "A song from an artist,album and/or lyrics", @@ -644,20 +644,20 @@ export const official_classes = { // see also https://polifonia-project.eu/wp-content/uploads/2022/01/Polifonia_D2.1_V1.0.pdf // Music ontology http://musicontology.com/docs/faq.html with data based on existing databases https://musicbrainz.org/doc/MusicBrainz_Database/Schema https://github.com/megaconfidence/open-song-database https://www.discogs.com/developers }, - "media/subtitle": { //https://captioneasy.com/subtitle-file-formats/ + "media:subtitle": { //https://captioneasy.com/subtitle-file-formats/ "ng:crdt": "YText", "ng:n": "Subtitles", "ng:a": "Subtitles", "ng:compat": [] // TBD }, - "media/overlay": { + "media:overlay": { "ng:crdt": "Graph", "ng:n": "Overlay", "ng:a": "Composition of an image, reel, text, icon, link, mention or other content into a layered content", "ng:o": "n:g:z:media", "ng:compat": [] }, - "social/activity": { + "social:activity": { "ng:crdt": "Graph", "ng:n": "Activity", "ng:a": "Activity sent in a Stream", @@ -666,19 +666,19 @@ export const official_classes = { }, "ng:compat": ["as:Activity"] }, - "social/channel": { + "social:channel": { "ng:crdt": "Graph", "ng:n": "Channel", "ng:a": "Broadcast channel with subscribers", "ng:compat": [] }, - "social/stream": { + "social:stream": { "ng:crdt": "Graph", "ng:n": "Stream", "ng:a": "A document or store's stream branch", "ng:compat": [] }, - "social/contact": { + "social:contact": { "ng:crdt": "Graph", "ng:n": "Contact", "ng:a": "Contact: an Individual, Organization or Group", @@ -686,20 +686,20 @@ export const official_classes = { "vcard":true, "foaf": true, }, - "ng:include": ["data/graph"], - "ng:compat": ["foaf:Person","foaf:Agent","vcard:Individual", "vcard:Organization", "vcard:Group", "file/iana/text/vcard", "file/iana/application/vcard+json", "file/iana/application/vcard+xml" ], + "ng:include": ["data:graph"], + "ng:compat": ["foaf:Person","foaf:Agent","vcard:Individual", "vcard:Organization", "vcard:Group", "file:iana:text:vcard", "file:iana:application:vcard+json", "file:iana:application:vcard+xml" ], }, - "social/event": { + "social:event": { "ng:crdt": "Graph", "ng:n": "Event", "ng:a": "An event occuring in specific location and time", "ng:x": { "as":true, }, - "ng:include": ["post/*"], + "ng:include": ["post:*"], "ng:compat": ["as:Event"] }, - "social/calendar": { + "social:calendar": { "ng:crdt": "Graph", "ng:n": "Calendar", "ng:a": "A calendar where events are gathered", @@ -707,10 +707,10 @@ export const official_classes = { "as":true, "time": true, }, - "ng:include": ["data/collection"], - "ng:compat": ["time:TemporalEntity", "file/iana/text/calendar", "file/iana/application/calendar+xml", "file/iana/application/calendar+json"] //https://www.rfc-editor.org/rfc/rfc5545 - }, - "social/scheduler": { + "ng:include": ["data:collection"], + "ng:compat": ["time:TemporalEntity", "file:iana:text:calendar", "file:iana:application:calendar+xml", "file:iana:application:calendar+json"] //https://www.rfc-editor.org/rfc/rfc5545 + }, + "social:scheduler": { "ng:crdt": "Graph", "ng:n": "Scheduler", "ng:a": "Helps finding a common time slot for several participants to a future event", @@ -719,7 +719,7 @@ export const official_classes = { }, "ng:compat": ["as:Invite","as:Reject","as:Accept","as:TentativeAccept","as:TentativeReject"] }, - "social/reaction": { + "social:reaction": { "ng:crdt": "Graph", "ng:n": "Reaction", "ng:a": "A reaction by user to some content", @@ -728,7 +728,12 @@ export const official_classes = { }, "ng:compat": ["as:Like", "as:Dislike", "as:Listen", "as:Read", "as:View"] }, - "prod/task": { + "social:chatroom": { + "ng:crdt": "Graph", + "ng:n": "ChatRoom", + "ng:a": "A room for group chat", + }, + "prod:task": { "ng:crdt": "Graph", "ng:n": "Task", "ng:a": "A task to be done", @@ -736,12 +741,12 @@ export const official_classes = { "as":true, "pair": "http://virtual-assembly.org/ontologies/pair#", }, - "ng:include": ["post/*"], + "ng:include": ["post:*"], "ng:compat": ["pair:Task"] //see VTODO in iCalendar https://www.cs.utexas.edu/~mfkb/RKF/tree/components/specs/ontologies/Calendar-onto.html // see todo and todoList of Mobilizon https://framagit.org/framasoft/mobilizon/-/blob/main/lib/federation/activity_stream/converter/todo.ex // https://framagit.org/framasoft/mobilizon/-/blob/main/lib/federation/activity_stream/converter/todo_list.ex }, - "prod/project": { + "prod:project": { "ng:crdt": "Graph", "ng:n": "Project", "ng:a": "A project management / KanBan", @@ -749,7 +754,7 @@ export const official_classes = { "as":true, "pair": "http://virtual-assembly.org/ontologies/pair#", }, - "ng:include": ["post/*"], + "ng:include": ["post:*"], "ng:compat": ["pair:Project"] }, // see SRO https://www.researchgate.net/publication/350158531_From_a_Scrum_Reference_Ontology_to_the_Integration_of_Applications_for_Data-Driven_Software_Development @@ -769,7 +774,7 @@ export const official_classes = { /// svelte: https://github.com/V-Py/svelte-kanban // https://github.com/supabase-community/svelte-kanban // https://github.com/therosbif/kanban - "prod/issue": { + "prod:issue": { "ng:crdt": "Graph", "ng:n": "Issue", "ng:a": "An issue to be solved", @@ -777,19 +782,19 @@ export const official_classes = { "as":true, "pair": "http://virtual-assembly.org/ontologies/pair#", }, - "ng:include": ["prod/task"], + "ng:include": ["prod:task"], "ng:compat": ["pair:Challenge"] }, //https://github.com/go-gitea/gitea/issues/20232 // datamodel of gitea issues: https://github.com/go-gitea/gitea/blob/165346c15c6d021028a65121e692a17ffc927e2c/models/issue.go#L35-L79 - "prod/form": { + "prod:form": { "ng:crdt": "Graph", "ng:n": "Form", "ng:a": "A form to be filled-in", "ng:x": { "form" : "http://rdf.danielbeeke.nl/form/form-dev.ttl#", }, - "ng:compat": ["form:*","file/iana/application/schema+json"] + "ng:compat": ["form:*","file:iana:application:schema+json"] }, // https://jsonforms.io/docs/ // https://github.com/jsonform/jsonform @@ -804,97 +809,97 @@ export const official_classes = { // https://www.mediawiki.org/wiki/Extension:Page_Forms // https://rdf-form.danielbeeke.nl/ // consider using Shapes - "prod/filling": { + "prod:filling": { "ng:crdt": "Graph", "ng:n": "Form filling", "ng:a": "A form that has been filled-in", "ng:compat": [] }, - "prod/cad": { // https://mattferraro.dev/posts/cadmium + "prod:cad": { // https://mattferraro.dev/posts/cadmium "ng:crdt": "Automerge", "ng:n": "CAD", "ng:a": "CADmium", - "ng:compat": [] + "ng:compat": [] }, - "prod/slides": { //https://github.com/hakimel/reveal.js + "prod:slides": { //https://github.com/hakimel/reveal.js //https://pandoc.org/MANUAL.html#slide-shows "ng:crdt": "Graph", "ng:n": "Slides", "ng:a": "Slides and presentations", - "ng:include": ["post/*"], + "ng:include": ["post:*"], "ng:compat": [] }, - "prod/question" : { + "prod:question" : { "ng:crdt": "Graph", "ng:n": "Question", "ng:a": "A question that needs answers", "ng:x": { "as":true, }, - "ng:include": ["post/*"], - "ng:compat": ["as:Question"] + "ng:include": ["post:*"], + "ng:compat": ["as:Question"] }, - "prod/answer" :{ + "prod:answer" :{ "ng:crdt": "Graph", "ng:n": "Answer", "ng:a": "An answer to a question", "ng:x": { "as":true, }, - "ng:include": ["post/*"], - "ng:compat": ["as:Note"] + "ng:include": ["post:*"], + "ng:compat": ["as:Note"] }, - "prod/poll" : { + "prod:poll" : { "ng:crdt": "Graph", "ng:n": "Poll", "ng:a": "A poll where people will vote", "ng:x": { "as":true, }, - "ng:include": ["post/*"], - "ng:compat": ["as:Question"] + "ng:include": ["post:*"], + "ng:compat": ["as:Question"] }, - "prod/vote" : { + "prod:vote" : { "ng:crdt": "Graph", "ng:n": "Vote", "ng:a": "A vote cast for a Poll", "ng:x": { "as":true, }, - "ng:compat": ["as:Note"] + "ng:compat": ["as:Note"] }, "file" : { "ng:crdt": "Graph", "ng:n": "File", "ng:a": "Binary file", "ng:o": "n:g:z:file_viewer", - "ng:compat": [] + "ng:compat": [] }, - "file/ng/wallet" : { + "file:ng:wallet" : { "ng:n": "NextGraph Wallet File", "ng:a": "NextGraph Wallet File (.ngw)", - "ng:compat": [] + "ng:compat": [] }, - "file/ng/doc" : { + "file:ng:doc" : { "ng:n": "NextGraph Document File", "ng:a": "NextGraph Document File (.ngd)", - "ng:compat": [] + "ng:compat": [] }, - "file/ng/html" : { + "file:ng:html" : { "ng:n": "NextGraph Document Html", "ng:a": "NextGraph Document Html standalone file", - "ng:compat": [] + "ng:compat": [] }, - "file/text" : { + "file:text" : { "ng:crdt": "Graph", "ng:n": "File", "ng:a": "Text file", "ng:o": "n:g:z:file_viewer", - "ng:compat": ["file/iana/text/*", "file/iana/image/svg+xml", "file/iana/application/n-quads", "file/iana/application/trig", "file/iana/application/n-triples", "file/iana/application/rdf+xml", "file/iana/application/ld+json", - "file/iana/application/xml", "file/iana/application/yaml", "file/iana/application/xhtml+xml", "file/iana/application/node","file/iana/application/sparql-results+json","file/iana/application/sparql-results+xml", - "file/iana/message/rfc822","file/iana/multipart/related", "file/iana/text/vnd.graphviz", "file/iana/application/vnd.excalidraw+json", "file/iana/application/x-tex","file/iana/text/x-tex", - "file/iana/application/vcard+json", "file/iana/application/vcard+xml", "file/iana/text/calendar", "file/iana/application/calendar+xml", "file/iana/application/calendar+json", - "file/iana/application/schema+json", "file/iana/application/geo+json", "file/iana/application/json" ] + "ng:compat": ["file:iana:text:*", "file:iana:image:svg+xml", "file:iana:application:n-quads", "file:iana:application:trig", "file:iana:application:n-triples", "file:iana:application:rdf+xml", "file:iana:application:ld+json", + "file:iana:application:xml", "file:iana:application:yaml", "file:iana:application:xhtml+xml", "file:iana:application:node","file:iana:application:sparql-results+json","file:iana:application:sparql-results+xml", + "file:iana:message:rfc822","file:iana:multipart:related", "file:iana:text:vnd.graphviz", "file:iana:application:vnd.excalidraw+json", "file:iana:application:x-tex","file:iana:text:x-tex", + "file:iana:application:vcard+json", "file:iana:application:vcard+xml", "file:iana:text:calendar", "file:iana:application:calendar+xml", "file:iana:application:calendar+json", + "file:iana:application:schema+json", "file:iana:application:geo+json", "file:iana:application:json" ] }, }; \ No newline at end of file diff --git a/ng-app/src/lib/DataClassIcon.svelte b/ng-app/src/lib/DataClassIcon.svelte index ce70da2..e1d399e 100644 --- a/ng-app/src/lib/DataClassIcon.svelte +++ b/ng-app/src/lib/DataClassIcon.svelte @@ -65,7 +65,8 @@ Bolt, Heart, Cog, - Square3Stack3d + Square3Stack3d, + ChatBubbleLeftRight, } from "svelte-heros-v2"; export let config = {}; @@ -74,64 +75,65 @@ const exact_mapping = { page: Window, "app": Cog, - "app/z": SquaresPlus, + "app:z": SquaresPlus, class: ViewfinderCircle, contract: Briefcase, - "query/text": MagnifyingGlass, - "query/web": MagnifyingGlass, - "data/graph": Sun, - "data/table": TableCells, - "data/collection": ListBullet, - "data/container": Square3Stack3d, - "data/board": RectangleGroup, - "data/grid": Squares2x2, - "data/geomap": MapPin, - "e/email": Envelope, - "mc/text": Bars3BottomLeft, - "mc/link": Link, + "query:text": MagnifyingGlass, + "query:web": MagnifyingGlass, + "data:graph": Sun, + "data:table": TableCells, + "data:collection": ListBullet, + "data:container": Square3Stack3d, + "data:board": RectangleGroup, + "data:grid": Squares2x2, + "data:geomap": MapPin, + "e:email": Envelope, + "mc:text": Bars3BottomLeft, + "mc:link": Link, "plato/card": Clipboard, "plato/pad": Square2Stack, - "media/image": Photo, - "media/reel": Film, - "media/video": Film, - "media/album": RectangleStack, - "media/audio": Microphone, - "media/song": MusicalNote, - "media/subtitle": Ticket, - "media/overlay": CursorArrowRays, - "social/channel": Megaphone, - "social/stream": Bolt, - "social/contact": User, - "social/event": Clock, - "social/calendar": CalendarDays, - "social/scheduler": Calendar, - "social/reaction": Heart, - "prod/task": Stop, - "prod/project": Flag, - "prod/issue": HandRaised, - "prod/form": Newspaper, - "prod/filling": PencilSquare, - "prod/cad": CubeTransparent, - "prod/slides": PresentationChartBar, - "prod/question": QuestionMarkCircle, - "prod/answer": CheckCircle, - "prod/poll": QuestionMarkCircle, - "prod/vote": CheckCircle, + "media:image": Photo, + "media:reel": Film, + "media:video": Film, + "media:album": RectangleStack, + "media:audio": Microphone, + "media:song": MusicalNote, + "media:subtitle": Ticket, + "media:overlay": CursorArrowRays, + "social:channel": Megaphone, + "social:stream": Bolt, + "social:contact": User, + "social:event": Clock, + "social:calendar": CalendarDays, + "social:scheduler": Calendar, + "social:reaction": Heart, + "social:chatroom": ChatBubbleLeftRight, + "prod:task": Stop, + "prod:project": Flag, + "prod:issue": HandRaised, + "prod:form": Newspaper, + "prod:filling": PencilSquare, + "prod:cad": CubeTransparent, + "prod:slides": PresentationChartBar, + "prod:question": QuestionMarkCircle, + "prod:answer": CheckCircle, + "prod:poll": QuestionMarkCircle, + "prod:vote": CheckCircle, }; const prefix_mapping = { - "post/": DocumentText, + "post:": DocumentText, code: CodeBracket, schema: ArrowsPointingOut, service: Cube, - "e/": GlobeAlt, - "app/": StopCircle, - "query/": RocketLaunch, - "data/": CircleStack, - "doc/diagram": DocumentChartBar, - "doc/chart": ChartPie, - "doc/viz": ChartPie, - "doc/": ClipboardDocumentList, + "e:": GlobeAlt, + "app:": StopCircle, + "query:": RocketLaunch, + "data:": CircleStack, + "doc:diagram": DocumentChartBar, + "doc:chart": ChartPie, + "doc:viz": ChartPie, + "doc:": ClipboardDocumentList, file: Document, }; @@ -147,7 +149,7 @@ + + + +
+ + {#if $cannot_load_offline} +
+ + {@html $t("doc.cannot_load_offline")} + {$t("pages.user_panel.title")}. + +
+ {:else} +
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; + }}> + +
+ {#if $can_have_header} + +
1024} > + {#if $header_icon} + + {/if} + {#if $cur_tab.doc.can_edit} + {#if !$header_title}{$t($edit_header_button)} {/if} + {/if} + {#if $header_title} +

{$header_title}

+ {/if} +
+ {#if $header_description} +
1024}> + {$header_description} +
+ {/if} + {/if} + {#if commits} + {#await commits.load()} +
1024}> +

{$t("connectivity.loading")}...

+
+ {:then} + + {#if $cur_app} + {#await load_official_app($cur_app) then app} +
1024} > + +
+ {/await} + {/if} + {/await} + {/if} + +
+ +
+ + + {/if} + +
\ No newline at end of file diff --git a/ng-app/src/lib/FullLayout.svelte b/ng-app/src/lib/FullLayout.svelte index 2e62566..ba4276f 100644 --- a/ng-app/src/lib/FullLayout.svelte +++ b/ng-app/src/lib/FullLayout.svelte @@ -18,7 +18,7 @@ Modal, Toggle, } from "flowbite-svelte"; - import { link, location } from "svelte-spa-router"; + import { link, location, push } from "svelte-spa-router"; import MobileBottomBarItem from "./MobileBottomBarItem.svelte"; import MobileBottomBar from "./MobileBottomBar.svelte"; import NavIcon from "./components/NavIcon.svelte"; @@ -30,9 +30,12 @@ // @ts-ignore import { t } from "svelte-i18n"; import { onMount, tick } from "svelte"; - import { cur_branch_has_discrete, cur_tab, cur_viewer, cur_editor, toggle_graph_discrete, open_doc, + import { cur_tab, cur_viewer, cur_editor, toggle_graph_discrete, cur_tab_update, available_editors, available_viewers, set_editor, set_viewer, set_view_or_edit, toggle_live_edit, - has_editor_chat, all_files_count, all_comments_count, nav_bar, save, hideMenu } from "../tab"; + has_editor_chat, all_files_count, all_comments_count, nav_bar, save, hideMenu, show_modal_menu } from "../tab"; + import { + active_session, redirect_after_login, + } from "../store"; import ZeraIcon from "./ZeraIcon.svelte"; import { @@ -81,6 +84,7 @@ PaperClip, XMark, ArrowLeft, + ArchiveBox, } from "svelte-heros-v2"; import NavBar from "./components/NavBar.svelte"; @@ -100,7 +104,7 @@ mobile = true; } - let panes_available = 0; + let panes_available; $: if (width < 983) { panes_available = 0; } else if (width >= 983 && width < 1304) { @@ -128,11 +132,12 @@ pane_left2_used = false; pane_right_used = false; if ($cur_tab.right_pane || $cur_tab.folders_pane || $cur_tab.toc_pane) { - $cur_tab.show_modal_menu = true; + $show_modal_menu = true; } - } else { - if ($cur_tab.show_modal_menu && !$cur_tab.show_menu) { - $cur_tab.show_modal_menu = false; + } + $: if (panes_available > 0) { + if ($show_modal_menu && !$cur_tab.show_menu) { + $show_modal_menu = false; } if (panes_available == 1) { if ($cur_tab.right_pane) { @@ -208,18 +213,24 @@ let toolsMenu; async function scrollToTop() { await tick(); - top.scrollIntoView(); + if (top) top.scrollIntoView(); } async function scrollToMenuShare() { await tick(); - shareMenu.scrollIntoView(); + if (shareMenu) shareMenu.scrollIntoView(); } async function scrollToMenuTools() { await tick(); - toolsMenu.scrollIntoView(); + if (toolsMenu) toolsMenu.scrollIntoView(); } - onMount(async () => {await open_doc(""); await scrollToTop()}); + onMount(async () => { + await scrollToTop(); + }); + active_session.subscribe((as) => { if(!as) { + $redirect_after_login = $location; + push("#/"); + } }) $: activeUrl = "#" + $location; @@ -234,38 +245,41 @@ } const openPane = (pane:string) => { - // TODO - if ( pane == "folders") { - $cur_tab.folders_pane = !$cur_tab.folders_pane; - if ($cur_tab.folders_pane) { - if (panes_available <= 1 ) { - $cur_tab.right_pane = ""; + cur_tab_update((ct) => { + if ( pane == "folders") { + ct.folders_pane = !ct.folders_pane; + if (ct.folders_pane) { + if (panes_available <= 1 ) { + ct.right_pane = ""; + } } - } - } else if ( pane == "toc") { - $cur_tab.toc_pane = !$cur_tab.toc_pane; - if ($cur_tab.toc_pane) { - if (panes_available <= 1 ) { - $cur_tab.folders_pane = false; - $cur_tab.right_pane = ""; - } else if (panes_available == 2) { - if ($cur_tab.folders_pane && $cur_tab.right_pane) - $cur_tab.folders_pane = false; + } else if ( pane == "toc") { + ct.toc_pane = !ct.toc_pane; + if (ct.toc_pane) { + if (panes_available <= 1 ) { + ct.folders_pane = false; + ct.right_pane = ""; + } else if (panes_available == 2) { + if (ct.folders_pane && ct.right_pane) + ct.folders_pane = false; + } + } + } else { + if (ct.right_pane == pane) + ct.right_pane = ""; + else { + ct.right_pane = pane; } } - } else { - if ($cur_tab.right_pane == pane) - $cur_tab.right_pane = ""; - else { - $cur_tab.right_pane = pane; + if (panes_available > 0) { + ct.show_menu = false; + $show_modal_menu = false; + } else { + $show_modal_menu = true; + ct.show_menu = false; } - } - if (panes_available) { - hideMenu(); - } else { - $cur_tab.show_modal_menu = true; - $cur_tab.show_menu = false; - } + return ct; + }); } const openShare = (share:string) => { @@ -288,25 +302,30 @@ hideMenu(); } + const openArchive = (share:string) => { + // TODO + hideMenu(); + } + const closeModal = () => { - cur_tab.update(ct => { + $show_modal_menu = false; + cur_tab_update(ct => { ct.show_menu = false; - ct.show_modal_menu = false; - if (!panes_available) { - $cur_tab.right_pane = ""; - $cur_tab.folders_pane = false; - $cur_tab.toc_pane = false; + if (panes_available === 0) { + ct.right_pane = ""; + ct.folders_pane = false; + ct.toc_pane = false; } return ct; }); } const closePaneInModal = () => { - cur_tab.update(ct => { + cur_tab_update(ct => { ct.show_menu = true; - $cur_tab.right_pane = ""; - $cur_tab.folders_pane = false; - $cur_tab.toc_pane = false; + ct.right_pane = ""; + ct.folders_pane = false; + ct.toc_pane = false; return ct; }); } @@ -357,7 +376,7 @@
    - {#if $cur_branch_has_discrete} + {#if $cur_tab.branch.has_discrete}
  • {/if} {:else} - launchAppStore($cur_tab.cur_branch.class)}> + launchAppStore($cur_tab.branch.class)}> {/if} - openPane("mc") }> - - {$t("doc.menu.items.mc.label")} - - - openPane("folders") }> - - {$t("doc.menu.items.folders.label")} - - openPane("toc") }> - - {$t("doc.menu.items.toc.label")} - - openPane("files") }> - - {$t("doc.menu.items.files.label")} {$all_files_count} - -
    - { open_share = !open_share; scrollToMenuShare(); } }> - - {$t("doc.menu.items.share.label")} - - {#if open_share } - {#each share_items as share} - openShare(share.n) }> - - {$t(`doc.menu.items.${share.n}.label`)} - - {/each} - {/if} + {#if $cur_tab.branch.id} + openPane("folders") }> + + {$t("doc.menu.items.folders.label")} + + openPane("toc") }> + + {$t("doc.menu.items.toc.label")} + + openPane("files") }> + + {$t("doc.menu.items.files.label")} {$all_files_count} + +
    + { open_share = !open_share; scrollToMenuShare(); } }> + + {$t("doc.menu.items.share.label")} + + {#if open_share } + {#each share_items as share} + openShare(share.n) }> + + {$t(`doc.menu.items.${share.n}.label`)} + + {/each} + {/if} - openPane("comments") }> - - {$t("doc.menu.items.comments.label")} {$all_comments_count} - + openPane("comments") }> + + {$t("doc.menu.items.comments.label")} {$all_comments_count} + - openPane("branches") }> - - {$t("doc.menu.items.branches.label")} - + {#if $cur_tab.doc.is_member} + openPane("branches") }> + + {$t("doc.menu.items.branches.label")} + + {/if} - openPane("history") }> - - {$t("doc.menu.items.history.label")} - + openPane("history") }> + + {$t("doc.menu.items.history.label")} + - - - {$t("doc.menu.items.find.label")} - + + + {$t("doc.menu.items.find.label")} + - - - {$t("doc.menu.items.bookmark.label")} - + + + {$t("doc.menu.items.bookmark.label")} + - - - {$t("doc.menu.items.annotate.label")} - + + + {$t("doc.menu.items.annotate.label")} + - openPane("info") }> - - {$t("doc.menu.items.info.label")} - + openPane("info") }> + + {$t("doc.menu.items.info.label")} + - openAction("notifs") }> - - {$t("doc.menu.items.notifs.label")} - - {#if $cur_tab.doc.is_member} - openAction("permissions") }> - openAction("notifs") }> + + {$t("doc.menu.items.notifs.label")} + + {#if $cur_tab.doc.is_member} + openAction("permissions") }> + + {$t("doc.menu.items.permissions.label")} + + {/if} + openAction("settings") }> + - {$t("doc.menu.items.permissions.label")} + {$t("doc.menu.items.settings.label")} +
    + {open_tools = !open_tools; scrollToMenuTools();} } > + + {$t("doc.menu.items.tools.label")} + + {#if open_tools } + {#each tools_items as tool} + openAction(tool.n) }> + + {$t(`doc.menu.items.${tool.n}.label`)} + + {/each} + {/if} {/if} - openAction("settings") }> - - {$t("doc.menu.items.settings.label")} + openPane("mc") }> + + {$t("doc.menu.items.mc.label")} -
    - {open_tools = !open_tools; scrollToMenuTools();} } > - - {$t("doc.menu.items.tools.label")} + openArchive() }> + + {$t("doc.menu.items.archive.label")} - {#if open_tools } - {#each tools_items as tool} - openAction(tool.n) }> - - {$t(`doc.menu.items.${tool.n}.label`)} - - {/each} - {/if}
@@ -621,7 +650,7 @@ {#if mobile}
{#if !withoutNavBar} -
+
{/if} @@ -637,15 +666,14 @@ 13 - + - +
{:else} @@ -712,6 +740,7 @@ @@ -724,6 +753,7 @@ @@ -790,7 +820,7 @@
{/if}
-
+
diff --git a/ng-app/src/lib/Home.svelte b/ng-app/src/lib/Home.svelte index 25872c0..aa45e7d 100644 --- a/ng-app/src/lib/Home.svelte +++ b/ng-app/src/lib/Home.svelte @@ -10,16 +10,28 @@ --> @@ -52,8 +70,8 @@ >
-
+
{/if} - - +
+
+
{$t("doc.header.buttons.bookmarked")}
+
+
+
{$t("doc.menu.items.mc.label")}
+
+
+
{$t("doc.header.buttons.all_docs")}
+
+
+ diff --git a/ng-app/src/lib/Login.svelte b/ng-app/src/lib/Login.svelte index 637f677..97fa3d5 100644 --- a/ng-app/src/lib/Login.svelte +++ b/ng-app/src/lib/Login.svelte @@ -590,7 +590,7 @@ class:h-[160px]={!mobile} class:h-[93px]={mobile} class:text-8xl={!mobile} - on:click={async () => await on_pin_key(num)} + on:click={async () => {window.document.activeElement.blur(); await on_pin_key(num)}} disabled={pin_code.length >= 4} > {num} @@ -606,7 +606,7 @@ class:h-[160px]={!mobile} class:h-[93px]={mobile} class:text-8xl={!mobile} - on:click={async () => await on_pin_key(shuffle_pin[9])} + on:click={async () => {window.document.activeElement.blur();await on_pin_key(shuffle_pin[9])}} disabled={pin_code.length >= 4} > {shuffle_pin[9]} diff --git a/ng-app/src/lib/Test.svelte b/ng-app/src/lib/Test.svelte index c15b09c..36f9a10 100644 --- a/ng-app/src/lib/Test.svelte +++ b/ng-app/src/lib/Test.svelte @@ -17,7 +17,7 @@ } from "../history/gitgraph-js/gitgraph"; import ng from "../api"; import { - branch_subs, + branch_subscribe, active_session, cannot_load_offline, online, @@ -25,7 +25,7 @@ } from "../store"; import { link } from "svelte-spa-router"; import { onMount, onDestroy, tick } from "svelte"; - import { Button, Progressbar, Spinner } from "flowbite-svelte"; + import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte"; import DataClassIcon from "./DataClassIcon.svelte"; import { t } from "svelte-i18n"; let is_tauri = import.meta.env.TAURI_PLATFORM; @@ -33,7 +33,7 @@ let upload_progress: null | { total: number; current: number; error?: any } = null; - let files = $active_session && branch_subs($active_session.private_store_id); + let commits = $active_session && branch_subscribe($active_session.private_store_id, true); let gitgraph; @@ -658,16 +658,14 @@
{#if $cannot_load_offline}
{:else}
- +
{/if} - {#if files} - {#await files.load()} + {#if commits} + {#await commits.load()}

{$t("connectivity.loading")}...

{:then} - {#each $files as file} + {#each $commits.graph as triple} {triple}
{/each} + {#each $commits.heads as head} {head}
{/each} + {#each $commits.files as file}

{file.name} diff --git a/ng-app/src/lib/components/Logo.svelte b/ng-app/src/lib/components/Logo.svelte index eaad5bf..f7cf789 100644 --- a/ng-app/src/lib/components/Logo.svelte +++ b/ng-app/src/lib/components/Logo.svelte @@ -28,7 +28,7 @@ Provide classes using the `className` prop. export let className: string = ""; let connection_status_class = "logo-blue"; // Color is adjusted to connection status. - $: if ($connection_status === "connecting") { + $: if ($connection_status === "connecting" || $connection_status === "starting") { connection_status_class = "logo-pulse"; } else if ($connection_status === "disconnected") { connection_status_class = "logo-gray"; diff --git a/ng-app/src/lib/components/NavBar.svelte b/ng-app/src/lib/components/NavBar.svelte index 635f851..dcee3aa 100644 --- a/ng-app/src/lib/components/NavBar.svelte +++ b/ng-app/src/lib/components/NavBar.svelte @@ -16,10 +16,15 @@ CheckCircle, CheckBadge, EllipsisVertical, + ExclamationTriangle, } from "svelte-heros-v2"; import NavIcon from "./NavIcon.svelte"; - import {save, nav_bar, showMenu} from "../../tab"; + import { + Popover, + } from "flowbite-svelte"; + + import {save, nav_bar, showMenu, cur_tab, cur_tab_store_name_override, cur_tab_store_icon_override} from "../../tab"; export let scrollToTop = () => {}; @@ -27,6 +32,11 @@ // going back window.history.go(-1); } + + const closeErrorPopup = () => { + window.document.getElementById("error_popover_btn").click(); + } +

@@ -35,24 +45,36 @@
{/if} - {#if $nav_bar.icon} + {#if $cur_tab_store_icon_override || $nav_bar.icon}
-
{/if} -
{$nav_bar.title}
- {#if $nav_bar.newest} +
+ {$cur_tab_store_name_override || $nav_bar.title} +
+ {#if $nav_bar.newest && !$cur_tab.header_in_view}
- {@html $nav_bar.newest < 100 ? "+ "+$nav_bar.newest : ""} + {@html $nav_bar.newest < 100 ? "+"+$nav_bar.newest : ""}
{/if} - {#if $nav_bar.save !== undefined} + {#if $cur_tab.persistent_error} +
+ +
+ {@html $cur_tab.persistent_error.desc} +

Dismiss +
+ {:else if $nav_bar.save !== undefined} {#if $nav_bar.save }
diff --git a/ng-app/src/lib/components/NavIcon.svelte b/ng-app/src/lib/components/NavIcon.svelte index 20c00ed..76d2694 100644 --- a/ng-app/src/lib/components/NavIcon.svelte +++ b/ng-app/src/lib/components/NavIcon.svelte @@ -21,7 +21,12 @@ Bolt, Megaphone, QuestionMarkCircle, + ExclamationCircle, Key, + LockClosed, + GlobeAlt, + UserGroup, + PaperAirplane, } from "svelte-heros-v2"; import DataClassIcon from "../DataClassIcon.svelte"; @@ -33,6 +38,11 @@ stream: Bolt, channel: Megaphone, private: Key, + protected: LockClosed, + public: GlobeAlt, + group: UserGroup, + dialog: PaperAirplane, + unknown_doc: ExclamationCircle, }; const find = (dataClass: string) => { diff --git a/ng-app/src/lib/components/PaneHeader.svelte b/ng-app/src/lib/components/PaneHeader.svelte index 0ba1bf0..781feb5 100644 --- a/ng-app/src/lib/components/PaneHeader.svelte +++ b/ng-app/src/lib/components/PaneHeader.svelte @@ -18,19 +18,22 @@ EllipsisVertical, } from "svelte-heros-v2"; import { t } from "svelte-i18n"; - import {cur_tab} from "../../tab"; + import {cur_tab_update} from "../../tab"; export let pane_name = ""; export let pane_items = {}; const closePane = (pane:string|boolean) => { - if (pane=="folders") { - $cur_tab.folders_pane = false; - } else if (pane=="toc") { - $cur_tab.toc_pane = false; - } else { - $cur_tab.right_pane = ""; - } + cur_tab_update(($cur_tab) => { + if (pane=="folders") { + $cur_tab.folders_pane = false; + } else if (pane=="toc") { + $cur_tab.toc_pane = false; + } else { + $cur_tab.right_pane = ""; + } + return $cur_tab; + }); } diff --git a/ng-app/src/locales/de.json b/ng-app/src/locales/de.json index 56d8912..0e9d390 100644 --- a/ng-app/src/locales/de.json +++ b/ng-app/src/locales/de.json @@ -202,7 +202,7 @@ "pins_no_match": "Du hast nicht zweimal die gleiche PIN eingegeben", "almost_done": "Wir sind fast fertig!", "save_wallet_options": { - "description": "Es gibt 4 Optionen, die du auswählen musst, bevor wir dein Wallet erstellen können. Diese Optionen können dir helfen, dein Wallet zu nutzen und zu behalten. Wir möchten aber auch vorsichtig mit deiner Sicherheit und Privatsphäre umgehen.

Denk daran, dass du in jedem Fall, sobald dein Wallet erstellt ist, eine Datei herunterladen wirst, die du privat irgendwo auf deinem Gerät, USB-Stick oder einer Festplatte aufbewahren solltest. Dies ist die Standardmethode, mit der du dein Wallet nutzen und aufbewahren kannst. Die folgenden Wahlmöglichkeiten können dir dein Leben ein wenig erleichtern.", + "description": "Es gibt 2 Optionen, die du auswählen musst, bevor wir dein Wallet erstellen können.", "trust": "Vertraust du diesem Gerät?", "trust_description": "Wenn ja, wenn dieses Gerät dir gehört oder von wenigen vertrauenswürdigen Personen deiner Familie oder deines Arbeitsplatzes genutzt wird und du dich in Zukunft wieder von diesem Gerät aus anmelden möchtest, dann kannst du dein Wallet auf diesem Gerät speichern. Wenn dieses Gerät hingegen öffentlich ist und von Fremden geteilt wird, speichere dein Wallet nicht hier.", "trust_toggle": "Mein Wallet auf diesem Gerät speichern?", diff --git a/ng-app/src/locales/en.json b/ng-app/src/locales/en.json index c6cfbe3..99358f3 100644 --- a/ng-app/src/locales/en.json +++ b/ng-app/src/locales/en.json @@ -1,5 +1,33 @@ { "doc": { + "doc": "Document", + "protected_store": "Protected Profile", + "public_store": "Public Site", + "private_store": "Private Store", + "group_store": "Group Store", + "dialog_store": "Dialog Store", + "not_found" : "Document not found", + "not_found_details_online" : "The document could not be found locally on this device, nor on the broker.", + "not_found_details_offline" : "The document could not be found locally on this device, and it seems like you are offline, so it could not be retrieved from any broker neither.

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.

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.

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!

Check your connectivity status in the ", + "header": { + "buttons": { + "edit": "Edit title, icon, intro", + "edit_profile": "Edit profile", + "bookmarked": "Saved", + "all_docs": "All Docs", + "groups": "Groups", + "channels": "Channels", + "inbox": "Inbox", + "chat": "Chat" + } + }, + "errors": { + "InvalidNuri": "Invalid NURI" + }, + "errors_details": { + "InvalidNuri": "The provided NextGraph URI is invalid" + }, "graph" : "Graph", "discrete" : "Document", "menu" : { @@ -152,6 +180,10 @@ "mc": { "label": "Magic Carpet", "desc": "Opens the Magic Carpet (like a clipboard, but better)" + }, + "archive": { + "label": "Archive", + "desc": "Archive documents that you don't need anymore" } } } @@ -299,10 +331,10 @@ "2": "In your wallet, we store all the permissions to access documents you have been granted with, or that you have created yourself.", "3": "In order to open it, you will need to enter your pazzle and a PIN code 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 66bits, which is considered very high by all standards.", - "5": "You should only create one unique wallet for yourself. 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 import your existing wallet in all the apps and websites where you need it.", - "6": "Your wallet can be imported with the help of a small file that you download, or with a QRcode. In any case, you should never share this file or QRcode with anybody else.", + "5": "You should only create one unique wallet for yourself. 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 import your unique existing wallet in all the apps and websites where you need it.", + "6": "Your wallet can be transferred from one device to another with the help of a small file that you download, or with a QRcode, or with a TextCode that you copy/paste. In any case, you should never share this file or QRcode with anybody else.", "7": "We at NextGraph will never see the content of your wallet. It is encrypted and we do not know your pazzle, so we cannot see what is inside.", - "8": "For the same reason, we won't be able to help you if you forget your pazzle or PIN code, or if you loose the wallet file. There is no \"password recovery\" option 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. There is no \"password recovery\" option in this case. You can note your pazzle down on a piece of paper until you remember it, but don't forget to destroy this note after a while." }, "create_wallet_now": "I create my wallet now!", "select_server": "NextGraph is based on an efficient decentralized P2P network, and in order to join this network and start using the app, you need to first select a broker server.", @@ -319,7 +351,7 @@ }, "choose_broker": "Please choose one broker among the list", "register_with_broker": "Register with {broker}", - "for_eu_citizens": "European Union Citizens", + "for_eu_citizens": "European Union Server", "for_rest": "For the rest of the world", "enter_invite_link": "Enter an invitation link", "scan_invite_qr": "Scan an invitation QR-code", @@ -362,18 +394,18 @@ "pins_no_match": "You didn't enter the same PIN twice", "almost_done": "We are almost done!", "save_wallet_options": { - "description": "There are 4 options to choose before we can create your wallet. Those options can help you to use and keep your wallet. But we also want to be careful with your security and privacy.

Remember that in any case, once your wallet will be created, you will download a file that you should keep privately somewhere on your device, USB key or hard-disk. This is the default way you can use and keep your wallet. Now let's look at some options that can make your life a bit easier.", + "description": "There are 2 options to choose before we can create your wallet.", "trust": "Do you trust this device?", "trust_description": "If you do, if this device is yours, or it is used by a few trusted persons of your family or workplace, and you would like to login again from this device in the future, then you can save your wallet on this device. To the contrary, if this device is public and shared by strangers, do not save your wallet here.", - "trust_toggle": "Save my wallet on this device?", + "trust_toggle": "Yes, save my wallet on this device", "device_name_description": "To see which devices you are connected with, every device should have a name (e.g. Bob's laptop). Please enter it here.", "cloud": "Keep a copy in the cloud?", "cloud_description": "Are you afraid that you will loose the file containing your wallet? If this would happen, your wallet would be lost forever, together with all your documents. We can keep an encrypted copy of your wallet in our cloud. Only you will be able to download it with a special link. You would have to keep this link safely though. By selecting this option, you agree to the", "cloud_toggle": "Save my wallet in the cloud?", "cloud_tos": "Terms of Service of our cloud", - "pdf": "Create a PDF file of your wallet?", - "pdf_description": "We can prepare for you a PDF file containing all the information of your wallet, unencrypted. You should print this file and then delete the PDF (and empty the trash). Keep this printed document in a safe place. It contains all the information to regenerate your wallet in case you lost access to it.", - "pdf_toggle": "Create a PDF of my wallet?", + "pdf": "Should we create a PDF file of your wallet?", + "pdf_description": "We can prepare for you a PDF file containing all the information of your wallet, unencrypted. In the next screen, once your wallet is ready, you will be able to download such PDF file. You should print this file and then delete the PDF (and empty the trash). Keep this printed document in a safe place. It contains all the information to regenerate your wallet in case you lost access to it.", + "pdf_toggle": "Yes, create a PDF of my wallet", "link": "Create a link to access your wallet easily?", "link_description": "When you want to use your wallet on the web or from other devices, we can help you find your wallet by creating a simple link accessible from anywhere. Only you will have access to this link. In order to do so, we will keep your wallet ID and some information about your broker on our cloud servers. If you prefer to opt out, just uncheck this option. By selecting this option, you agree to the", "link_toggle": "Create a link to my wallet?" diff --git a/ng-app/src/routes/Home.svelte b/ng-app/src/routes/Home.svelte index 905161e..54941fc 100644 --- a/ng-app/src/routes/Home.svelte +++ b/ng-app/src/routes/Home.svelte @@ -29,7 +29,6 @@ let display_login_create = !$has_wallets || !$active_wallet; let unsubscribe; onMount(() => { - cannot_load_offline.set(false); //setTimeout(function () {}, 2); const combined = derived([active_wallet, has_wallets], ([$s1, $s2]) => [ $s1, diff --git a/ng-app/src/routes/NURI.svelte b/ng-app/src/routes/NURI.svelte index b5aaebf..883a560 100644 --- a/ng-app/src/routes/NURI.svelte +++ b/ng-app/src/routes/NURI.svelte @@ -12,24 +12,56 @@ import { onMount } from "svelte"; import { push } from "svelte-spa-router"; import FullLayout from "../lib/FullLayout.svelte"; + import Document from "../lib/Document.svelte"; import { t } from "svelte-i18n"; // The params prop contains values matched from the URL export let params = {}; import { active_session, } from "../store"; - - console.log(params); - - onMount(async () => { - if (!$active_session) { - push("#/"); - } else { - //await scrollToTop(); - } + import { + change_nav_bar, cur_tab, reset_in_memory + } from "../tab"; + import { + Square3Stack3d, + Megaphone, + InboxArrowDown, + ChatBubbleLeftRight, + Phone, + VideoCamera, + } from "svelte-heros-v2"; + //console.log(params); + let nuri = ""; + $: if ($active_session && params[1]) { if (params[1].startsWith("o:"+$active_session.private_store_id)) push("#/"); + else if (params[1].startsWith("o:"+$active_session.protected_store_id)) push("#/shared"); + else if (params[1].startsWith("o:"+$active_session.public_store_id)) push("#/site"); else nuri = params[1]; } + onMount(() => { + change_nav_bar("nav:unknown_doc",$t("doc.doc"), true); + reset_in_memory(); }); - { params[1] } + {#if nuri && $cur_tab.doc.is_store && $cur_tab.store.store_type === "group"} +
+
+
{$t("doc.header.buttons.chat")}
+
+
+
+
+
+
+
+
+
+
+
+
{$t("doc.header.buttons.all_docs")}
+
+
+ {/if} + {#if nuri} + + {/if}
\ No newline at end of file diff --git a/ng-app/src/routes/NotFound.svelte b/ng-app/src/routes/NotFound.svelte index 419341d..f9df038 100644 --- a/ng-app/src/routes/NotFound.svelte +++ b/ng-app/src/routes/NotFound.svelte @@ -16,6 +16,7 @@ import { ArrowLeft, } from "svelte-heros-v2"; + export let params; diff --git a/ng-app/src/routes/Shared.svelte b/ng-app/src/routes/Shared.svelte new file mode 100644 index 0000000..de65767 --- /dev/null +++ b/ng-app/src/routes/Shared.svelte @@ -0,0 +1,54 @@ + + + + + +
+
+
{$t("doc.header.buttons.groups")}
+
+
+
{$t("doc.header.buttons.inbox")}
+
+
+
{$t("doc.header.buttons.channels")}
+
+
+
{$t("doc.header.buttons.all_docs")}
+
+
+ +
+ + \ No newline at end of file diff --git a/ng-app/src/routes/Site.svelte b/ng-app/src/routes/Site.svelte new file mode 100644 index 0000000..94d1437 --- /dev/null +++ b/ng-app/src/routes/Site.svelte @@ -0,0 +1,46 @@ + + + + + +
+
+
{$t("doc.header.buttons.channels")}
+
+
+
{$t("doc.header.buttons.all_docs")}
+
+
+ +
+ + \ No newline at end of file diff --git a/ng-app/src/routes/WalletCreate.svelte b/ng-app/src/routes/WalletCreate.svelte index ffd7e03..0494622 100644 --- a/ng-app/src/routes/WalletCreate.svelte +++ b/ng-app/src/routes/WalletCreate.svelte @@ -992,7 +992,7 @@
-
+ {/if}
@@ -1445,7 +1445,7 @@ type="text" /> {/if} -

+

{@html $t("pages.wallet_create.save_wallet_options.pdf")}

- {#if !options.cloud} +