Upload / Download Progress #30

Merged
niko merged 1 commits from feat/ng-app/progress-indicators into master 6 months ago
  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, ArrowLeft,
} from "svelte-heros-v2"; } from "svelte-heros-v2";
import PasswordInput from "./components/PasswordInput.svelte"; import PasswordInput from "./components/PasswordInput.svelte";
import Spinner from "./components/Spinner.svelte";
//import Worker from "../worker.js?worker&inline"; //import Worker from "../worker.js?worker&inline";
export let wallet; export let wallet;
export let for_import = false; export let for_import = false;
@ -374,7 +375,9 @@
<!-- Save wallet? --> <!-- Save wallet? -->
{#if for_import} {#if for_import}
<div class="max-w-xl lg:px-8 mx-auto px-4 mb-2"> <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"> <p class="text-sm">
{$t("pages.wallet_create.save_wallet_options.trust_description")} {$t("pages.wallet_create.save_wallet_options.trust_description")}
{#if !tauri_platform} {#if !tauri_platform}
@ -384,6 +387,7 @@
<Toggle class="" bind:checked={trusted} <Toggle class="" bind:checked={trusted}
>{$t("pages.login.trust_device_yes")}</Toggle >{$t("pages.login.trust_device_yes")}</Toggle
> >
<!-- Ask for Device Name -->
</div> </div>
</div> </div>
{/if} {/if}
@ -392,27 +396,7 @@
<div class="flex flex-col justify-centerspace-x-12 mt-4 mb-4"> <div class="flex flex-col justify-centerspace-x-12 mt-4 mb-4">
{#if !loaded} {#if !loaded}
{$t("pages.login.loading_pazzle")}... {$t("pages.login.loading_pazzle")}...
<svg <Spinner className="my-4 h-14 w-14 mx-auto" />
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>
{:else} {:else}
<button <button
on:click={start_with_pazzle} on:click={start_with_pazzle}
@ -642,27 +626,7 @@
{:else if step == "opening"} {:else if step == "opening"}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700"> <div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700">
{@html $t("pages.login.opening_wallet")} {@html $t("pages.login.opening_wallet")}
<svg <Spinner className="mt-10 h-14 w-14 mx-auto" />
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>
</div> </div>
{:else if step == "end"} {:else if step == "end"}
{#if error} {#if error}

@ -21,17 +21,19 @@
active_session, active_session,
cannot_load_offline, cannot_load_offline,
online, online,
get_blob,
} from "../store"; } from "../store";
import { link } from "svelte-spa-router"; import { link } from "svelte-spa-router";
import { onMount, onDestroy, tick } from "svelte"; import { onMount, onDestroy, tick } from "svelte";
import { Button } from "flowbite-svelte"; import { Button, Progressbar, Spinner } from "flowbite-svelte";
import DataClassIcon from "./DataClassIcon.svelte"; import DataClassIcon from "./DataClassIcon.svelte";
import { t } from "svelte-i18n"; import { t } from "svelte-i18n";
let is_tauri = import.meta.env.TAURI_PLATFORM; let is_tauri = import.meta.env.TAURI_PLATFORM;
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; 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; let fileinput;
function uploadFile(upload_id, nuri, file, success) { function uploadFile(upload_id, nuri, file, success) {
let chunkSize = 1048564; let chunkSize = 1_048_564;
let fileSize = file.size; let fileSize = file.size;
let offset = 0; let offset = 0;
let readBlock = null; let readBlock = null;
upload_progress = { total: fileSize, current: offset };
let onLoadHandler = async function (event) { let onLoadHandler = async function (event) {
let result = event.target.result; let result = event.target.result;
if (event.target.error == null) { if (event.target.error == null) {
offset += event.target.result.byteLength; offset += result.byteLength;
//console.log("chunk", event.target.result); upload_progress = { total: fileSize, current: offset };
// console.log("chunk", result);
let res = await ng.upload_chunk( let res = await ng.upload_chunk(
$active_session.session_id, $active_session.session_id,
upload_id, upload_id,
event.target.result, result,
nuri nuri
); );
//console.log("chunk upload res", res); //console.log("chunk upload res", res);
@ -609,6 +563,7 @@
return; return;
} }
// If finished:
if (offset >= fileSize) { if (offset >= fileSize) {
//console.log("file uploaded"); //console.log("file uploaded");
let res = await ng.upload_chunk( let res = await ng.upload_chunk(
@ -619,8 +574,16 @@
); );
//console.log("end upload res", res); //console.log("end upload res", res);
if (success) { if (success) {
upload_progress = { total: fileSize, current: fileSize };
success(res); success(res);
} else {
upload_progress = { total: fileSize, current: fileSize, error: true };
} }
// Make progress bar disappear
setTimeout(() => {
upload_progress = null;
}, 2_500);
return; return;
} }
@ -696,7 +659,7 @@
{#if $cannot_load_offline} {#if $cannot_load_offline}
<div class="row p-4"> <div class="row p-4">
<p> <p>
{$t("pages.text.cannot_load_offline")} {@html $t("pages.test.cannot_load_offline")}
<a href="#/user">{$t("pages.user_panel.title")}</a>. <a href="#/user">{$t("pages.user_panel.title")}</a>.
</p> </p>
</div> </div>
@ -747,6 +710,17 @@
bind:this={fileinput} bind:this={fileinput}
/> />
</div> </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} {#if files}
{#await files.load()} {#await files.load()}
<p>{$t("connectivity.loading")}...</p> <p>{$t("connectivity.loading")}...</p>
@ -755,7 +729,11 @@
<p> <p>
{file.name} {file.name}
{#await get_img(file) then url} {#await get_blob(file)}
<div class="ml-2">
<Spinner />
</div>
{:then url}
{#if url} {#if url}
<img src={url} title={file.nuri} alt={file.name} /> <img src={url} title={file.nuri} alt={file.name} />
{/if} {/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"> <script lang="ts">
export let value: string = ""; export let value: string = "";
export let id: 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"> <script lang="ts">
export let value: string | undefined = undefined; export let value: string | undefined = undefined;
export let placeholder: 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": { "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 ", "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": { "login": {
"heading": "How to open your wallet? You have 2 options:", "heading": "How to open your wallet? You have 2 options:",

@ -45,6 +45,7 @@
import { onMount, onDestroy, tick } from "svelte"; import { onMount, onDestroy, tick } from "svelte";
import { wallets, set_active_session, has_wallets } from "../store"; import { wallets, set_active_session, has_wallets } from "../store";
import Spinner from "../lib/components/Spinner.svelte";
const param = new URLSearchParams($querystring); const param = new URLSearchParams($querystring);
@ -528,27 +529,7 @@
{:else} {:else}
{wait} {wait}
{/if} {/if}
<svg <Spinner className="mt-10 h-14 w-14 mx-auto" />
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>
</div> </div>
{:else} {:else}
<div class="container3" bind:this={top}> <div class="container3" bind:this={top}>
@ -1325,7 +1306,9 @@
bind:this={phrase} bind:this={phrase}
class="mt-10 mr-0" class="mt-10 mr-0"
id="security-phrase-input" 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} bind:value={security_txt}
on:keypress={security_phrase_ok} on:keypress={security_phrase_ok}
/><button on:click={async () => await security_phrase_ok()}> /><button on:click={async () => await security_phrase_ok()}>
@ -1375,7 +1358,8 @@
{:else} {:else}
<span class="font-semibold" <span class="font-semibold"
>{$t("pages.wallet_create.click_to_upload")}</span >{$t("pages.wallet_create.click_to_upload")}</span
> {$t("pages.wallet_create.or_drag_drop")} >
{$t("pages.wallet_create.or_drag_drop")}
{/if} {/if}
</p> </p>
<svg <svg
@ -1440,6 +1424,7 @@
"pages.wallet_create.save_wallet_options.trust_toggle" "pages.wallet_create.save_wallet_options.trust_toggle"
)}</Toggle )}</Toggle
> >
<!-- Device Name-->
</p> </p>
<p class="max-w-xl md:mx-auto mt-10 lg:max-w-2xl text-left"> <p class="max-w-xl md:mx-auto mt-10 lg:max-w-2xl text-left">
<span class="text-xl" <span class="text-xl"
@ -1461,7 +1446,7 @@
)}</span )}</span
>. >.
<br /><br /> <br /><br />
<Toggle disabled bind:checked={options.cloud} <Toggle disabled bind:checked={options.cloud}
>{@html $t( >{@html $t(
"pages.wallet_create.save_wallet_options.cloud_toggle" "pages.wallet_create.save_wallet_options.cloud_toggle"
)}</Toggle )}</Toggle
@ -1535,27 +1520,7 @@
{#if !ready} {#if !ready}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700"> <div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700">
{$t("pages.wallet_create.creating")} {$t("pages.wallet_create.creating")}
<svg <Spinner className="mt-10 h-6 w-6 mx-auto" />
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>
</div> </div>
{:else} {:else}
<div class="text-left mx-4"> <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. // Make sure that a file named `locales/<lang>.json` exists when adding it here.
export const available_languages = { export const available_languages = {
en: "English", en: "English",
de: "Deutsch", de: "Deutsch",
fr: "Français", fr: "Français",
ru: "Русский", ru: "Русский",
es: "Español", es: "Español",
it: "Italiano", it: "Italiano",
zh: "中文", zh: "中文",
pt: "Português", pt: "Português",
}; };
for (const lang of Object.keys(available_languages)) { for (const lang of Object.keys(available_languages)) {
register(lang, () => import(`./locales/${lang}.json`)) register(lang, () => import(`./locales/${lang}.json`))
} }
init({ init({
fallbackLocale: "en", fallbackLocale: "en",
initialLocale: "en", initialLocale: "en",
}); });
export const select_default_lang = async () => { 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; //export default branch_commits;
Loading…
Cancel
Save