files and attachments pane

pull/37/head
Niko PLP 4 months ago
parent b6680d7cab
commit 00398272bd
  1. 1
      Cargo.lock
  2. 25
      ng-app/index-native.html
  3. 112
      ng-app/src-tauri/src/lib.rs
  4. 52
      ng-app/src/api.ts
  5. 19
      ng-app/src/apps/ListView.svelte
  6. 1
      ng-app/src/lib/Document.svelte
  7. 2
      ng-app/src/lib/FullLayout.svelte
  8. 1
      ng-app/src/lib/Pane.svelte
  9. 723
      ng-app/src/lib/Test.svelte
  10. 237
      ng-app/src/lib/panes/Files.svelte
  11. 7
      ng-app/src/lib/panes/History.svelte
  12. 7
      ng-app/src/locales/de.json
  13. 13
      ng-app/src/locales/en.json
  14. 39
      ng-app/src/store.ts
  15. 2
      ng-app/src/tab.ts
  16. 2
      ng-broker/src/server_broker.rs
  17. 19
      ng-net/src/app_protocol.rs
  18. 1
      ng-sdk-js/Cargo.toml
  19. 230
      ng-sdk-js/src/lib.rs
  20. 1
      ng-verifier/src/request_processor.rs
  21. 29
      ngaccount/web/index.html

1
Cargo.lock generated

