mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-07-23 00:12:53 +10:00
feat(download ui): add speed and time remaining information
closes #7 Co-authored-by: AdenMGB <140392385+AdenMGB@users.noreply.github.com>
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import "~/composables/queue";
|
import "~/composables/downloads.js";
|
||||||
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { AppStatus } from "~/types";
|
import { AppStatus } from "~/types";
|
||||||
|
|||||||
@@ -5,10 +5,23 @@ export type QueueState = {
|
|||||||
status: string;
|
status: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type StatsState = {
|
||||||
|
speed: number; // Bytes per second
|
||||||
|
time: number; // Seconds,
|
||||||
|
};
|
||||||
|
|
||||||
export const useQueueState = () =>
|
export const useQueueState = () =>
|
||||||
useState<QueueState>("queue", () => ({ queue: [], status: "Unknown" }));
|
useState<QueueState>("queue", () => ({ queue: [], status: "Unknown" }));
|
||||||
|
|
||||||
|
export const useStatsState = () =>
|
||||||
|
useState<StatsState>("stats", () => ({ speed: 0, time: 0 }));
|
||||||
|
|
||||||
listen("update_queue", (event) => {
|
listen("update_queue", (event) => {
|
||||||
const queue = useQueueState();
|
const queue = useQueueState();
|
||||||
queue.value = event.payload as QueueState;
|
queue.value = event.payload as QueueState;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
listen("update_stats", (event) => {
|
||||||
|
const stats = useStatsState();
|
||||||
|
stats.value = event.payload as StatsState;
|
||||||
|
});
|
||||||
@@ -21,7 +21,6 @@
|
|||||||
"@tauri-apps/plugin-os": "~2",
|
"@tauri-apps/plugin-os": "~2",
|
||||||
"@tauri-apps/plugin-shell": ">=2.0.0",
|
"@tauri-apps/plugin-shell": ">=2.0.0",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"moment": "^2.30.1",
|
|
||||||
"nuxt": "^3.13.0",
|
"nuxt": "^3.13.0",
|
||||||
"scss": "^0.2.4",
|
"scss": "^0.2.4",
|
||||||
"vue": "latest",
|
"vue": "latest",
|
||||||
|
|||||||
+97
-30
@@ -1,18 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-zinc-950 p-4 min-h-full">
|
<div class="bg-zinc-950 p-4 min-h-full space-y-4">
|
||||||
|
<div class="h-16 overflow-hidden relative rounded-xl flex flex-row border border-zinc-900">
|
||||||
|
<div
|
||||||
|
class="bg-zinc-900 z-10 w-32 flex flex-col gap-x-2 text-blue-400 font-display items-left justify-center pl-2">
|
||||||
|
<span class="font-semibold">{{ formatKilobytes(stats.speed) }}</span>
|
||||||
|
<span v-if="stats.time > 0" class="text-sm">{{ formatTime(stats.time) }} left</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="absolute inset-0 h-full flex flex-row items-end justify-end">
|
||||||
|
<div v-for="bar in speedHistory" :style="{ height: `${bar / speedMax * 100}%` }"
|
||||||
|
class="w-[8px] bg-blue-600/40" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<draggable v-model="queue.queue" @end="onEnd">
|
<draggable v-model="queue.queue" @end="onEnd">
|
||||||
<template #item="{ element }: { element: (typeof queue.value.queue)[0] }">
|
<template #item="{ element }: { element: (typeof queue.value.queue)[0] }">
|
||||||
<li
|
<li v-if="games[element.id]" :key="element.id"
|
||||||
v-if="games[element.id]"
|
class="mb-4 bg-zinc-900 rounded-lg flex flex-row justify-between gap-x-6 py-5 px-4">
|
||||||
:key="element.id"
|
|
||||||
class="mb-4 bg-zinc-900 rounded-lg flex flex-row justify-between gap-x-6 py-5 px-4"
|
|
||||||
>
|
|
||||||
<div class="w-full flex items-center max-w-md gap-x-4 relative">
|
<div class="w-full flex items-center max-w-md gap-x-4 relative">
|
||||||
<img
|
<img class="size-24 flex-none bg-zinc-800 object-cover rounded" :src="games[element.id].cover" alt="" />
|
||||||
class="size-24 flex-none bg-zinc-800 object-cover rounded"
|
|
||||||
:src="games[element.id].cover"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
<div class="min-w-0 flex-auto">
|
<div class="min-w-0 flex-auto">
|
||||||
<p class="text-xl font-semibold text-zinc-100">
|
<p class="text-xl font-semibold text-zinc-100">
|
||||||
<NuxtLink :href="`/library/${element.id}`" class="">
|
<NuxtLink :href="`/library/${element.id}`" class="">
|
||||||
@@ -30,31 +35,20 @@
|
|||||||
<p class="text-md text-zinc-500 uppercase font-display font-bold">
|
<p class="text-md text-zinc-500 uppercase font-display font-bold">
|
||||||
{{ element.status }}
|
{{ element.status }}
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div v-if="element.progress" class="mt-1 w-96 bg-zinc-800 rounded-lg overflow-hidden">
|
||||||
v-if="element.progress"
|
<div class="h-2 bg-blue-600" :style="{ width: `${element.progress * 100}%` }" />
|
||||||
class="mt-1 w-96 bg-zinc-800 rounded-lg overflow-hidden"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="h-2 bg-blue-600"
|
|
||||||
:style="{ width: `${element.progress * 100}%` }"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button @click="() => cancelGame(element.id)" class="group">
|
<button @click="() => cancelGame(element.id)" class="group">
|
||||||
<XMarkIcon
|
<XMarkIcon class="transition size-8 flex-none text-zinc-600 group-hover:text-zinc-300"
|
||||||
class="transition size-8 flex-none text-zinc-600 group-hover:text-zinc-300"
|
aria-hidden="true" />
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<p v-else>Loading...</p>
|
<p v-else>Loading...</p>
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
<div
|
<div class="text-zinc-600 uppercase font-semibold font-display w-full text-center" v-if="queue.queue.length == 0">
|
||||||
class="text-zinc-600 uppercase font-semibold font-display w-full text-center"
|
|
||||||
v-if="queue.queue.length == 0"
|
|
||||||
>
|
|
||||||
No items in the queue
|
No items in the queue
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,19 +59,64 @@ import { XMarkIcon } from "@heroicons/vue/20/solid";
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import type { Game, GameStatus } from "~/types";
|
import type { Game, GameStatus } from "~/types";
|
||||||
|
|
||||||
const queue = useQueueState();
|
const windowWidth = ref(window.innerWidth);
|
||||||
|
window.addEventListener('resize', (event) => {
|
||||||
|
windowWidth.value = window.innerWidth;
|
||||||
|
})
|
||||||
|
|
||||||
const current = computed(() => queue.value.queue.at(0));
|
const queue = useQueueState();
|
||||||
const rest = computed(() => queue.value.queue.slice(1));
|
const stats = useStatsState();
|
||||||
|
const speedHistory = useState<Array<number>>(() => []);
|
||||||
|
const speedHistoryMax = computed(() => windowWidth.value / 8);
|
||||||
|
const speedMax = computed(() => speedHistory.value.reduce((a, b) => a > b ? a : b) * 1.3);
|
||||||
|
const previousGameId = ref<string | undefined>();
|
||||||
|
|
||||||
const games: Ref<{
|
const games: Ref<{
|
||||||
[key: string]: { game: Game; status: Ref<GameStatus>; cover: string };
|
[key: string]: { game: Game; status: Ref<GameStatus>; cover: string };
|
||||||
}> = ref({});
|
}> = ref({});
|
||||||
|
|
||||||
|
|
||||||
|
function resetHistoryGraph() {
|
||||||
|
speedHistory.value = [];
|
||||||
|
stats.value = { time: 0, speed: 0 };
|
||||||
|
}
|
||||||
|
function checkReset(v: QueueState) {
|
||||||
|
const currentGame = v.queue.at(0);
|
||||||
|
// If we're finished
|
||||||
|
if (!currentGame && previousGameId.value) {
|
||||||
|
previousGameId.value = undefined;
|
||||||
|
resetHistoryGraph();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If we don't have a game
|
||||||
|
if (!currentGame) return;
|
||||||
|
// If we started a new download
|
||||||
|
if (currentGame && !previousGameId.value) {
|
||||||
|
previousGameId.value = currentGame.id;
|
||||||
|
resetHistoryGraph();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If it's a different game now
|
||||||
|
if (currentGame.id != previousGameId.value
|
||||||
|
) {
|
||||||
|
previousGameId.value = currentGame.id;
|
||||||
|
resetHistoryGraph();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
watch(queue, (v) => {
|
watch(queue, (v) => {
|
||||||
loadGamesForQueue(v);
|
loadGamesForQueue(v);
|
||||||
|
checkReset(v);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(stats, (v) => {
|
||||||
|
const newLength = speedHistory.value.push(v.speed);
|
||||||
|
if (newLength > speedHistoryMax.value) {
|
||||||
|
speedHistory.value.splice(0, 1);
|
||||||
|
}
|
||||||
|
checkReset(queue.value);
|
||||||
|
})
|
||||||
|
|
||||||
function loadGamesForQueue(v: typeof queue.value) {
|
function loadGamesForQueue(v: typeof queue.value) {
|
||||||
for (const { id } of v.queue) {
|
for (const { id } of v.queue) {
|
||||||
if (games.value[id]) return;
|
if (games.value[id]) return;
|
||||||
@@ -101,4 +140,32 @@ async function onEnd(event: { oldIndex: number; newIndex: number }) {
|
|||||||
async function cancelGame(id: string) {
|
async function cancelGame(id: string) {
|
||||||
await invoke("cancel_game", { gameId: id });
|
await invoke("cancel_game", { gameId: id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatKilobytes(bytes: number): string {
|
||||||
|
const units = ["KB", "MB", "GB", "TB", "PB"];
|
||||||
|
let value = bytes;
|
||||||
|
let unitIndex = 0;
|
||||||
|
const scalar = 1000;
|
||||||
|
|
||||||
|
while (value >= scalar && unitIndex < units.length - 1) {
|
||||||
|
value /= scalar;
|
||||||
|
unitIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${value.toFixed(1)} ${units[unitIndex]}/s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(seconds: number): string {
|
||||||
|
if (seconds < 60) {
|
||||||
|
return `${Math.round(seconds)}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
if (minutes < 60) {
|
||||||
|
return `${minutes}m ${Math.round(seconds % 60)}s`
|
||||||
|
}
|
||||||
|
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
return `${hours}h ${minutes % 60}m`;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ pub enum DownloadManagerSignal {
|
|||||||
/// Any error which occurs in the agent
|
/// Any error which occurs in the agent
|
||||||
Error(GameDownloadError),
|
Error(GameDownloadError),
|
||||||
/// Pushes UI update
|
/// Pushes UI update
|
||||||
Update,
|
UpdateUIQueue,
|
||||||
|
UpdateUIStats(usize, usize), //kb/s and seconds
|
||||||
/// Uninstall game
|
/// Uninstall game
|
||||||
/// Takes game ID
|
/// Takes game ID
|
||||||
Uninstall(String),
|
Uninstall(String),
|
||||||
@@ -156,7 +157,7 @@ impl DownloadManager {
|
|||||||
let to_move = queue.remove(current_index).unwrap();
|
let to_move = queue.remove(current_index).unwrap();
|
||||||
queue.insert(new_index, to_move);
|
queue.insert(new_index, to_move);
|
||||||
self.command_sender
|
self.command_sender
|
||||||
.send(DownloadManagerSignal::Update)
|
.send(DownloadManagerSignal::UpdateUIQueue)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
pub fn cancel(&self, game_id: String) {
|
pub fn cancel(&self, game_id: String) {
|
||||||
@@ -188,7 +189,7 @@ impl DownloadManager {
|
|||||||
self.command_sender.send(DownloadManagerSignal::Go).unwrap();
|
self.command_sender.send(DownloadManagerSignal::Go).unwrap();
|
||||||
}
|
}
|
||||||
self.command_sender
|
self.command_sender
|
||||||
.send(DownloadManagerSignal::Update)
|
.send(DownloadManagerSignal::UpdateUIQueue)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
pub fn pause_downloads(&self) {
|
pub fn pause_downloads(&self) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use crate::{
|
|||||||
db::{Database, GameStatus, GameTransientStatus},
|
db::{Database, GameStatus, GameTransientStatus},
|
||||||
library::{
|
library::{
|
||||||
on_game_complete, push_game_update, GameUpdateEvent, QueueUpdateEvent,
|
on_game_complete, push_game_update, GameUpdateEvent, QueueUpdateEvent,
|
||||||
QueueUpdateEventQueueData,
|
QueueUpdateEventQueueData, StatsUpdateEvent,
|
||||||
},
|
},
|
||||||
state::{GameStatusManager, GameStatusWithTransient},
|
state::{GameStatusManager, GameStatusWithTransient},
|
||||||
DB,
|
DB,
|
||||||
@@ -128,7 +128,13 @@ impl DownloadManagerBuilder {
|
|||||||
push_game_update(&self.app_handle, id, status);
|
push_game_update(&self.app_handle, id, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_manager_update(&self) {
|
fn push_ui_stats_update(&self, kbs: usize, time: usize) {
|
||||||
|
let event_data = StatsUpdateEvent { speed: kbs, time };
|
||||||
|
|
||||||
|
self.app_handle.emit("update_stats", event_data).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_ui_queue_update(&self) {
|
||||||
let queue = self.download_queue.read();
|
let queue = self.download_queue.read();
|
||||||
let queue_objs: Vec<QueueUpdateEventQueueData> = queue
|
let queue_objs: Vec<QueueUpdateEventQueueData> = queue
|
||||||
.iter()
|
.iter()
|
||||||
@@ -208,8 +214,11 @@ impl DownloadManagerBuilder {
|
|||||||
DownloadManagerSignal::Cancel => {
|
DownloadManagerSignal::Cancel => {
|
||||||
self.manage_cancel_signal();
|
self.manage_cancel_signal();
|
||||||
}
|
}
|
||||||
DownloadManagerSignal::Update => {
|
DownloadManagerSignal::UpdateUIQueue => {
|
||||||
self.push_manager_update();
|
self.push_ui_queue_update();
|
||||||
|
}
|
||||||
|
DownloadManagerSignal::UpdateUIStats(kbs, time) => {
|
||||||
|
self.push_ui_stats_update(kbs, time);
|
||||||
}
|
}
|
||||||
DownloadManagerSignal::Finish => {
|
DownloadManagerSignal::Finish => {
|
||||||
self.stop_and_wait_current_download();
|
self.stop_and_wait_current_download();
|
||||||
@@ -315,7 +324,7 @@ impl DownloadManagerBuilder {
|
|||||||
self.manage_go_signal();
|
self.manage_go_signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.push_manager_update();
|
self.push_ui_queue_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manage_stop_signal(&mut self) {
|
fn manage_stop_signal(&mut self) {
|
||||||
@@ -356,7 +365,9 @@ impl DownloadManagerBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.sender.send(DownloadManagerSignal::Update).unwrap();
|
self.sender
|
||||||
|
.send(DownloadManagerSignal::UpdateUIQueue)
|
||||||
|
.unwrap();
|
||||||
self.sender.send(DownloadManagerSignal::Go).unwrap();
|
self.sender.send(DownloadManagerSignal::Go).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +417,9 @@ impl DownloadManagerBuilder {
|
|||||||
GameTransientStatus::Downloading { version_name },
|
GameTransientStatus::Downloading { version_name },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
self.sender.send(DownloadManagerSignal::Update).unwrap();
|
self.sender
|
||||||
|
.send(DownloadManagerSignal::UpdateUIQueue)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manage_go_signal(&mut self) {
|
fn manage_go_signal(&mut self) {
|
||||||
@@ -483,7 +496,9 @@ impl DownloadManagerBuilder {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.sender.send(DownloadManagerSignal::Update).unwrap();
|
self.sender
|
||||||
|
.send(DownloadManagerSignal::UpdateUIQueue)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
fn manage_error_signal(&mut self, error: GameDownloadError) {
|
fn manage_error_signal(&mut self, error: GameDownloadError) {
|
||||||
let current_status = self.current_download_agent.clone().unwrap();
|
let current_status = self.current_download_agent.clone().unwrap();
|
||||||
@@ -504,7 +519,9 @@ impl DownloadManagerBuilder {
|
|||||||
db_handle.games.transient_statuses.remove(id);
|
db_handle.games.transient_statuses.remove(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.sender.send(DownloadManagerSignal::Update).unwrap();
|
self.sender
|
||||||
|
.send(DownloadManagerSignal::UpdateUIQueue)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
fn manage_cancel_signal(&mut self) {
|
fn manage_cancel_signal(&mut self) {
|
||||||
self.stop_and_wait_current_download();
|
self.stop_and_wait_current_download();
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ use std::{
|
|||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicUsize, Ordering},
|
atomic::{AtomicUsize, Ordering},
|
||||||
mpsc::Sender,
|
mpsc::Sender,
|
||||||
Arc, Mutex,
|
Arc, Mutex, RwLock,
|
||||||
},
|
},
|
||||||
time::Instant,
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
@@ -20,6 +20,8 @@ pub struct ProgressObject {
|
|||||||
|
|
||||||
points_towards_update: Arc<AtomicUsize>,
|
points_towards_update: Arc<AtomicUsize>,
|
||||||
points_to_push_update: Arc<AtomicUsize>,
|
points_to_push_update: Arc<AtomicUsize>,
|
||||||
|
last_update: Arc<RwLock<Instant>>,
|
||||||
|
amount_last_update: Arc<AtomicUsize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProgressHandle {
|
pub struct ProgressHandle {
|
||||||
@@ -59,6 +61,8 @@ impl ProgressObject {
|
|||||||
|
|
||||||
points_towards_update: Arc::new(AtomicUsize::new(0)),
|
points_towards_update: Arc::new(AtomicUsize::new(0)),
|
||||||
points_to_push_update: Arc::new(AtomicUsize::new(points_to_push_update)),
|
points_to_push_update: Arc::new(AtomicUsize::new(points_to_push_update)),
|
||||||
|
last_update: Arc::new(RwLock::new(Instant::now())),
|
||||||
|
amount_last_update: Arc::new(AtomicUsize::new(0)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,12 +73,42 @@ impl ProgressObject {
|
|||||||
|
|
||||||
let to_update = self.points_to_push_update.fetch_add(0, Ordering::Relaxed);
|
let to_update = self.points_to_push_update.fetch_add(0, Ordering::Relaxed);
|
||||||
|
|
||||||
if current_amount < to_update {
|
if current_amount >= to_update {
|
||||||
return;
|
self.points_towards_update
|
||||||
|
.fetch_sub(to_update, Ordering::Relaxed);
|
||||||
|
self.sender
|
||||||
|
.send(DownloadManagerSignal::UpdateUIQueue)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_update = self.last_update.read().unwrap();
|
||||||
|
let last_update_difference = Instant::now().duration_since(*last_update).as_millis();
|
||||||
|
if last_update_difference > 1000 {
|
||||||
|
// push update
|
||||||
|
drop(last_update);
|
||||||
|
let mut last_update = self.last_update.write().unwrap();
|
||||||
|
*last_update = Instant::now();
|
||||||
|
drop(last_update);
|
||||||
|
|
||||||
|
let current_amount = self.sum();
|
||||||
|
let max = self.get_max();
|
||||||
|
let amount_at_last_update = self.amount_last_update.fetch_add(0, Ordering::Relaxed);
|
||||||
|
self.amount_last_update
|
||||||
|
.store(current_amount, Ordering::Relaxed);
|
||||||
|
|
||||||
|
let amount_since_last_update = current_amount - amount_at_last_update;
|
||||||
|
|
||||||
|
let kilobytes_per_second = amount_since_last_update / (last_update_difference as usize).max(1);
|
||||||
|
|
||||||
|
let remaining = max - current_amount; // bytes
|
||||||
|
let time_remaining = (remaining / 1000) / kilobytes_per_second.max(1);
|
||||||
|
self.sender
|
||||||
|
.send(DownloadManagerSignal::UpdateUIStats(
|
||||||
|
kilobytes_per_second,
|
||||||
|
time_remaining,
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
self.points_towards_update
|
|
||||||
.fetch_sub(to_update, Ordering::Relaxed);
|
|
||||||
self.sender.send(DownloadManagerSignal::Update).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_time_now(&self) {
|
pub fn set_time_now(&self) {
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ pub struct QueueUpdateEvent {
|
|||||||
pub status: DownloadManagerStatus,
|
pub status: DownloadManagerStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, Clone)]
|
||||||
|
pub struct StatsUpdateEvent {
|
||||||
|
pub speed: usize,
|
||||||
|
pub time: usize,
|
||||||
|
}
|
||||||
|
|
||||||
// Game version with some fields missing and size information
|
// Game version with some fields missing and size information
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|||||||
@@ -3935,11 +3935,6 @@ mlly@^1.3.0, mlly@^1.4.2, mlly@^1.6.1, mlly@^1.7.1:
|
|||||||
pkg-types "^1.2.0"
|
pkg-types "^1.2.0"
|
||||||
ufo "^1.5.4"
|
ufo "^1.5.4"
|
||||||
|
|
||||||
moment@^2.30.1:
|
|
||||||
version "2.30.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
|
|
||||||
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
|
|
||||||
|
|
||||||
mri@^1.2.0:
|
mri@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
|
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
|
||||||
|
|||||||
Reference in New Issue
Block a user