Merge pull request 'Upload / Download Progress' (#30) from feat/ng-app/progress-indicators into master

pull/31/head
Niko PLP 2 months ago
commit f8a2f6ebb4
  1. 50
      ng-app/src/lib/Login.svelte
  2. 98
      ng-app/src/lib/Test.svelte
  3. 11
      ng-app/src/lib/components/CopyToClipboard.svelte
  4. 11
      ng-app/src/lib/components/PasswordInput.svelte
  5. 38
      ng-app/src/lib/components/Spinner.svelte
  6. 3
      ng-app/src/locales/en.json
  7. 55
      ng-app/src/routes/WalletCreate.svelte
  8. 74
      ng-app/src/store.ts

@ -32,6 +32,7 @@
ArrowLeft,
} from "svelte-heros-v2";
import PasswordInput from "./components/PasswordInput.svelte";
import Spinner from "./components/Spinner.svelte";
//import Worker from "../worker.js?worker&inline";
export let wallet;
export let for_import = false;
@ -374,7 +375,9 @@
<!-- Save wallet? -->
{#if for_import}
<div class="max-w-xl lg:px-8 mx-auto px-4 mb-2">
<span class="text-xl">{$t("pages.wallet_create.save_wallet_options.trust")} </span> <br />
<span class="text-xl"
>{$t("pages.wallet_create.save_wallet_options.trust")}
</span> <br />
<p class="text-sm">
{$t("pages.wallet_create.save_wallet_options.trust_description")}
{#if !tauri_platform}
@ -384,6 +387,7 @@
<Toggle class="" bind:checked={trusted}
>{$t("pages.login.trust_device_yes")}</Toggle
>
<!-- Ask for Device Name -->
</div>
</div>
{/if}
@ -392,27 +396,7 @@
<div class="flex flex-col justify-centerspace-x-12 mt-4 mb-4">
{#if !loaded}
{$t("pages.login.loading_pazzle")}...
<svg
class="animate-spin my-4 h-14 w-14 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<Spinner className="my-4 h-14 w-14 mx-auto" />
{:else}
<button
on:click={start_with_pazzle}
@ -642,27 +626,7 @@
{:else if step == "opening"}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700">
{@html $t("pages.login.opening_wallet")}
<svg
class="animate-spin mt-10 h-14 w-14 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<Spinner className="mt-10 h-14 w-14 mx-auto" />
</div>
{:else if step == "end"}
{#if error}

@ -21,17 +21,19 @@
active_session,
cannot_load_offline,
online,
get_blob,
} from "../store";
import { link } from "svelte-spa-router";
import { onMount, onDestroy, tick } from "svelte";
import { Button } from "flowbite-svelte";
import { Button, Progressbar, Spinner } from "flowbite-svelte";
import DataClassIcon from "./DataClassIcon.svelte";
import { t } from "svelte-i18n";
let is_tauri = import.meta.env.TAURI_PLATFORM;
let files = $active_session && branch_subs($active_session.private_store_id);
let upload_progress: null | { total: number; current: number; error?: any } =
null;
let img_map = {};
let files = $active_session && branch_subs($active_session.private_store_id);
let gitgraph;
@ -526,76 +528,28 @@
// ]);
});
async function get_img(ref) {
if (!ref) return false;
let cache = img_map[ref.nuri];
if (cache) {
return cache;
}
let prom = new Promise(async (resolve) => {
try {
let nuri = {
target: "PrivateStore",
entire_store: false,
access: [{ Key: ref.reference.key }],
locator: [],
object: ref.reference.id,
};
let file_request = {
V0: {
command: "FileGet",
nuri,
session_id: $active_session.session_id,
},
};
let final_blob;
let content_type;
let unsub = await ng.app_request_stream(file_request, async (blob) => {
//console.log("GOT APP RESPONSE", blob);
if (blob.V0.FileMeta) {
content_type = blob.V0.FileMeta.content_type;
final_blob = new Blob([], { type: content_type });
} else if (blob.V0.FileBinary) {
if (blob.V0.FileBinary.byteLength > 0) {
final_blob = new Blob([final_blob, blob.V0.FileBinary], {
type: content_type,
});
}
} else if (blob.V0 == "EndOfStream") {
var imageUrl = URL.createObjectURL(final_blob);
resolve(imageUrl);
}
});
} catch (e) {
console.error(e);
resolve(false);
}
});
img_map[ref.nuri] = prom;
return prom;
}
let fileinput;
function uploadFile(upload_id, nuri, file, success) {
let chunkSize = 1048564;
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 += event.target.result.byteLength;
//console.log("chunk", event.target.result);
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,
event.target.result,
result,
nuri
);
//console.log("chunk upload res", res);
@ -609,6 +563,7 @@
return;
}
// If finished:
if (offset >= fileSize) {
//console.log("file uploaded");
let res = await ng.upload_chunk(
@ -619,8 +574,16 @@
);
//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;
}
@ -696,7 +659,7 @@
{#if $cannot_load_offline}
<div class="row p-4">
<p>
{$t("pages.text.cannot_load_offline")}
{@html $t("pages.test.cannot_load_offline")}
<a href="#/user">{$t("pages.user_panel.title")}</a>.
</p>
</div>
@ -747,6 +710,17 @@
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 files}
{#await files.load()}
<p>{$t("connectivity.loading")}...</p>
@ -755,7 +729,11 @@
<p>
{file.name}
{#await get_img(file) then url}
{#await get_blob(file)}
<div class="ml-2">
<Spinner />
</div>
{:then url}
{#if url}
<img src={url} title={file.nuri} alt={file.name} />
{/if}

@ -1,3 +1,14 @@
<!--
// 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">
export let value: string = "";
export let id: string;

@ -1,3 +1,14 @@
<!--
// 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">
export let value: string | undefined = undefined;
export let placeholder: string | undefined = undefined;

@ -0,0 +1,38 @@
<!--
// 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">
export let className: string = "";
export let color: string = "currentColor";
export let width: number = 24;
</script>
<svg
class={"animate-spin " + className}
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke={color}
viewBox={`0 0 ${width} ${width}`}
>
<circle
class="opacity-25"
cx={width / 2}
cy={width / 2}
r={width / 2.4}
stroke={color}
stroke-width={width / 6}
/>
<path
class="opacity-75"
fill={color}
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>

@ -63,7 +63,8 @@
},
"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"
"add_image": "Add Image",
"upload_progress": "Uploading..."
},
"login": {
"heading": "How to open your wallet? You have 2 options:",

@ -45,6 +45,7 @@
import { onMount, onDestroy, tick } from "svelte";
import { wallets, set_active_session, has_wallets } from "../store";
import Spinner from "../lib/components/Spinner.svelte";
const param = new URLSearchParams($querystring);
@ -528,27 +529,7 @@
{:else}
{wait}
{/if}
<svg
class="animate-spin mt-10 h-14 w-14 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<Spinner className="mt-10 h-14 w-14 mx-auto" />
</div>
{:else}
<div class="container3" bind:this={top}>
@ -1325,7 +1306,9 @@
bind:this={phrase}
class="mt-10 mr-0"
id="security-phrase-input"
placeholder={$t("pages.wallet_create.type_security_phrase_placeholder")}
placeholder={$t(
"pages.wallet_create.type_security_phrase_placeholder"
)}
bind:value={security_txt}
on:keypress={security_phrase_ok}
/><button on:click={async () => await security_phrase_ok()}>
@ -1375,7 +1358,8 @@
{:else}
<span class="font-semibold"
>{$t("pages.wallet_create.click_to_upload")}</span
> {$t("pages.wallet_create.or_drag_drop")}
>
{$t("pages.wallet_create.or_drag_drop")}
{/if}
</p>
<svg
@ -1440,6 +1424,7 @@
"pages.wallet_create.save_wallet_options.trust_toggle"
)}</Toggle
>
<!-- Device Name-->
</p>
<p class="max-w-xl md:mx-auto mt-10 lg:max-w-2xl text-left">
<span class="text-xl"
@ -1461,7 +1446,7 @@
)}</span
>.
<br /><br />
<Toggle disabled bind:checked={options.cloud}
<Toggle disabled bind:checked={options.cloud}
>{@html $t(
"pages.wallet_create.save_wallet_options.cloud_toggle"
)}</Toggle
@ -1535,27 +1520,7 @@
{#if !ready}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700">
{$t("pages.wallet_create.creating")}
<svg
class="animate-spin mt-10 h-6 w-6 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<Spinner className="mt-10 h-6 w-6 mx-auto" />
</div>
{:else}
<div class="text-left mx-4">

@ -24,23 +24,23 @@ let all_branches = {};
// Make sure that a file named `locales/<lang>.json` exists when adding it here.
export const available_languages = {
en: "English",
de: "Deutsch",
fr: "Français",
ru: "Русский",
es: "Español",
it: "Italiano",
zh: "中文",
pt: "Português",
en: "English",
de: "Deutsch",
fr: "Français",
ru: "Русский",
es: "Español",
it: "Italiano",
zh: "中文",
pt: "Português",
};
for (const lang of Object.keys(available_languages)) {
register(lang, () => import(`./locales/${lang}.json`))
register(lang, () => import(`./locales/${lang}.json`))
}
init({
fallbackLocale: "en",
initialLocale: "en",
fallbackLocale: "en",
initialLocale: "en",
});
export const select_default_lang = async () => {
@ -453,4 +453,56 @@ export const branch_subs = function(nuri) {
}
};
let blob_cache = {};
export async function get_blob(ref: { nuri: string | number; reference: { key: any; id: any; }; }) {
if (!ref) return false;
const cached = blob_cache[ref.nuri];
if (cached) {
return cached;
}
let prom = new Promise(async (resolve) => {
try {
let nuri = {
target: "PrivateStore",
entire_store: false,
access: [{ Key: ref.reference.key }],
locator: [],
object: ref.reference.id,
};
let file_request = {
V0: {
command: "FileGet",
nuri,
session_id: get(active_session).session_id,
},
};
let final_blob;
let content_type;
let unsub = await ng.app_request_stream(file_request, async (blob) => {
//console.log("GOT APP RESPONSE", blob);
if (blob.V0.FileMeta) {
content_type = blob.V0.FileMeta.content_type;
final_blob = new Blob([], { type: content_type });
} else if (blob.V0.FileBinary) {
if (blob.V0.FileBinary.byteLength > 0) {
final_blob = new Blob([final_blob, blob.V0.FileBinary], {
type: content_type,
});
}
} else if (blob.V0 == "EndOfStream") {
var imageUrl = URL.createObjectURL(final_blob);
resolve(imageUrl);
}
});
} catch (e) {
console.error(e);
resolve(false);
}
});
blob_cache[ref.nuri] = prom;
return prom;
}
//export default branch_commits;
Loading…
Cancel
Save