@ -3498,6 +3498,7 @@ name = "ng-sdk-js"
version = "0.1.0-preview.1"
dependencies = [
"async-std",
"futures",
"getrandom 0.1.16",
"gloo-timers",
"js-sys",

@ -56,9 +56,34 @@
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NextGraph</title>
<style>
.splashing {
height: 95vh;display: grid;
}
</style>
</head>
<body>
<div id="splash" class="splashing">
<div style="margin: auto; display: grid;">
<svg
style="width:100px;height:100px;margin: 0 auto 20px ;"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 225 225"
>
<g>
<circle
r="106.98013"
cy="112.90476"
cx="109.88096"
style="fill:#ffffff;stroke:none;stroke-width:0.268375" />
<path
d="M 98.343352,190.26108 C 80.403778,187.53354 65.011938,179.57839 52.608228,166.62327 38.602093,151.99448 31.178059,133.41381 31.178059,112.98841 c 0,-10.21889 1.700058,-19.44396 5.221234,-28.332119 4.28678,-10.820699 10.037295,-19.39063 18.535095,-27.62263 4.72982,-4.58187 6.60687,-6.10643 11.28099,-9.16256 11.89869,-7.779841 24.173884,-11.879991 38.095802,-12.724761 19.80437,-1.2017 39.11165,5.11306 54.60284,17.858751 1.50718,1.24006 2.72951,2.35934 2.71628,2.48729 -0.0132,0.12795 -3.85821,3.63443 -8.54442,7.79217 -4.6862,4.157729 -10.04724,8.96276 -11.91342,10.677819 -1.86617,1.715071 -3.54094,3.11831 -3.7217,3.11831 -0.18075,0 -1.39985,-0.745188 -2.70911,-1.655969 -7.53011,-5.23834 -15.99428,-7.82188 -25.62597,-7.82188 -12.731628,0 -23.249192,4.3379 -32.143882,13.257541 -6.39594,6.413868 -10.70387,14.555268 -12.50018,23.623578 -0.69099,3.48832 -0.68968,13.53072 0.002,17.00893 3.70508,18.62577 18.31886,33.10194 36.642322,36.29729 4.16439,0.72621 11.98099,0.71223 15.98975,-0.0286 14.03187,-2.59311 25.86047,-11.36806 32.26533,-23.93578 0.77379,-1.51834 1.26018,-2.88461 1.08086,-3.03616 -0.17934,-0.15156 -6.87448,-1.1779 -14.87813,-2.28078 -9.7795,-1.34758 -14.92353,-2.21379 -15.68471,-2.64117 -1.52067,-0.85379 -2.83611,-2.88806 -2.83611,-4.3859 0,-1.1732 2.02687,-15.86876 2.49085,-18.05962 0.29676,-1.40127 2.42559,-3.4934 3.84317,-3.77691 0.62227,-0.12445 8.82712,0.85555 18.28065,2.18348 9.43343,1.32511 17.26269,2.29453 17.39833,2.15427 0.13566,-0.14026 1.11808,-6.54833 2.18313,-14.24014 1.10778,-8.000208 2.20407,-14.60184 2.56177,-15.426229 0.34392,-0.792599 1.11019,-1.849131 1.70287,-2.34782 2.06321,-1.736079 3.1433,-1.785011 12.20439,-0.55291 9.63637,1.310309 10.70873,1.56224 12.28077,2.88503 1.64359,1.382979 2.2732,2.810909 2.25906,5.123309 -0.007,1.10173 -0.92172,8.29645 -2.03332,15.98826 -1.11158,7.69182 -1.97159,14.04091 -1.91113,14.1091 0.0605,0.0682 7.16644,1.11143 15.79109,2.31832 11.10566,1.55407 16.00827,2.38757 16.80223,2.85657 1.53015,0.90389 2.48023,2.64785 2.45017,4.49756 -0.0462,2.84349 -2.41252,18.12279 -2.97521,19.21089 -0.66164,1.27949 -2.60244,2.54696 -3.92109,2.56074 -0.51973,0.005 -7.87449,-0.95937 -16.34391,-2.144 -8.46944,-1.18464 -15.47588,-2.077 -15.56986,-1.98301 -0.094,0.094 -1.18792,7.34163 -2.43097,16.10589 -1.44004,10.15311 -2.49792,16.43621 -2.91556,17.31631 -0.72531,1.52848 -2.76261,3.06291 -4.53817,3.41802 -0.95688,0.19138 -10.90014,-0.92798 -13.59859,-1.53084 -0.5471,-0.12223 -1.89146,0.67252 -4.50941,2.66588 -11.2627,8.57562 -24.34195,13.90917 -38.35741,15.64164 -4.40038,0.54395 -15.72658,0.43298 -19.853658,-0.19451 z"
style="fill:#4972a5;fill-opacity:1;stroke:#4972a5;stroke-width:0.377976;stroke-opacity:1" />
</g>
</svg>
</div>
</div>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>

@ -338,6 +338,25 @@ async fn decode_invitation(invite: String) -> Option<Invitation> {
decode_invitation_string(invite)
}
#[tauri::command(rename_all = "snake_case")]
async fn file_get(
session_id: u64,
stream_id: &str,
reference: BlockRef,
branch_nuri: String,
app: tauri::AppHandle,
) -> Result<(), String> {
let branch_nuri =
NuriV0::new_from(&branch_nuri).map_err(|e| format!("branch_nuri: {}", e.to_string()))?;
let mut nuri = NuriV0::new_from_obj_ref(&reference);
nuri.copy_target_from(&branch_nuri);
let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri, None);
request.set_session_id(session_id);
app_request_stream(request, stream_id, app).await
}
#[tauri::command(rename_all = "snake_case")]
async fn app_request_stream(
request: AppRequest,
@ -382,6 +401,69 @@ async fn app_request_stream(
Ok(())
}
#[tauri::command(rename_all = "snake_case")]
async fn file_save_to_downloads(
session_id: u64,
reference: ObjectRef,
filename: String,
branch_nuri: String,
app: tauri::AppHandle,
) -> Result<(), String> {
let branch_nuri =
NuriV0::new_from(&branch_nuri).map_err(|e| format!("branch_nuri: {}", e.to_string()))?;
let mut nuri = NuriV0::new_from_obj_ref(&reference);
nuri.copy_target_from(&branch_nuri);
let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri, None);
request.set_session_id(session_id);
let (mut reader, cancel) = nextgraph::local_broker::app_request_stream(request)
.await
.map_err(|e| e.to_string())?;
let mut file_vec: Vec<u8> = vec![];
while let Some(app_response) = reader.next().await {
match app_response {
AppResponse::V0(AppResponseV0::FileMeta(filemeta)) => {
file_vec = Vec::with_capacity(filemeta.size as usize);
}
AppResponse::V0(AppResponseV0::FileBinary(mut bin)) => {
if !bin.is_empty() {
file_vec.append(&mut bin);
}
}
AppResponse::V0(AppResponseV0::EndOfStream) => break,
_ => return Err("invalid response".to_string()),
}
}
let mut i: usize = 0;
loop {
let dest_filename = if i == 0 {
filename.clone()
} else {
filename
.rsplit_once(".")
.map(|(l, r)| format!("{l} ({}).{r}", i.to_string()))
.or_else(|| Some(format!("{filename} ({})", i.to_string())))
.unwrap()
};
let path = app
.path()
.resolve(dest_filename, BaseDirectory::Download)
.unwrap();
if path.exists() {
i = i + 1;
} else {
write(path, &file_vec).map_err(|e| e.to_string())?;
break;
}
}
Ok(())
}
#[tauri::command(rename_all = "snake_case")]
async fn doc_fetch_private_subscribe() -> Result<AppRequest, String> {
let request = AppRequest::new(
@ -494,7 +576,7 @@ async fn sparql_query(
}
#[tauri::command(rename_all = "snake_case")]
async fn app_request(request: AppRequest, _app: tauri::AppHandle) -> Result<AppResponse, String> {
async fn app_request(request: AppRequest) -> Result<AppResponse, String> {
//log_debug!("app request {:?}", request);
nextgraph::local_broker::app_request(request)
@ -502,19 +584,40 @@ async fn app_request(request: AppRequest, _app: tauri::AppHandle) -> Result<AppR
.map_err(|e| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn app_request_with_nuri_command(
nuri: String,
command: AppRequestCommandV0,
session_id: u64,
payload: Option<AppRequestPayloadV0>,
) -> Result<AppResponse, String> {
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
let payload = payload.map(|p| AppRequestPayload::V0(p));
let request = AppRequest::V0(AppRequestV0 {
session_id,
command,
nuri,
payload,
});
app_request(request).await
}
#[tauri::command(rename_all = "snake_case")]
async fn upload_chunk(
session_id: u64,
upload_id: u32,
chunk: serde_bytes::ByteBuf,
nuri: NuriV0,
nuri: String,
_app: tauri::AppHandle,
) -> Result<AppResponse, String> {
//log_debug!("upload_chunk {:?}", chunk);
let mut request = AppRequest::new(
AppRequestCommandV0::FilePut,
nuri,
NuriV0::new_from(&nuri).map_err(|e| e.to_string())?,
Some(AppRequestPayload::V0(
AppRequestPayloadV0::RandomAccessFilePutChunk((upload_id, chunk)),
)),
@ -725,7 +828,10 @@ impl AppBuilder {
doc_fetch_repo_subscribe,
cancel_stream,
app_request_stream,
file_get,
file_save_to_downloads,
app_request,
app_request_with_nuri_command,
upload_chunk,
get_device_name,
sparql_query,

@ -39,6 +39,7 @@ const mapping = {
"user_connect": ["info","user_id","location"],
"user_disconnect": ["user_id"],
"app_request": ["request"],
"app_request_with_nuri_command": ["nuri", "command", "session_id", "payload"],
"sparql_query": ["session_id","sparql","nuri"],
"sparql_update": ["session_id","sparql","nuri"],
"test": [ ],
@ -46,6 +47,7 @@ const mapping = {
"doc_fetch_private_subscribe": [],
"doc_fetch_repo_subscribe": ["repo_o"],
"branch_history": ["session_id", "nuri"],
"file_save_to_downloads": ["session_id", "reference", "filename", "branch_nuri"],
}
@ -162,7 +164,42 @@ const handler = {
}
return ret;
}
else if (path[0] === "app_request_stream") {
else if (path[0] === "file_get") {
let stream_id = (lastStreamId += 1).toString();
//console.log("stream_id",stream_id);
let { getCurrent } = await import("@tauri-apps/plugin-window");
//let session_id = args[0];
let callback = args[3];
let unlisten = await getCurrent().listen(stream_id, async (event) => {
//console.log(event.payload);
if (event.payload.V0.FileBinary) {
event.payload.V0.FileBinary = Uint8Array.from(event.payload.V0.FileBinary);
}
let ret = callback(event.payload);
if (ret === true) {
await tauri.invoke("cancel_stream", {stream_id});
} else if (ret.then) {
ret.then(async (val)=> {
if (val === true) {
await tauri.invoke("cancel_stream", {stream_id});
}
});
}
})
try {
await tauri.invoke("file_get",{stream_id, session_id:args[0], reference: args[1], branch_nuri:args[2]});
} catch (e) {
unlisten();
await tauri.invoke("cancel_stream", {stream_id});
throw e;
}
return () => {
unlisten();
tauri.invoke("cancel_stream", {stream_id});
}
} else if (path[0] === "app_request_stream") {
let stream_id = (lastStreamId += 1).toString();
//console.log("stream_id",stream_id);
let { getCurrent } = await import("@tauri-apps/plugin-window");
@ -170,7 +207,7 @@ const handler = {
let request = args[0];
let callback = args[1];
let unlisten = await getCurrent().listen(stream_id, (event) => {
let unlisten = await getCurrent().listen(stream_id, async (event) => {
//console.log(event.payload);
if (event.payload.V0.FileBinary) {
event.payload.V0.FileBinary = Uint8Array.from(event.payload.V0.FileBinary);
@ -184,7 +221,16 @@ const handler = {
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(()=> {})
let ret = callback(event.payload);
if (ret === true) {
await tauri.invoke("cancel_stream", {stream_id});
} else if (ret.then) {
ret.then(async (val)=> {
if (val === true) {
await tauri.invoke("cancel_stream", {stream_id});
}
});
}
})
try {
await tauri.invoke("app_request_stream",{stream_id, request});

@ -12,7 +12,6 @@
<script lang="ts">
import {
get_blob,
} from "../store";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
@ -34,21 +33,5 @@
{#each commits.graph as triple}
<div class="flex"> {triple}</div>
{/each}
{#each commits.files as file}
<div class="flex">
{file.name}
{#await get_blob(file)}
<div class="row">
<Spinner />
</div>
{:then url}
{#if url}
<img src={url} title={file.nuri} alt={file.name} />
{/if}
{/await}
</div>
{/each}
</div>

@ -15,7 +15,6 @@
active_session,
cannot_load_offline,
online,
get_blob,
} from "../store";
import {

@ -429,7 +429,7 @@
backdropClass="bg-gray-900 bg-opacity-50 dark:bg-opacity-80 menu-bg-modal"
>
<div class="static">
<div class="absolute top-2 right-4 w-10 h-10" role="button" aria-label="Close menu" title="Close menu"
<div class="absolute top-2 right-4 w-10 h-10 bg-white" role="button" aria-label="Close menu" title="Close menu"
on:click={closeModal}
on:keypress={closeModal}
tabindex="0">

@ -15,6 +15,7 @@
const panes = {
"history": "History",
"files": "Files",
};
const load_pane = async (pane_name) => {

@ -10,737 +10,24 @@
-->
<script lang="ts">
import {
createGitgraph,
templateExtend,
TemplateName,
} from "./panes/history/gitgraph-js/gitgraph";
import ng from "../api";
import {
branch_subscribe,
active_session,
cannot_load_offline,
online,
get_blob,
} from "../store";
import { link } from "svelte-spa-router";
import { onMount, onDestroy, tick } from "svelte";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import DataClassIcon from "./DataClassIcon.svelte";
import { Button, Progressbar, Spinner } from "flowbite-svelte";
import { t } from "svelte-i18n";
let is_tauri = import.meta.env.TAURI_PLATFORM;
let upload_progress: null | { total: number; current: number; error?: any } =
null;
let commits = $active_session && branch_subscribe($active_session.private_store_id, true);
let gitgraph;
let next = [
{
hash: "I",
subject: "niko2",
author: "",
parents: ["G"],
},
{
hash: "T",
subject: "niko2",
author: "",
parents: ["D", "H"],
},
{
hash: "Z",
subject: "niko2",
author: "",
parents: ["E"],
},
{
hash: "L",
subject: "niko2",
author: "",
parents: ["H"],
},
{
hash: "J",
subject: "niko2",
author: "",
parents: ["L", "Z", "I"],
},
{
hash: "K",
subject: "niko2",
author: "",
parents: ["G", "E"],
},
{
hash: "X",
subject: "niko2",
author: "",
parents: ["I"],
},
{
hash: "Q",
subject: "niko2",
author: "",
parents: ["L", "X"],
},
];
function add() {
let n = next.shift();
if (n) gitgraph.commit(n);
}
onMount(async () => {
const graphContainer = document.getElementById("graph-container");
gitgraph = createGitgraph(graphContainer, {
template: templateExtend(TemplateName.Metro, {
branch: { label: { display: false } },
commit: { message: { displayAuthor: false } },
}),
});
gitgraph.swimlanes(["A", "F", "C"]);
gitgraph.import([
{
hash: "A",
subject: "niko2",
branch: "A",
parents: [],
author: "",
x: 0,
y: 0,
},
{
hash: "B",
subject: "niko2",
branch: "A",
author: "",
parents: ["A"],
x: 0,
y: 1,
},
{
hash: "D",
subject: "niko2",
branch: "A",
author: "",
parents: ["B"],
x: 0,
y: 2,
},
{
hash: "C",
subject: "niko2",
branch: "C",
author: "",
parents: ["B"],
x: 2,
y: 3,
},
{
hash: "F",
subject: "niko2",
branch: "F",
author: "",
parents: ["B", "C"],
x: 1,
y: 4,
},
{
hash: "G",
subject: "niko2",
branch: "F",
parents: ["F"],
author: "",
x: 1,
y: 5,
},
{
hash: "E",
subject: "niko2",
branch: "C",
author: "",
parents: ["C"],
x: 2,
y: 6,
},
// {
// hash: "H",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D", "G"],
// x: 0,
// y: 7,
// },
// {
// hash: "I",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D", "H"],
// x: 0,
// y: 8,
// },
// {
// hash: "H",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D", "G", "E"],
// x: 0,
// y: 7,
// },
]);
// window.setTimeout(() => {
// gitgraph.commit({
// hash: "H",
// subject: "niko2",
// author: "",
// parents: ["D", "G", "E"],
// });
// }, 0);
window.setTimeout(() => {
gitgraph.commit({
hash: "H",
subject: "niko2",
author: "",
parents: ["G", "E"],
});
}, 0);
// window.setTimeout(() => {
// gitgraph.commit({
// hash: "H",
// subject: "niko2",
// author: "",
// parents: ["G"],
// });
// }, 0);
// gitgraph.swimlanes(["A", "B", false, "D"]);
// gitgraph.import([
// {
// hash: "A",
// subject: "niko2",
// branch: "A",
// parents: [],
// author: "",
// x: 0,
// y: 0,
// },
// {
// hash: "C",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["A"],
// x: 0,
// y: 1,
// },
// {
// hash: "D",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["C"],
// x: 0,
// y: 2,
// },
// {
// hash: "E",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D"],
// x: 0,
// y: 3,
// },
// {
// hash: "B",
// subject: "niko2",
// branch: "C",
// author: "",
// parents: ["A"],
// x: 2,
// y: 4,
// },
// {
// hash: "G",
// subject: "niko2",
// branch: "C",
// parents: ["B"],
// author: "",
// x: 2,
// y: 5,
// },
// {
// hash: "F",
// subject: "niko2",
// branch: "B",
// author: "",
// parents: ["D", "G"],
// x: 1,
// y: 6,
// },
// {
// hash: "H",
// subject: "niko2",
// branch: "D",
// author: "",
// parents: ["G"],
// x: 3,
// y: 7,
// },
// // {
// // hash: "I",
// // subject: "niko2",
// // branch: "A",
// // author: "",
// // parents: ["E", "F", "H"],
// // x: 0,
// // y: 8,
// // },
// ]);
// gitgraph.swimlanes([
// "A",
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// ]);
// gitgraph.import([
// {
// hash: "A",
// subject: "niko2",
// branch: "A",
// parents: [],
// author: "",
// x: 0,
// y: 0,
// },
// {
// hash: "B",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["A"],
// x: 0,
// y: 1,
// },
// {
// hash: "C",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["B"],
// x: 0,
// y: 2,
// },
// {
// hash: "D",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["C"],
// x: 0,
// y: 3,
// },
// {
// hash: "E",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D"],
// x: 0,
// y: 4,
// },
// {
// hash: "J",
// subject: "niko2",
// branch: "J",
// parents: ["A"],
// author: "",
// x: 2,
// y: 5,
// },
// {
// hash: "K",
// subject: "niko2",
// branch: "J",
// author: "",
// parents: ["J"],
// x: 2,
// y: 6,
// },
// {
// hash: "L",
// subject: "niko2",
// branch: "L",
// author: "",
// parents: ["A"],
// x: 3,
// y: 7,
// },
// {
// hash: "M",
// subject: "niko2",
// branch: "L",
// author: "",
// parents: ["L"],
// x: 3,
// y: 8,
// },
// {
// hash: "G",
// subject: "niko2",
// branch: "G",
// author: "",
// parents: ["C", "K", "M"],
// x: 1,
// y: 9,
// },
// {
// hash: "H",
// subject: "niko2",
// branch: "G",
// author: "",
// parents: ["G"],
// x: 1,
// y: 10,
// },
// {
// hash: "I",
// subject: "niko2",
// branch: "G",
// author: "",
// parents: ["H"],
// x: 1,
// y: 11,
// },
// {
// hash: "F",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["E", "I"],
// x: 0,
// y: 12,
// },
// {
// hash: "1",
// subject: "niko2",
// branch: "1",
// author: "",
// parents: ["A"],
// x: 4,
// y: 13,
// },
// {
// hash: "2",
// subject: "niko2",
// branch: "2",
// author: "",
// parents: ["A"],
// x: 5,
// y: 14,
// },
// {
// hash: "3",
// subject: "niko2",
// branch: "3",
// author: "",
// parents: ["A"],
// x: 6,
// y: 15,
// },
// {
// hash: "4",
// subject: "niko2",
// branch: "4",
// author: "",
// parents: ["A"],
// x: 7,
// y: 16,
// },
// {
// hash: "5",
// subject: "niko2",
// branch: "5",
// author: "",
// parents: ["A"],
// x: 8,
// y: 17,
// },
// {
// hash: "6",
// subject: "niko2",
// branch: "6",
// author: "",
// parents: ["A"],
// x: 9,
// y: 18,
// },
// {
// hash: "7",
// subject: "niko2",
// branch: "7",
// author: "",
// parents: ["A"],
// x: 10,
// y: 19,
// },
// {
// hash: "8",
// subject: "niko2",
// branch: "8",
// author: "",
// parents: ["A"],
// x: 11,
// y: 20,
// },
// {
// hash: "9",
// subject: "niko2",
// branch: "9",
// author: "",
// parents: ["A"],
// x: 12,
// y: 21,
// },
// ]);
});
let fileinput;
function uploadFile(upload_id, nuri, file, success) {
let chunkSize = 1_048_564;
let fileSize = file.size;
let offset = 0;
let readBlock = null;
upload_progress = { total: fileSize, current: offset };
let onLoadHandler = async function (event) {
let result = event.target.result;
if (event.target.error == null) {
offset += result.byteLength;
upload_progress = { total: fileSize, current: offset };
// console.log("chunk", result);
let res = await ng.upload_chunk(
$active_session.session_id,
upload_id,
result,
nuri
);
//console.log("chunk upload res", res);
// if (onChunkRead) {
// onChunkRead(result);
// }
} else {
// if (onChunkError) {
// onChunkError(event.target.error);
// }
return;
}
// If finished:
if (offset >= fileSize) {
//console.log("file uploaded");
let res = await ng.upload_chunk(
$active_session.session_id,
upload_id,
[],
nuri
);
//console.log("end upload res", res);
if (success) {
upload_progress = { total: fileSize, current: fileSize };
success(res);
} else {
upload_progress = { total: fileSize, current: fileSize, error: true };
}
// Make progress bar disappear
setTimeout(() => {
upload_progress = null;
}, 2_500);
return;
}
readBlock(offset, chunkSize, file);
};
readBlock = function (offset, length, file) {
let fileReader = new FileReader();
let blob = file.slice(offset, length + offset);
fileReader.onload = onLoadHandler;
fileReader.readAsArrayBuffer(blob);
};
readBlock(offset, chunkSize, file);
return;
}
const onFileSelected = async (e) => {
let image = e.target.files[0];
if (!image) return;
//console.log(image);
let nuri = {
target: "PrivateStore",
entire_store: false,
access: [],
locator: [],
};
let start_request = {
V0: {
command: "FilePut",
nuri,
payload: {
V0: {
RandomAccessFilePut: image.type,
},
},
session_id: $active_session.session_id,
},
};
let start_res = await ng.app_request(start_request);
let upload_id = start_res.V0.FileUploading;
uploadFile(upload_id, nuri, image, async (reference) => {
if (reference) {
let request = {
V0: {
command: "FilePut",
nuri,
payload: {
V0: {
AddFile: {
filename: image.name,
object: reference.V0.FileUploaded,
},
},
},
session_id: $active_session.session_id,
},
};
await ng.app_request(request);
}
});
fileinput.value = "";
};
</script>
<div>
<div id="graph-container"></div>
{#if $cannot_load_offline}
<div class="row p-4">
<Alert color="yellow">
{@html $t("doc.cannot_load_offline")}
<a href="#/user">{$t("pages.user_panel.title")}</a>.
</Alert>
</div>
{:else}
<div class="row pt-2">
<Button
type="button"
on:click={() => {
add();
}}
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mr-2 mb-2"
>
g
</Button>
<Button
disabled={!$online && !is_tauri}
type="button"
on:click={() => {
fileinput.click();
}}
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mr-2 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
/>
</svg>
{$t("pages.test.add_image")}
</Button>
<input
style="display:none"
type="file"
accept=".jpg, .jpeg, .png"
on:change={(e) => onFileSelected(e)}
bind:this={fileinput}
/>
</div>
{#if upload_progress !== null}
<div class="mx-6 mt-2">
<Progressbar
progress={(
(100 * upload_progress.current) /
upload_progress.total
).toFixed(0)}
labelOutside={$t("pages.test.upload_progress")}
/>
</div>
{/if}
{#if commits}
{#await commits.load()}
<p>{$t("connectivity.loading")}...</p>
{:then}
{#each $commits.graph as triple} {triple}<br/> {/each}
{#each $commits.heads as head} {head} <br/> {/each}
{#each $commits.files as file}
<p>
{file.name}
{#await get_blob(file)}
<div class="ml-2">
<Spinner />
</div>
{:then url}
{#if url}
<img src={url} title={file.nuri} alt={file.name} />
{/if}
{/await}
</p>
{/each}
{/await}
{/if}
{/if}
</div>

@ -0,0 +1,237 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import ng from "../../api";
import {
branch_subscribe,
active_session,
online,
get_blob,
} from "../../store";
import { cur_tab } from "../../tab";
import {
ExclamationTriangle,
ArrowDownTray,
ArrowUpTray,
} from "svelte-heros-v2";
import { onMount, onDestroy, tick } from "svelte";
import { Button, Progressbar, Spinner } from "flowbite-svelte";
import { t } from "svelte-i18n";
let is_tauri = import.meta.env.TAURI_PLATFORM;
let upload_progress: null | { total: number; current: number; error?: any } = null;
let commits = $active_session && branch_subscribe($cur_tab.branch.nuri, false);
let fileinput;
let file_urls = {};
const prepare_url = (nuri) => {
if (!file_urls[nuri]) {
file_urls[nuri] = {
click: false
};
}
return true;
}
const download = async (file) => {
if (is_tauri) {
await ng.file_save_to_downloads($active_session.session_id, file.reference, file.name, "did:ng:"+$cur_tab.branch.nuri);
} else {
file_urls[file.nuri].url = await get_blob(file, false);
//console.log(file.name);
//console.log(file_urls[file.nuri].click);
await tick();
file_urls[file.nuri].click.click();
}
}
const isImage = async (url) : Promise<boolean> => {
if ( typeof url === 'string' ) {
let blob = await fetch(url).then(r => r.blob());
return blob.type.startsWith("image/");
}
return false;
}
function uploadFile(upload_id, nuri, file, success) {
console.log(nuri);
let chunkSize = 1_048_564;
let fileSize = file.size;
let offset = 0;
let readBlock = null;
upload_progress = { total: fileSize, current: offset };
let onLoadHandler = async function (event) {
let result = event.target.result;
if (event.target.error == null) {
offset += result.byteLength;
upload_progress = { total: fileSize, current: offset };
// console.log("chunk", result);
let res = await ng.upload_chunk(
$active_session.session_id,
upload_id,
result,
nuri
);
//console.log("chunk upload res", res);
// if (onChunkRead) {
// onChunkRead(result);
// }
} else {
// if (onChunkError) {
// onChunkError(event.target.error);
// }
return;
}
// If finished:
if (offset >= fileSize) {
//console.log("file uploaded");
let res = await ng.upload_chunk(
$active_session.session_id,
upload_id,
[],
nuri
);
//console.log("end upload res", res);
if (success) {
upload_progress = { total: fileSize, current: fileSize };
success(res);
} else {
upload_progress = { total: fileSize, current: fileSize, error: true };
}
// Make progress bar disappear
setTimeout(() => {
upload_progress = null;
}, 1_000);
return;
}
readBlock(offset, chunkSize, file);
};
readBlock = function (offset, length, file) {
let fileReader = new FileReader();
let blob = file.slice(offset, length + offset);
fileReader.onload = onLoadHandler;
fileReader.readAsArrayBuffer(blob);
};
readBlock(offset, chunkSize, file);
return;
}
const onFileSelected = async (e) => {
let image = e.target.files[0];
if (!image) return;
//console.log(image.type);
let start_request_payload = {
RandomAccessFilePut: image.type,
};
let nuri = "did:ng:"+$cur_tab.branch.nuri;
let start_res = await ng.app_request_with_nuri_command(nuri, "FilePut", $active_session.session_id, start_request_payload);
let upload_id = start_res.V0.FileUploading;
uploadFile(upload_id, nuri, image, async (reference) => {
if (reference) {
let file_put_payload = {
AddFile: {
filename: image.name,
object: reference.V0.FileUploaded,
},
};
await ng.app_request_with_nuri_command(nuri, "FilePut", $active_session.session_id, file_put_payload);
}
});
fileinput.value = "";
};
</script>
<div class="w-full">
<div class="row pt-2 w-full">
<Button
disabled={!$online && !is_tauri}
type="button"
on:click={() => {
fileinput.click();
}}
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mr-2 mb-2"
>
<ArrowUpTray class="w-8 h-8 mr-2 -ml-1"/>
{$t("doc.file.upload")}
</Button>
<input
style="display:none"
type="file"
on:change={(e) => onFileSelected(e)}
bind:this={fileinput}
/>
</div>
{#if upload_progress !== null}
<div class="mx-6 mt-2">
<Progressbar
progress={(
(100 * upload_progress.current) /
upload_progress.total
).toFixed(0)}
labelOutside={$t("doc.file.upload_progress")}
/>
</div>
{/if}
{#if commits}
{#await commits.load()}
<p>{$t("connectivity.loading")}...</p>
{:then}
{#each $commits.files as file}
<p class="mb-5">
{#await get_blob(file, true)}
<div class="ml-2">
<Spinner />
</div>
{:then url}
{#await isImage(url) then is}
{#if is}
<img src={url} title={file.nuri} alt={file.name} />
{/if}
{/await}
<span class="ml-2 text-gray-600">{file.name}<br/></span>
{#if url === false}
<span><ExclamationTriangle tabindex="-1" class="ml-2 w-6 h-8 focus:outline-none" style="display:inline"/>{$t("errors.cannot_load_this_file")}</span>
{:else if prepare_url(file.nuri)}
<a bind:this={file_urls[file.nuri].click}
href={file_urls[file.nuri].url || ""}
target="_blank"
download={file.name}
></a>
<button class="ml-2 select-none p-1 pb-0 text-gray-600" style="box-shadow:none;" on:click={()=>download(file)}>
<span><ArrowDownTray tabindex="-1" class="w-6 h-8 mr-3 focus:outline-none" style="display:inline"/>{$t("doc.file.download")}</span>
</button>
{/if}
{/await}
</p>
{/each}
{/await}
{/if}
</div>

@ -13,9 +13,6 @@
import {
branch_subscribe,
active_session,
cannot_load_offline,
online,
get_blob
} from "../../store";
import { get } from "svelte/store";
import { onMount, onDestroy, tick } from "svelte";
@ -36,7 +33,7 @@
import { t } from "svelte-i18n";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import { cur_tab, nav_bar, can_have_header, header_icon, header_title, header_description, cur_branch, set_header_in_view, edit_header_button, cur_app, load_official_app } from "../../tab";
import { cur_tab } from "../../tab";
import ng from "../../api";
import {
@ -139,7 +136,7 @@
{#if commit[1].final_consistency}<ShieldCheck tabindex="-1" class="w-5 h-5 absolute text-primary-600" style="top:9px;right:20px;" />
{:else if commit[1].signature}<ShieldCheck tabindex="-1" class="w-5 h-5 absolute text-green-600" style="top:9px;right:20px;" />
{/if}
<Icon tabindex="-1" class="w-5 h-5 outline-none absolute " style="top:9px;right:0px;" variation="outline" color="currentColor" icon={commit_type_icons[commit[1].commit_type]} />
<Icon tabindex="-1" class="w-5 h-5 focus:outline-none absolute " style="top:9px;right:0px;" variation="outline" color="currentColor" icon={commit_type_icons[commit[1].commit_type]} />
{#if commit[1].commit_type==="TransactionBoth"}<Cloud tabindex="-1" class="w-5 h-5 absolute " style="top:28px;right:0px;" />{/if}
<b>{commit[0].substring(0,7)}</b><br/>
<span class="text-xs leading-tight">{commit[1].author.substring(0,9)}</span>

@ -74,11 +74,6 @@
"description": "Auf diesen Gerät scheint es noch kein Wallet zu geben.<br />Wenn du bereits ein Wallet hast, wähle \"Anmelden\". Andernfalls wähle \"Wallet erstellen\".",
"create_wallet": "Wallet erstellen"
},
"test": {
"cannot_load_offline": "Du bist offline und nutzt die Web-App. Du musst dich mindestens ein mal mit dem Broker verbinden, bevor du die App lokal nutzen kannst. Das liegt daran, dass die Web-App keine lokalen Kopien deiner Dokumente speichern kann.<br /><br />Sobald du ein mal verbunden warst, wirst du auch bei Verbindungsabbruch zu einigen Funktionen Zugriff haben. Das Senden von Binärdateien ist dann nicht möglich, da dein Browser nur begrenzte Speicherkapazitäten von circa 5MB zur Verfügung stellt.<br /><br />Diese Einschränkungen fallen weg, sobald das Feature \"UserStorage for Web\" veröffentlicht wurde!<br /><br />Deinen Verbindungsstatus siehst du auf",
"add_image": "Bild hinzufügen",
"upload_progress": "Lade hoch..."
},
"login": {
"heading": "Wie öffente ich mein Wallet? Es gibt zwei Optionen:",
"with_pazzle": "Mit deinem Pazzle",
@ -329,7 +324,7 @@
"ServerError": "Server-Fehler.",
"InvalidResponse": "Ungültige Antwort erhalten.",
"BootstrapError": "Fehler beim Bootstrapping",
"NotAServerError": "Kein Server.",
"NotAServerError": "Kein Server-Fehler.",
"VerifierError": "Fehler während der Überprüfung.",
"SiteNotFoundOnBroker": "Die Seite kann nicht auf dem Broker gefunden werden",
"BrokerConfigErrorStr": "{error}",

@ -27,6 +27,11 @@
"chat": "Chat"
}
},
"file": {
"download": "Download",
"upload_progress": "Uploading...",
"upload": "Upload file"
},
"errors": {
"InvalidNuri": "Invalid NURI"
},
@ -273,11 +278,6 @@
"description": "We could not find a wallet saved on this device.<br /> If you already have a wallet, select \"Log in\", otherwise, select \"Create Wallet\" here below.",
"create_wallet": "Create Wallet"
},
"test": {
"cannot_load_offline": "You are offline and using the web app. You need to connect to the broker at least once before you can start using the app locally because the web app does not keep a local copy of your documents.<br /><br /> Once connected, if you lose connectivity again, you will be able to have limited access to some functionalities. Sending binary files won't be possible, because the limit of local storage in your browser is around 5MB.<br /><br /> All those limitations will be lifted once the \"UserStorage for Web\" feature will be released. Stay tuned! <br /><br /> Check your connection status in the ",
"add_image": "Add Image",
"upload_progress": "Uploading..."
},
"login": {
"heading": "How to open your wallet? You have 2 options:",
"with_pazzle": "With your Pazzle",
@ -550,7 +550,8 @@
"IncompatibleQrCode": "You scanned a NextGraph QR-Code that is of the wrong type.",
"NotARendezVous": "You scanned an invalid QR-Code.",
"camera_unavailable": "Camera is unavailable.",
"ServerAlreadyRunningInOtherProcess": "App is already running on this device. Check it and close it."
"ServerAlreadyRunningInOtherProcess": "App is already running on this device. Check it and close it.",
"cannot_load_this_file": "Cannot load this file"
},
"connectivity": {
"stopped": "Stopped",

@ -452,11 +452,11 @@ export const branch_subscribe = function(nuri:string, in_tab:boolean) {
//console.log("sub");
let already_subscribed = all_branches[nuri];
if (!already_subscribed) {