mirror of
https://github.com/LCE-Hub/LCE-Emerald-Launcher.git
synced 2026-09-23 08:32:51 +00:00
feat: playtime counting
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tauri::{AppHandle, Manager, State};
|
use tauri::{AppHandle, Manager, State};
|
||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
use crate::commands::runners;
|
use crate::commands::runners;
|
||||||
@@ -8,6 +9,7 @@ use crate::config;
|
|||||||
use crate::platform::macos;
|
use crate::platform::macos;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use crate::platform::linux;
|
use crate::platform::linux;
|
||||||
|
use crate::playtime::{self, PlaytimeResponse};
|
||||||
use crate::state::GameState;
|
use crate::state::GameState;
|
||||||
use crate::types::McServer;
|
use crate::types::McServer;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
@@ -113,6 +115,7 @@ pub async fn launch_game(
|
|||||||
cmd.arg(a);
|
cmd.arg(a);
|
||||||
}
|
}
|
||||||
cmd.current_dir(&working_dir);
|
cmd.current_dir(&working_dir);
|
||||||
|
let playtime_start = std::time::Instant::now();
|
||||||
let child = cmd.spawn().map_err(|e| e.to_string())?;
|
let child = cmd.spawn().map_err(|e| e.to_string())?;
|
||||||
{
|
{
|
||||||
let mut lock = state.child.lock().await;
|
let mut lock = state.child.lock().await;
|
||||||
@@ -138,6 +141,11 @@ pub async fn launch_game(
|
|||||||
*lock = None;
|
*lock = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let duration = playtime_start.elapsed();
|
||||||
|
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||||
|
let start = now - duration.as_secs();
|
||||||
|
playtime::record_session(&app, &instance_id, start, now);
|
||||||
|
|
||||||
return if status.success() || status.code() == Some(253) || status.code() == Some(96) {
|
return if status.success() || status.code() == Some(253) || status.code() == Some(96) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@@ -222,6 +230,7 @@ pub async fn launch_game(
|
|||||||
.stdout(std::process::Stdio::null())
|
.stdout(std::process::Stdio::null())
|
||||||
.stderr(std::process::Stdio::null());
|
.stderr(std::process::Stdio::null());
|
||||||
|
|
||||||
|
let playtime_start = std::time::Instant::now();
|
||||||
let child = cmd.spawn().map_err(|e| e.to_string())?;
|
let child = cmd.spawn().map_err(|e| e.to_string())?;
|
||||||
{
|
{
|
||||||
let mut lock = state.child.lock().await;
|
let mut lock = state.child.lock().await;
|
||||||
@@ -247,6 +256,11 @@ pub async fn launch_game(
|
|||||||
*lock = None;
|
*lock = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let duration = playtime_start.elapsed();
|
||||||
|
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||||
|
let start = now - duration.as_secs();
|
||||||
|
playtime::record_session(&app, &instance_id, start, now);
|
||||||
|
|
||||||
return if status.success() || status.code() == Some(253) || status.code() == Some(96) {
|
return if status.success() || status.code() == Some(253) || status.code() == Some(96) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@@ -263,6 +277,7 @@ pub async fn launch_game(
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
cmd.process_group(0);
|
cmd.process_group(0);
|
||||||
cmd.current_dir(&working_dir);
|
cmd.current_dir(&working_dir);
|
||||||
|
let playtime_start = std::time::Instant::now();
|
||||||
let child = cmd.spawn().map_err(|e| e.to_string())?;
|
let child = cmd.spawn().map_err(|e| e.to_string())?;
|
||||||
{
|
{
|
||||||
let mut lock = state.child.lock().await;
|
let mut lock = state.child.lock().await;
|
||||||
@@ -285,6 +300,10 @@ pub async fn launch_game(
|
|||||||
let mut lock = state.child.lock().await;
|
let mut lock = state.child.lock().await;
|
||||||
*lock = None;
|
*lock = None;
|
||||||
}
|
}
|
||||||
|
let duration = playtime_start.elapsed();
|
||||||
|
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||||
|
let start = now - duration.as_secs();
|
||||||
|
playtime::record_session(&app, &instance_id, start, now);
|
||||||
return if status.success() || status.code() == Some(253) || status.code() == Some(96) {
|
return if status.success() || status.code() == Some(253) || status.code() == Some(96) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@@ -362,6 +381,11 @@ pub fn get_instance_path(app: AppHandle, instance_id: String) -> String {
|
|||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_playtime(app: AppHandle, instance_id: String) -> PlaytimeResponse {
|
||||||
|
playtime::get_playtime(&app, &instance_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ensure_server_list(instance_dir: &PathBuf, servers: Vec<McServer>) {
|
pub fn ensure_server_list(instance_dir: &PathBuf, servers: Vec<McServer>) {
|
||||||
let servers_db = instance_dir.join("servers.db");
|
let servers_db = instance_dir.join("servers.db");
|
||||||
let mut all_servers = Vec::new();
|
let mut all_servers = Vec::new();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ mod types;
|
|||||||
mod state;
|
mod state;
|
||||||
mod config;
|
mod config;
|
||||||
mod util;
|
mod util;
|
||||||
|
mod playtime;
|
||||||
mod platform;
|
mod platform;
|
||||||
mod networking;
|
mod networking;
|
||||||
mod workshop_server;
|
mod workshop_server;
|
||||||
@@ -80,6 +81,7 @@ pub fn run() {
|
|||||||
steam::add_to_steam,
|
steam::add_to_steam,
|
||||||
proxy_cmd::http_proxy_request,
|
proxy_cmd::http_proxy_request,
|
||||||
game::get_instance_path,
|
game::get_instance_path,
|
||||||
|
game::get_playtime,
|
||||||
commands::console2lce::import_world,
|
commands::console2lce::import_world,
|
||||||
stun::stun_discover,
|
stun::stun_discover,
|
||||||
direct::start_direct_proxy,
|
direct::start_direct_proxy,
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
use tauri::AppHandle;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use crate::util;
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct PlaytimeSession {
|
||||||
|
pub start: u64,
|
||||||
|
pub end: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct PlaytimeData {
|
||||||
|
pub sessions: HashMap<String, Vec<PlaytimeSession>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PlaytimeResponse {
|
||||||
|
pub total_seconds: u64,
|
||||||
|
pub week_seconds: u64,
|
||||||
|
pub day_seconds: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn playtime_path(app: &AppHandle) -> PathBuf {
|
||||||
|
util::get_app_dir(app).join("playtime.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(app: &AppHandle) -> PlaytimeData {
|
||||||
|
let path = playtime_path(app);
|
||||||
|
if path.exists() {
|
||||||
|
let content = std::fs::read_to_string(&path).unwrap_or_default();
|
||||||
|
serde_json::from_str(&content).unwrap_or(PlaytimeData { sessions: HashMap::new() })
|
||||||
|
} else {
|
||||||
|
PlaytimeData { sessions: HashMap::new() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(app: &AppHandle, data: &PlaytimeData) {
|
||||||
|
let path = playtime_path(app);
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
let _ = std::fs::create_dir_all(parent);
|
||||||
|
}
|
||||||
|
if let Ok(content) = serde_json::to_string_pretty(data) {
|
||||||
|
let _ = std::fs::write(&path, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn record_session(app: &AppHandle, instance_id: &str, start: u64, end: u64) {
|
||||||
|
let mut data = load(app);
|
||||||
|
let sessions = data.sessions.entry(instance_id.to_string()).or_default();
|
||||||
|
sessions.push(PlaytimeSession { start, end });
|
||||||
|
save(app, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_playtime(app: &AppHandle, instance_id: &str) -> PlaytimeResponse {
|
||||||
|
let data = load(app);
|
||||||
|
let sessions = data.sessions.get(instance_id);
|
||||||
|
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||||
|
let week_ago = now - 7 * 24 * 60 * 60;
|
||||||
|
let day_ago = now - 24 * 60 * 60;
|
||||||
|
let total_seconds: u64 = sessions.map_or(0, |s| s.iter().map(|s| s.end - s.start).sum());
|
||||||
|
let week_seconds: u64 = sessions.map_or(0, |s| s.iter()
|
||||||
|
.filter(|s| s.start >= week_ago)
|
||||||
|
.map(|s| s.end - s.start)
|
||||||
|
.sum());
|
||||||
|
let day_seconds: u64 = sessions.map_or(0, |s| s.iter()
|
||||||
|
.filter(|s| s.start >= day_ago)
|
||||||
|
.map(|s| s.end - s.start)
|
||||||
|
.sum());
|
||||||
|
|
||||||
|
PlaytimeResponse { total_seconds, week_seconds, day_seconds }
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { TauriService, PlaytimeResponse } from "../../services/TauriService";
|
||||||
|
|
||||||
|
function formatTime(seconds: number): string {
|
||||||
|
const h = Math.floor(seconds / 3600);
|
||||||
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
|
const s = seconds % 60;
|
||||||
|
if (h > 0) return `${h}h ${m}m ${s}s`;
|
||||||
|
if (m > 0) return `${m}m ${s}s`;
|
||||||
|
return `${s}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlaytimeModal({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
instanceId,
|
||||||
|
instanceName,
|
||||||
|
playBackSound,
|
||||||
|
}: {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
instanceId: string;
|
||||||
|
instanceName: string;
|
||||||
|
playBackSound: (s?: string) => void;
|
||||||
|
}) {
|
||||||
|
const [playtime, setPlaytime] = useState<PlaytimeResponse | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setPlaytime(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
TauriService.getPlaytime(instanceId)
|
||||||
|
.then(setPlaytime)
|
||||||
|
.catch(console.error)
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [isOpen, instanceId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return;
|
||||||
|
const handleKey = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
playBackSound("close_click.wav");
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("keydown", handleKey);
|
||||||
|
return () => window.removeEventListener("keydown", handleKey);
|
||||||
|
}, [isOpen, onClose, playBackSound]);
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
className="fixed inset-0 w-screen h-screen z-[100] flex items-center justify-center bg-black/80 backdrop-blur-md outline-none border-none"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="relative w-[380px] p-6 flex flex-col items-center shadow-2xl"
|
||||||
|
style={{
|
||||||
|
backgroundImage: "url('/images/frame_background.png')",
|
||||||
|
backgroundSize: "100% 100%",
|
||||||
|
imageRendering: "pixelated",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2 className="text-[#FFFF55] text-2xl mc-text-shadow mb-4 border-b-2 border-[#373737] pb-2 w-full text-center uppercase">
|
||||||
|
Playtime
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-white text-sm mc-text-shadow mb-5 text-center">
|
||||||
|
{instanceName}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div className="text-gray-400 text-sm mc-text-shadow mb-4">Loading...</div>
|
||||||
|
) : playtime ? (
|
||||||
|
<div className="w-full flex flex-col gap-3 mb-4">
|
||||||
|
<div className="flex justify-between items-center bg-black/30 px-4 py-3 border border-[#373737]">
|
||||||
|
<span className="text-[#AAAAAA] text-sm mc-text-shadow uppercase tracking-wider">
|
||||||
|
Total
|
||||||
|
</span>
|
||||||
|
<span className="text-white text-lg mc-text-shadow font-bold">
|
||||||
|
{formatTime(playtime.totalSeconds)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center bg-black/30 px-4 py-3 border border-[#373737]">
|
||||||
|
<span className="text-[#AAAAAA] text-sm mc-text-shadow uppercase tracking-wider">
|
||||||
|
This Week
|
||||||
|
</span>
|
||||||
|
<span className="text-white text-lg mc-text-shadow font-bold">
|
||||||
|
{formatTime(playtime.weekSeconds)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center bg-black/30 px-4 py-3 border border-[#373737]">
|
||||||
|
<span className="text-[#AAAAAA] text-sm mc-text-shadow uppercase tracking-wider">
|
||||||
|
Today
|
||||||
|
</span>
|
||||||
|
<span className="text-white text-lg mc-text-shadow font-bold">
|
||||||
|
{formatTime(playtime.daySeconds)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-red-400 text-sm mc-text-shadow mb-4">Failed to load playtime data.</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
playBackSound("close_click.wav");
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
className="w-32 h-10 flex items-center justify-center text-xl mc-text-shadow transition-colors outline-none border-none text-white"
|
||||||
|
style={{
|
||||||
|
backgroundImage: "url('/images/button_highlighted.png')",
|
||||||
|
backgroundSize: "100% 100%",
|
||||||
|
imageRendering: "pixelated",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { useState, useEffect, useRef, memo } from "react";
|
import { useState, useEffect, useRef, memo } from "react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { TauriService } from "../../services/TauriService";
|
import { TauriService, PlaytimeResponse } from "../../services/TauriService";
|
||||||
import CustomTUModal from "../modals/CustomTUModal";
|
import CustomTUModal from "../modals/CustomTUModal";
|
||||||
import SetUidModal from "../modals/SetUidModal";
|
import SetUidModal from "../modals/SetUidModal";
|
||||||
import ImportWorldModal from "../modals/ImportWorldModal";
|
import ImportWorldModal from "../modals/ImportWorldModal";
|
||||||
|
import PlaytimeModal from "../modals/PlaytimeModal";
|
||||||
import {
|
import {
|
||||||
useUI,
|
useUI,
|
||||||
useConfig,
|
useConfig,
|
||||||
@@ -46,6 +47,14 @@ const DeleteConfirmButton = memo(function DeleteConfirmButton({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function formatPlaytime(seconds: number): string {
|
||||||
|
const h = Math.floor(seconds / 3600);
|
||||||
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
|
if (h > 0) return `${h}h ${m}m`;
|
||||||
|
if (m > 0) return `${m}m`;
|
||||||
|
return seconds > 0 ? `${seconds}s` : "";
|
||||||
|
}
|
||||||
|
|
||||||
const VersionsView = memo(function VersionsView() {
|
const VersionsView = memo(function VersionsView() {
|
||||||
const { setActiveView } = useUI();
|
const { setActiveView } = useUI();
|
||||||
const {
|
const {
|
||||||
@@ -78,6 +87,9 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
const [editingEdition, setEditingEdition] = useState<Edition | null>(null);
|
const [editingEdition, setEditingEdition] = useState<Edition | null>(null);
|
||||||
const [isImportWorldModalOpen, setIsImportWorldModalOpen] = useState(false);
|
const [isImportWorldModalOpen, setIsImportWorldModalOpen] = useState(false);
|
||||||
const [importWorldTarget, setImportWorldTarget] = useState<{ id: string; name: string } | null>(null);
|
const [importWorldTarget, setImportWorldTarget] = useState<{ id: string; name: string } | null>(null);
|
||||||
|
const [isPlaytimeModalOpen, setIsPlaytimeModalOpen] = useState(false);
|
||||||
|
const [playtimeTarget, setPlaytimeTarget] = useState<{ id: string; name: string } | null>(null);
|
||||||
|
const [playtimeMap, setPlaytimeMap] = useState<Record<string, PlaytimeResponse>>({});
|
||||||
const [initialPath, setInitialPath] = useState<string>("");
|
const [initialPath, setInitialPath] = useState<string>("");
|
||||||
const [hoveredBtn, setHoveredBtn] = useState<{
|
const [hoveredBtn, setHoveredBtn] = useState<{
|
||||||
row: number;
|
row: number;
|
||||||
@@ -189,6 +201,21 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
}
|
}
|
||||||
}, [focusIndex]);
|
}, [focusIndex]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPlaytimes = async () => {
|
||||||
|
const map: Record<string, PlaytimeResponse> = {};
|
||||||
|
await Promise.all(installedVersions.map(async (id) => {
|
||||||
|
try {
|
||||||
|
map[id] = await TauriService.getPlaytime(id);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
setPlaytimeMap(map);
|
||||||
|
};
|
||||||
|
fetchPlaytimes();
|
||||||
|
}, [installedVersions]);
|
||||||
|
|
||||||
const handleEditionClick = (edition: Edition, index: number) => {
|
const handleEditionClick = (edition: Edition, index: number) => {
|
||||||
const isInstalled = installedVersions.includes(edition.instanceId);
|
const isInstalled = installedVersions.includes(edition.instanceId);
|
||||||
if (isInstalled) {
|
if (isInstalled) {
|
||||||
@@ -331,6 +358,36 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
>
|
>
|
||||||
{edition.name}
|
{edition.name}
|
||||||
</span>
|
</span>
|
||||||
|
{isInstalled && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
playPressSound();
|
||||||
|
setPlaytimeTarget({ id: edition.instanceId, name: edition.name });
|
||||||
|
setIsPlaytimeModalOpen(true);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-1.5 px-2 py-1 bg-black/60 border border-[#555] hover:border-[#FFFF55] group transition-colors flex-shrink-0"
|
||||||
|
title="View playtime"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="#AAAAAA"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className="group-hover:stroke-[#FFFF55] transition-colors"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<polyline points="12 6 12 12 16 14" />
|
||||||
|
</svg>
|
||||||
|
<span className="text-xs text-[#AAAAAA] group-hover:text-[#FFFF55] leading-none transition-colors">
|
||||||
|
{playtimeMap[edition.instanceId] ? formatPlaytime(playtimeMap[edition.instanceId].totalSeconds) : ""}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{edition.category &&
|
{edition.category &&
|
||||||
edition.category.map((cat: string) => (
|
edition.category.map((cat: string) => (
|
||||||
<span
|
<span
|
||||||
@@ -769,6 +826,14 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
targetInstanceName={importWorldTarget?.name ?? ""}
|
targetInstanceName={importWorldTarget?.name ?? ""}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PlaytimeModal
|
||||||
|
isOpen={isPlaytimeModalOpen}
|
||||||
|
onClose={() => { setIsPlaytimeModalOpen(false); setPlaytimeTarget(null); }}
|
||||||
|
playBackSound={playBackSound}
|
||||||
|
instanceId={playtimeTarget?.id ?? ""}
|
||||||
|
instanceName={playtimeTarget?.name ?? ""}
|
||||||
|
/>
|
||||||
|
|
||||||
{deleteConfirmEdition && (
|
{deleteConfirmEdition && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ export interface InstalledWorkshopPackage {
|
|||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PlaytimeResponse {
|
||||||
|
totalSeconds: number;
|
||||||
|
weekSeconds: number;
|
||||||
|
daySeconds: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class TauriService {
|
export class TauriService {
|
||||||
static async saveConfig(config: AppConfig): Promise<void> {
|
static async saveConfig(config: AppConfig): Promise<void> {
|
||||||
return invoke("save_config", { config });
|
return invoke("save_config", { config });
|
||||||
@@ -283,6 +289,10 @@ export class TauriService {
|
|||||||
return invoke("http_proxy_request", { method, url, body, headers });
|
return invoke("http_proxy_request", { method, url, body, headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getPlaytime(instanceId: string): Promise<PlaytimeResponse> {
|
||||||
|
return invoke("get_playtime", { instanceId });
|
||||||
|
}
|
||||||
|
|
||||||
static async getInstancePath(instanceId: string): Promise<string> {
|
static async getInstancePath(instanceId: string): Promise<string> {
|
||||||
return invoke("get_instance_path", { instanceId });
|
return invoke("get_instance_path", { instanceId });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user