forked from akanyan/STARTLINER
feat: global progress bar
Also fix me having no foresight and executing things inside log::debug! macros
This commit is contained in:
1
TODO.md
1
TODO.md
@ -4,6 +4,5 @@
|
|||||||
|
|
||||||
### Long-term
|
### Long-term
|
||||||
|
|
||||||
- Progress bars and other GUI sugar
|
|
||||||
- artemis as a special package
|
- artemis as a special package
|
||||||
- Other arcade games (if there is demand)
|
- Other arcade games (if there is demand)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::{collections::HashSet, path::PathBuf};
|
use std::{collections::HashSet, path::PathBuf};
|
||||||
use futures::Stream;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::{AppHandle, Emitter};
|
use tauri::{AppHandle, Emitter};
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
@ -15,7 +14,7 @@ pub struct DownloadHandler {
|
|||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct DownloadTick {
|
pub struct DownloadTick {
|
||||||
pkg_key: PkgKey,
|
pkg_key: PkgKey,
|
||||||
ratio: f32
|
ratio: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DownloadHandler {
|
impl DownloadHandler {
|
||||||
@ -50,14 +49,15 @@ impl DownloadHandler {
|
|||||||
|
|
||||||
let mut cache_file_w = File::create(&zip_path_part).await?;
|
let mut cache_file_w = File::create(&zip_path_part).await?;
|
||||||
let mut byte_stream = reqwest::get(&rmt.download_url).await?.bytes_stream();
|
let mut byte_stream = reqwest::get(&rmt.download_url).await?.bytes_stream();
|
||||||
let first_hint = byte_stream.size_hint().0 as f32;
|
let mut total_bytes = 0;
|
||||||
|
|
||||||
log::info!("downloading: {}", rmt.download_url);
|
log::info!("downloading: {}", rmt.download_url);
|
||||||
while let Some(item) = byte_stream.next().await {
|
while let Some(item) = byte_stream.next().await {
|
||||||
let i = item?;
|
let i = item?;
|
||||||
app.emit("download-tick", DownloadTick {
|
total_bytes += i.len();
|
||||||
|
_ = app.emit("download-progress", DownloadTick {
|
||||||
pkg_key: pkg_key.clone(),
|
pkg_key: pkg_key.clone(),
|
||||||
ratio: 1.0f32 - (byte_stream.size_hint().0 as f32) / first_hint
|
ratio: (total_bytes as f32) / (rmt.file_size as f32),
|
||||||
})?;
|
})?;
|
||||||
cache_file_w.write_all(&mut i.as_ref()).await?;
|
cache_file_w.write_all(&mut i.as_ref()).await?;
|
||||||
}
|
}
|
||||||
|
@ -102,14 +102,15 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.listen("download-end", closure!(clone apph, |ev| {
|
app.listen("download-end", closure!(clone apph, |ev| {
|
||||||
log::debug!("download-end triggered: {}", ev.payload());
|
|
||||||
let raw = ev.payload();
|
let raw = ev.payload();
|
||||||
|
log::debug!("download-end triggered: {}", raw);
|
||||||
let key = PkgKey(raw[1..raw.len()-1].to_owned());
|
let key = PkgKey(raw[1..raw.len()-1].to_owned());
|
||||||
let apph = apph.clone();
|
let apph = apph.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let mutex = apph.state::<Mutex<AppData>>();
|
let mutex = apph.state::<Mutex<AppData>>();
|
||||||
let mut appd = mutex.lock().await;
|
let mut appd = mutex.lock().await;
|
||||||
log::debug!("download-end install {:?}", appd.pkgs.install_package(&key, true, false).await);
|
let res = appd.pkgs.install_package(&key, true, false).await;
|
||||||
|
log::debug!("download-end install {:?}", res);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -126,19 +127,21 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
app.listen("install-end-prelude", closure!(clone apph, |ev| {
|
app.listen("install-end-prelude", closure!(clone apph, |ev| {
|
||||||
log::debug!("install-end-prelude triggered: {}", ev.payload());
|
|
||||||
let payload = serde_json::from_str::<Payload>(ev.payload());
|
let payload = serde_json::from_str::<Payload>(ev.payload());
|
||||||
|
log::debug!("install-end-prelude triggered: {:?}", payload);
|
||||||
let apph = apph.clone();
|
let apph = apph.clone();
|
||||||
if let Ok(payload) = payload {
|
if let Ok(payload) = payload {
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let mutex = apph.state::<Mutex<AppData>>();
|
let mutex = apph.state::<Mutex<AppData>>();
|
||||||
let mut appd = mutex.lock().await;
|
let mut appd = mutex.lock().await;
|
||||||
|
let res = appd.toggle_package(payload.pkg.clone(), ToggleAction::EnableSelf);
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"install-end-prelude toggle {:?}",
|
"install-end-prelude toggle {:?}",
|
||||||
appd.toggle_package(payload.pkg.clone(), ToggleAction::EnableSelf)
|
res
|
||||||
);
|
);
|
||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
log::debug!("install-end {:?}", apph.emit("install-end", payload));
|
let res = apph.emit("install-end", payload);
|
||||||
|
log::debug!("install-end {:?}", res);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log::error!("install-end-prelude: invalid payload: {}", ev.payload());
|
log::error!("install-end-prelude: invalid payload: {}", ev.payload());
|
||||||
@ -232,7 +235,8 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
let mutex = app.state::<Mutex<AppData>>();
|
let mutex = app.state::<Mutex<AppData>>();
|
||||||
let appd = mutex.lock().await;
|
let appd = mutex.lock().await;
|
||||||
if let Some(p) = &appd.profile {
|
if let Some(p) = &appd.profile {
|
||||||
log::debug!("save: {:?}", p.save());
|
let res = p.save();
|
||||||
|
log::debug!("save: {:?}", res);
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -22,4 +22,5 @@ pub struct V1Version {
|
|||||||
pub icon: String,
|
pub icon: String,
|
||||||
pub dependencies: BTreeSet<PkgKeyVersion>,
|
pub dependencies: BTreeSet<PkgKeyVersion>,
|
||||||
pub download_url: String,
|
pub download_url: String,
|
||||||
|
pub file_size: i64,
|
||||||
}
|
}
|
@ -81,6 +81,7 @@ pub struct Remote {
|
|||||||
pub nsfw: bool,
|
pub nsfw: bool,
|
||||||
pub categories: Vec<String>,
|
pub categories: Vec<String>,
|
||||||
pub dependencies: BTreeSet<PkgKey>,
|
pub dependencies: BTreeSet<PkgKey>,
|
||||||
|
pub file_size: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PkgKey {
|
impl PkgKey {
|
||||||
@ -112,7 +113,8 @@ impl Package {
|
|||||||
nsfw: p.has_nsfw_content,
|
nsfw: p.has_nsfw_content,
|
||||||
version: v.version_number,
|
version: v.version_number,
|
||||||
categories: p.categories,
|
categories: p.categories,
|
||||||
dependencies: Self::sanitize_deps(v.dependencies)
|
dependencies: Self::sanitize_deps(v.dependencies),
|
||||||
|
file_size: v.file_size
|
||||||
}),
|
}),
|
||||||
source: PackageSource::Rainy,
|
source: PackageSource::Rainy,
|
||||||
})
|
})
|
||||||
|
@ -21,7 +21,7 @@ pub struct PackageStore {
|
|||||||
offline: bool,
|
offline: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
pub struct Payload {
|
pub struct Payload {
|
||||||
pub pkg: PkgKey
|
pub pkg: PkgKey
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,60 @@ listen<string>('launch-error', (event) => {
|
|||||||
errorMessage.value = event.payload;
|
errorMessage.value = event.payload;
|
||||||
errorHeader.value = 'Launch error';
|
errorHeader.value = 'Launch error';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
interface DownloadingStatus {
|
||||||
|
ratio: number;
|
||||||
|
pkg_key: string;
|
||||||
|
}
|
||||||
|
const downloading_status: Ref<DownloadingStatus[]> = ref([]);
|
||||||
|
|
||||||
|
const download_value = computed(() => {
|
||||||
|
return (
|
||||||
|
downloading_status.value.map((v) => v.ratio).reduce((a, v) => a * v) *
|
||||||
|
100
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const downloadProgressText = computed(() => {
|
||||||
|
if (download_value.value < 7) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
let pkgs = `${downloading_status.value.length} package${downloading_status.value.length === 1 ? '' : 's'}`;
|
||||||
|
if (download_value.value < 14) {
|
||||||
|
return pkgs;
|
||||||
|
} else {
|
||||||
|
return `${pkgs} (${Math.floor(download_value.value)}%)`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
listen<DownloadingStatus>('download-progress', (event) => {
|
||||||
|
let status = downloading_status.value.find(
|
||||||
|
(v) => v.pkg_key === event.payload.pkg_key
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status === undefined) {
|
||||||
|
status = {
|
||||||
|
ratio: 0,
|
||||||
|
pkg_key: event.payload.pkg_key,
|
||||||
|
};
|
||||||
|
downloading_status.value.push(status);
|
||||||
|
}
|
||||||
|
status.ratio = event.payload.ratio;
|
||||||
|
|
||||||
|
const remove = () => {
|
||||||
|
if (status !== undefined) {
|
||||||
|
downloading_status.value = downloading_status.value.filter(
|
||||||
|
(v) => v.pkg_key !== event.payload.pkg_key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (status.ratio === 1.0) {
|
||||||
|
remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => remove, 10_000);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -101,6 +155,16 @@ listen<string>('launch-error', (event) => {
|
|||||||
? 'main-scale-l'
|
? 'main-scale-l'
|
||||||
: 'main-scale-xl'
|
: 'main-scale-xl'
|
||||||
"
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="downloading_status.length > 0"
|
||||||
|
class="download-progress-bg"
|
||||||
|
></div>
|
||||||
|
<ProgressBar
|
||||||
|
v-if="downloading_status.length > 0"
|
||||||
|
:value="download_value"
|
||||||
|
class="download-progress"
|
||||||
|
>{{ downloadProgressText }}</ProgressBar
|
||||||
>
|
>
|
||||||
<ConfirmDialog>
|
<ConfirmDialog>
|
||||||
<template #message="{ message }">
|
<template #message="{ message }">
|
||||||
@ -353,4 +417,23 @@ body {
|
|||||||
.p-progressbar-label {
|
.p-progressbar-label {
|
||||||
transition-duration: 0s !important;
|
transition-duration: 0s !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.download-progress {
|
||||||
|
position: fixed !important;
|
||||||
|
bottom: 0;
|
||||||
|
left: 5vw;
|
||||||
|
width: 90vw;
|
||||||
|
z-index: 10000 !important;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
.download-progress-bg {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 60px;
|
||||||
|
background-color: var(--p-surface-900);
|
||||||
|
border-top: 1px solid var(--p-surface-600);
|
||||||
|
z-index: 998;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
import { invoke } from '../invoke';
|
import { invoke } from '../invoke';
|
||||||
import { usePkgStore } from '../stores';
|
import { usePkgStore } from '../stores';
|
||||||
@ -11,20 +12,26 @@ const props = defineProps({
|
|||||||
pkg: Object as () => Package,
|
pkg: Object as () => Package,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const deleting = ref(false);
|
||||||
|
|
||||||
const remove = async () => {
|
const remove = async () => {
|
||||||
if (props.pkg === undefined) {
|
if (props.pkg === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleting.value = true;
|
||||||
|
|
||||||
await invoke('delete_package', {
|
await invoke('delete_package', {
|
||||||
key: pkgKey(props.pkg),
|
key: pkgKey(props.pkg),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
deleting.value = false;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Button
|
<Button
|
||||||
v-if="pkg?.loc && !pkg?.js.busy"
|
v-if="pkg?.loc && !pkg?.js.downloading"
|
||||||
rounded
|
rounded
|
||||||
icon="pi pi-trash"
|
icon="pi pi-trash"
|
||||||
severity="danger"
|
severity="danger"
|
||||||
@ -32,7 +39,7 @@ const remove = async () => {
|
|||||||
size="small"
|
size="small"
|
||||||
class="self-center ml-4"
|
class="self-center ml-4"
|
||||||
style="width: 2rem; height: 2rem"
|
style="width: 2rem; height: 2rem"
|
||||||
:loading="pkg?.js.busy"
|
:loading="deleting"
|
||||||
v-on:click="remove()"
|
v-on:click="remove()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -45,7 +52,7 @@ const remove = async () => {
|
|||||||
size="small"
|
size="small"
|
||||||
class="self-center ml-4"
|
class="self-center ml-4"
|
||||||
style="width: 2rem; height: 2rem"
|
style="width: 2rem; height: 2rem"
|
||||||
:loading="pkg?.js.busy"
|
:loading="pkg?.js.downloading"
|
||||||
v-on:click="async () => await pkgs.install(pkg)"
|
v-on:click="async () => await pkgs.install(pkg)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -15,7 +15,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
const pkgs = usePkgStore();
|
const pkgs = usePkgStore();
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
const groupCallIndex = ref(0);
|
|
||||||
const empty = ref(false);
|
const empty = ref(false);
|
||||||
const gameSublist: Ref<string[]> = ref([]);
|
const gameSublist: Ref<string[]> = ref([]);
|
||||||
|
|
||||||
@ -46,10 +45,7 @@ const group = computed(() => {
|
|||||||
({ namespace }) => namespace
|
({ namespace }) => namespace
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if (groupCallIndex.value > 0) {
|
|
||||||
empty.value = Object.keys(res).length === 0;
|
empty.value = Object.keys(res).length === 0;
|
||||||
}
|
|
||||||
groupCallIndex.value += 1;
|
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -20,17 +20,15 @@ const install = async () => {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (props.pkg !== undefined) {
|
if (props.pkg !== undefined) {
|
||||||
props.pkg.js.busy = false;
|
props.pkg.js.downloading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (rv === 'Deferred') { /* download progress */ }
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Button
|
<Button
|
||||||
v-if="needsUpdate(pkg) && !pkg?.js.busy"
|
v-if="needsUpdate(pkg) && !pkg?.js.downloading"
|
||||||
rounded
|
rounded
|
||||||
icon="pi pi-download"
|
icon="pi pi-download"
|
||||||
severity="success"
|
severity="success"
|
||||||
|
@ -76,7 +76,7 @@ const themeModel = computed({
|
|||||||
>
|
>
|
||||||
<ToggleSwitch v-model="verboseModel" />
|
<ToggleSwitch v-model="verboseModel" />
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
<OptionRow title="Light theme">
|
<OptionRow title="Theme">
|
||||||
<SelectButton
|
<SelectButton
|
||||||
v-model="themeModel"
|
v-model="themeModel"
|
||||||
:options="[
|
:options="[
|
||||||
|
@ -119,13 +119,13 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
listen<InstallStatus>('install-start', async (ev) => {
|
listen<InstallStatus>('install-start', async (ev) => {
|
||||||
const key = ev.payload.pkg;
|
const key = ev.payload.pkg;
|
||||||
await this.reload(key);
|
await this.reload(key);
|
||||||
this.pkg[key].js.busy = true;
|
this.pkg[key].js.downloading = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
listen<InstallStatus>('install-end', async (ev) => {
|
listen<InstallStatus>('install-end', async (ev) => {
|
||||||
const key = ev.payload.pkg;
|
const key = ev.payload.pkg;
|
||||||
await this.reload(key);
|
await this.reload(key);
|
||||||
this.pkg[key].js.busy = false;
|
this.pkg[key].js.downloading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -152,17 +152,22 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
|
|
||||||
async reloadWith(key: string, pkg: Package) {
|
async reloadWith(key: string, pkg: Package) {
|
||||||
if (this.pkg[key] === undefined) {
|
if (this.pkg[key] === undefined) {
|
||||||
this.pkg[key] = { js: { busy: false } } as Package;
|
this.pkg[key] = { js: { downloading: false } } as Package;
|
||||||
} else {
|
} else {
|
||||||
this.pkg[key].loc = null;
|
this.pkg[key].loc = null;
|
||||||
this.pkg[key].rmt = null;
|
this.pkg[key].rmt = null;
|
||||||
}
|
}
|
||||||
Object.assign(this.pkg[key], pkg);
|
Object.assign(this.pkg[key], pkg);
|
||||||
|
|
||||||
|
if (!pkg.js) {
|
||||||
|
pkg.js = { downloading: false };
|
||||||
|
}
|
||||||
|
|
||||||
if (pkg.rmt !== null) {
|
if (pkg.rmt !== null) {
|
||||||
pkg.rmt.categories.forEach((c) =>
|
pkg.rmt.categories.forEach((c) =>
|
||||||
this.availableCategories.add(c)
|
this.availableCategories.add(c)
|
||||||
);
|
);
|
||||||
|
pkg.js.downloading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -193,9 +198,8 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
force: true,
|
force: true,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
|
||||||
if (pkg !== undefined) {
|
if (pkg !== undefined) {
|
||||||
pkg.js.busy = false;
|
pkg.js.downloading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -19,7 +19,7 @@ export interface Package {
|
|||||||
icon: string;
|
icon: string;
|
||||||
} | null;
|
} | null;
|
||||||
js: {
|
js: {
|
||||||
busy: boolean;
|
downloading: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user