You've already forked AstralRinth
forked from didirus/AstralRinth
Logs wireup (#116)
* wireup * Added live logs * Finish up wireup * Run linter * finish most * Fix most issues * Finish page * run lint --------- Co-authored-by: Jai A <jaiagr+gpg@pm.me> Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
@@ -138,84 +138,88 @@ pub async fn find_java17_jres() -> crate::Result<Vec<JavaVersion>> {
|
||||
}
|
||||
|
||||
pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
|
||||
let state = State::get().await?;
|
||||
Box::pin(
|
||||
async move {
|
||||
let state = State::get().await?;
|
||||
|
||||
let loading_bar = init_loading(
|
||||
LoadingBarType::JavaDownload {
|
||||
version: java_version,
|
||||
},
|
||||
100.0,
|
||||
"Downloading java version",
|
||||
)
|
||||
.await?;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Package {
|
||||
pub download_url: String,
|
||||
pub name: PathBuf,
|
||||
}
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Fetching java version")).await?;
|
||||
let packages = fetch_json::<Vec<Package>>(
|
||||
Method::GET,
|
||||
&format!(
|
||||
"https://api.azul.com/metadata/v1/zulu/packages?arch={}&java_version={}&os={}&archive_type=zip&javafx_bundled=false&java_package_type=jre&page_size=1",
|
||||
std::env::consts::ARCH, java_version, std::env::consts::OS
|
||||
),
|
||||
None,
|
||||
None,
|
||||
&state.fetch_semaphore,
|
||||
).await?;
|
||||
emit_loading(&loading_bar, 10.0, Some("Downloading java version")).await?;
|
||||
|
||||
if let Some(download) = packages.first() {
|
||||
let file = fetch_advanced(
|
||||
Method::GET,
|
||||
&download.download_url,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some((&loading_bar, 80.0)),
|
||||
&state.fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let path = state.directories.java_versions_dir();
|
||||
|
||||
if path.exists() {
|
||||
tokio::fs::remove_dir_all(&path).await?;
|
||||
}
|
||||
|
||||
let mut archive = zip::ZipArchive::new(std::io::Cursor::new(file))
|
||||
.map_err(|_| {
|
||||
crate::Error::from(crate::ErrorKind::InputError(
|
||||
"Failed to read java zip".to_string(),
|
||||
))
|
||||
})?;
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Extracting java")).await?;
|
||||
archive.extract(&path).map_err(|_| {
|
||||
crate::Error::from(crate::ErrorKind::InputError(
|
||||
"Failed to extract java zip".to_string(),
|
||||
))
|
||||
})?;
|
||||
emit_loading(&loading_bar, 100.0, Some("Done extracting java")).await?;
|
||||
Ok(path
|
||||
.join(
|
||||
download
|
||||
.name
|
||||
.file_stem()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
let loading_bar = init_loading(
|
||||
LoadingBarType::JavaDownload {
|
||||
version: java_version,
|
||||
},
|
||||
100.0,
|
||||
"Downloading java version",
|
||||
)
|
||||
.join(format!("zulu-{}.jre/Contents/Home/bin/java", java_version)))
|
||||
} else {
|
||||
Err(crate::ErrorKind::LauncherError(format!(
|
||||
"No Java Version found for Java version {}, OS {}, and Architecture {}",
|
||||
java_version, std::env::consts::OS, std::env::consts::ARCH,
|
||||
)).into())
|
||||
}
|
||||
.await?;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Package {
|
||||
pub download_url: String,
|
||||
pub name: PathBuf,
|
||||
}
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Fetching java version")).await?;
|
||||
let packages = fetch_json::<Vec<Package>>(
|
||||
Method::GET,
|
||||
&format!(
|
||||
"https://api.azul.com/metadata/v1/zulu/packages?arch={}&java_version={}&os={}&archive_type=zip&javafx_bundled=false&java_package_type=jre&page_size=1",
|
||||
std::env::consts::ARCH, java_version, std::env::consts::OS
|
||||
),
|
||||
None,
|
||||
None,
|
||||
&state.fetch_semaphore,
|
||||
).await?;
|
||||
emit_loading(&loading_bar, 10.0, Some("Downloading java version")).await?;
|
||||
|
||||
if let Some(download) = packages.first() {
|
||||
let file = fetch_advanced(
|
||||
Method::GET,
|
||||
&download.download_url,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some((&loading_bar, 80.0)),
|
||||
&state.fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let path = state.directories.java_versions_dir();
|
||||
|
||||
if path.exists() {
|
||||
tokio::fs::remove_dir_all(&path).await?;
|
||||
}
|
||||
|
||||
let mut archive = zip::ZipArchive::new(std::io::Cursor::new(file))
|
||||
.map_err(|_| {
|
||||
crate::Error::from(crate::ErrorKind::InputError(
|
||||
"Failed to read java zip".to_string(),
|
||||
))
|
||||
})?;
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Extracting java")).await?;
|
||||
archive.extract(&path).map_err(|_| {
|
||||
crate::Error::from(crate::ErrorKind::InputError(
|
||||
"Failed to extract java zip".to_string(),
|
||||
))
|
||||
})?;
|
||||
emit_loading(&loading_bar, 100.0, Some("Done extracting java")).await?;
|
||||
Ok(path
|
||||
.join(
|
||||
download
|
||||
.name
|
||||
.file_stem()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
)
|
||||
.join(format!("zulu-{}.jre/Contents/Home/bin/java", java_version)))
|
||||
} else {
|
||||
Err(crate::ErrorKind::LauncherError(format!(
|
||||
"No Java Version found for Java version {}, OS {}, and Architecture {}",
|
||||
java_version, std::env::consts::OS, std::env::consts::ARCH,
|
||||
)).into())
|
||||
}
|
||||
}
|
||||
).await
|
||||
}
|
||||
|
||||
// Get all JREs that exist on the system
|
||||
|
||||
@@ -5,44 +5,64 @@ use tokio::fs::read_to_string;
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Logs {
|
||||
pub datetime_string: String,
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
pub stdout: Option<String>,
|
||||
pub stderr: Option<String>,
|
||||
}
|
||||
impl Logs {
|
||||
async fn build(
|
||||
profile_uuid: uuid::Uuid,
|
||||
datetime_string: String,
|
||||
clear_contents: Option<bool>,
|
||||
) -> crate::Result<Self> {
|
||||
Ok(Self {
|
||||
stdout: get_stdout_by_datetime(profile_uuid, &datetime_string)
|
||||
.await?,
|
||||
stderr: get_stderr_by_datetime(profile_uuid, &datetime_string)
|
||||
.await?,
|
||||
stdout: if clear_contents.unwrap_or(false) {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
get_stdout_by_datetime(profile_uuid, &datetime_string)
|
||||
.await?,
|
||||
)
|
||||
},
|
||||
stderr: if clear_contents.unwrap_or(false) {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
get_stderr_by_datetime(profile_uuid, &datetime_string)
|
||||
.await?,
|
||||
)
|
||||
},
|
||||
datetime_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn get_logs(profile_uuid: uuid::Uuid) -> crate::Result<Vec<Logs>> {
|
||||
pub async fn get_logs(
|
||||
profile_uuid: uuid::Uuid,
|
||||
clear_contents: Option<bool>,
|
||||
) -> crate::Result<Vec<Logs>> {
|
||||
let state = State::get().await?;
|
||||
let logs_folder = state.directories.profile_logs_dir(profile_uuid);
|
||||
let mut logs = Vec::new();
|
||||
for entry in std::fs::read_dir(logs_folder)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
if let Some(datetime_string) = path.file_name() {
|
||||
logs.push(
|
||||
Logs::build(
|
||||
profile_uuid,
|
||||
datetime_string.to_string_lossy().to_string(),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
if logs_folder.exists() {
|
||||
for entry in std::fs::read_dir(logs_folder)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
if let Some(datetime_string) = path.file_name() {
|
||||
logs.push(
|
||||
Logs::build(
|
||||
profile_uuid,
|
||||
datetime_string.to_string_lossy().to_string(),
|
||||
clear_contents,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut logs = logs.into_iter().collect::<crate::Result<Vec<Logs>>>()?;
|
||||
logs.sort_by_key(|x| x.datetime_string.clone());
|
||||
Ok(logs)
|
||||
@@ -54,8 +74,12 @@ pub async fn get_logs_by_datetime(
|
||||
datetime_string: String,
|
||||
) -> crate::Result<Logs> {
|
||||
Ok(Logs {
|
||||
stdout: get_stdout_by_datetime(profile_uuid, &datetime_string).await?,
|
||||
stderr: get_stderr_by_datetime(profile_uuid, &datetime_string).await?,
|
||||
stdout: Some(
|
||||
get_stdout_by_datetime(profile_uuid, &datetime_string).await?,
|
||||
),
|
||||
stderr: Some(
|
||||
get_stderr_by_datetime(profile_uuid, &datetime_string).await?,
|
||||
),
|
||||
datetime_string,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -187,24 +187,27 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
#[tracing::instrument]
|
||||
async fn get_all_autoinstalled_jre_path() -> Result<HashSet<PathBuf>, JREError>
|
||||
{
|
||||
let state = State::get().await.map_err(|_| JREError::StateError)?;
|
||||
Box::pin(async move {
|
||||
let state = State::get().await.map_err(|_| JREError::StateError)?;
|
||||
|
||||
let mut jre_paths = HashSet::new();
|
||||
let base_path = state.directories.java_versions_dir();
|
||||
let mut jre_paths = HashSet::new();
|
||||
let base_path = state.directories.java_versions_dir();
|
||||
|
||||
if base_path.is_dir() {
|
||||
for entry in std::fs::read_dir(base_path)? {
|
||||
let entry = entry?;
|
||||
let file_path = entry.path().join("bin");
|
||||
let contents = std::fs::read_to_string(file_path)?;
|
||||
if base_path.is_dir() {
|
||||
for entry in std::fs::read_dir(base_path)? {
|
||||
let entry = entry?;
|
||||
let file_path = entry.path().join("bin");
|
||||
let contents = std::fs::read_to_string(file_path)?;
|
||||
|
||||
let entry = entry.path().join(contents);
|
||||
println!("{:?}", entry);
|
||||
jre_paths.insert(entry);
|
||||
let entry = entry.path().join(contents);
|
||||
println!("{:?}", entry);
|
||||
jre_paths.insert(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(jre_paths)
|
||||
Ok(jre_paths)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
// Gets all JREs from the PATH env variable
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"ofetch": "^1.0.1",
|
||||
"omorphia": "^0.4.10",
|
||||
"omorphia": "^0.4.13",
|
||||
"pinia": "^2.0.33",
|
||||
"vite-svg-loader": "^4.0.0",
|
||||
"vue": "^3.2.45",
|
||||
|
||||
@@ -14,8 +14,17 @@ pub struct Logs {
|
||||
|
||||
/// Get all Logs for a profile, sorted by datetime
|
||||
#[tauri::command]
|
||||
pub async fn logs_get_logs(profile_uuid: Uuid) -> Result<Vec<Logs>> {
|
||||
Ok(logs::get_logs(profile_uuid).await?)
|
||||
pub async fn logs_get_logs(
|
||||
profile_uuid: Uuid,
|
||||
clear_contents: Option<bool>,
|
||||
) -> Result<Vec<Logs>> {
|
||||
use std::time::Instant;
|
||||
let now = Instant::now();
|
||||
let val = logs::get_logs(profile_uuid, clear_contents).await?;
|
||||
let elapsed = now.elapsed();
|
||||
println!("Elapsed: {:.2?}", elapsed);
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Get a Log struct for a profile by profile id and datetime string
|
||||
|
||||
@@ -40,6 +40,10 @@ a {
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
color: var(--color-base) !important;
|
||||
outline: 2px solid transparent;
|
||||
|
||||
@@ -17,8 +17,8 @@ pub struct Logs {
|
||||
|
||||
/// Get all logs that exist for a given profile
|
||||
/// This is returned as an array of Log objects, sorted by datetime_string (the folder name, when the log was created)
|
||||
export async function get_logs(profileUuid) {
|
||||
return await invoke('logs_get_logs', { profileUuid })
|
||||
export async function get_logs(profileUuid, clearContents) {
|
||||
return await invoke('logs_get_logs', { profileUuid, clearContents })
|
||||
}
|
||||
|
||||
/// Get a profile's log by datetime_string (the folder name, when the log was created)
|
||||
|
||||
@@ -63,7 +63,13 @@
|
||||
</div>
|
||||
<div class="content">
|
||||
<Promotion />
|
||||
<router-view :instance="instance" />
|
||||
<RouterView v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Suspense @pending="loadingBar.startLoading()" @resolve="loadingBar.stopLoading()">
|
||||
<component :is="Component" :instance="instance"></component>
|
||||
</Suspense>
|
||||
</template>
|
||||
</RouterView>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -81,7 +87,7 @@ import { useRoute } from 'vue-router'
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { useBreadcrumbs, useSearch } from '@/store/state'
|
||||
import { useBreadcrumbs, useLoading, useSearch } from '@/store/state'
|
||||
|
||||
const route = useRoute()
|
||||
const searchStore = useSearch()
|
||||
@@ -96,6 +102,8 @@ breadcrumbs.setContext({
|
||||
link: route.path,
|
||||
})
|
||||
|
||||
const loadingBar = useLoading()
|
||||
|
||||
const uuid = ref(null)
|
||||
const playing = ref(false)
|
||||
const loading = ref(false)
|
||||
@@ -240,15 +248,18 @@ Button {
|
||||
width: 100%;
|
||||
color: var(--color-primary);
|
||||
padding: var(--gap-md);
|
||||
box-shadow: none;
|
||||
|
||||
&.router-link-exact-active {
|
||||
box-shadow: var(--shadow-inset-lg);
|
||||
background: var(--color-button-bg);
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-button-bg);
|
||||
color: var(--color-contrast);
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: var(--shadow-inset-lg);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,92 +1,177 @@
|
||||
<template>
|
||||
<Card class="log-card">
|
||||
<div class="button-row">
|
||||
<DropdownSelect :options="['logs/latest.log']" />
|
||||
<DropdownSelect
|
||||
name="Log date"
|
||||
:model-value="logs[selectedLogIndex]"
|
||||
:options="logs"
|
||||
:display-name="(option) => option?.name"
|
||||
:disabled="logs.length === 0"
|
||||
@change="(value) => (selectedLogIndex = value.index)"
|
||||
/>
|
||||
<div class="button-group">
|
||||
<Button>
|
||||
<ClipboardCopyIcon />
|
||||
Copy
|
||||
<Button :disabled="!logs[selectedLogIndex]" @click="copyLog()">
|
||||
<ClipboardCopyIcon v-if="!copied" />
|
||||
<CheckIcon v-else />
|
||||
{{ copied ? 'Copied' : 'Copy' }}
|
||||
</Button>
|
||||
<Button color="primary">
|
||||
<Button disabled color="primary">
|
||||
<SendIcon />
|
||||
Share
|
||||
</Button>
|
||||
<Button color="danger">
|
||||
<Button
|
||||
:disabled="!logs[selectedLogIndex] || logs[selectedLogIndex].live === true"
|
||||
color="danger"
|
||||
@click="deleteLog()"
|
||||
>
|
||||
<TrashIcon />
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-text">
|
||||
<div v-for="(line, index) in fileContents.value.split('\n')" :key="index">{{ line }}</div>
|
||||
<div ref="logContainer" class="log-text">
|
||||
<!-- {{ logs[1] }}-->
|
||||
<div v-for="line in logs[selectedLogIndex]?.stdout.split('\n')" :key="line" class="no-wrap">
|
||||
{{ line }}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Card, Button, TrashIcon, SendIcon, ClipboardCopyIcon, DropdownSelect } from 'omorphia'
|
||||
</script>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
fileContents: {
|
||||
value:
|
||||
"'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
'[22:13:02] [Server thread/INFO]: venashial lost connection: Disconnected\n' +
|
||||
'[22:13:02] [Server thread/INFO]: venashial left the game\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Stopping singleplayer server as player logged out\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Stopping server\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Saving players\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Saving worlds\n' +
|
||||
"[22:13:02] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:overworld\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_nether\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (New World): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM-1): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM1): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage: All dimensions are saved\n' +
|
||||
'[22:13:06] [Render thread/INFO]: Stopping worker threads\n' +
|
||||
'[22:13:07] [Render thread/INFO]: Stopping!\n' +
|
||||
'[22:13:07] [CraftPresence-ShutDown-Handler/INFO]: Shutting down CraftPresence...\n' +
|
||||
"'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
'[22:13:02] [Server thread/INFO]: venashial lost connection: Disconnected\n' +
|
||||
'[22:13:02] [Server thread/INFO]: venashial left the game\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Stopping singleplayer server as player logged out\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Stopping server\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Saving players\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Saving worlds\n' +
|
||||
"[22:13:02] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:overworld\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_nether\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (New World): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM-1): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM1): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage: All dimensions are saved\n' +
|
||||
'[22:13:06] [Render thread/INFO]: Stopping worker threads\n' +
|
||||
'[22:13:07] [Render thread/INFO]: Stopping!\n' +
|
||||
'[22:13:07] [CraftPresence-ShutDown-Handler/INFO]: Shutting down CraftPresence...\n' +
|
||||
"'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
'[22:13:02] [Server thread/INFO]: venashial lost connection: Disconnected\n' +
|
||||
'[22:13:02] [Server thread/INFO]: venashial left the game\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Stopping singleplayer server as player logged out\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Stopping server\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Saving players\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Saving worlds\n' +
|
||||
"[22:13:02] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:overworld\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_nether\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (New World): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM-1): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM1): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage: All dimensions are saved\n' +
|
||||
'[22:13:06] [Render thread/INFO]: Stopping worker threads\n' +
|
||||
'[22:13:07] [Render thread/INFO]: Stopping!\n' +
|
||||
'[22:13:07] [CraftPresence-ShutDown-Handler/INFO]: Shutting down CraftPresence...',
|
||||
},
|
||||
}
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CheckIcon,
|
||||
ClipboardCopyIcon,
|
||||
DropdownSelect,
|
||||
SendIcon,
|
||||
TrashIcon,
|
||||
} from 'omorphia'
|
||||
import { delete_logs_by_datetime, get_logs, get_stdout_by_datetime } from '@/helpers/logs.js'
|
||||
import { nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import calendar from 'dayjs/plugin/calendar'
|
||||
import { get_stdout_by_uuid, get_uuids_by_profile_path } from '@/helpers/process.js'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { process_listener } from '@/helpers/events.js'
|
||||
|
||||
dayjs.extend(calendar)
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
async function getLiveLog() {
|
||||
const uuids = await get_uuids_by_profile_path(route.params.id)
|
||||
let returnValue
|
||||
if (uuids.length === 0) {
|
||||
returnValue = 'No live game detected. \nStart your game to proceed'
|
||||
} else {
|
||||
returnValue = await get_stdout_by_uuid(uuids[0])
|
||||
}
|
||||
|
||||
return { name: 'Live Log', stdout: returnValue, live: true }
|
||||
}
|
||||
|
||||
async function getLogs() {
|
||||
return (await get_logs(props.instance.uuid, true)).reverse().map((log) => {
|
||||
log.name = dayjs(
|
||||
log.datetime_string.slice(0, 8) + 'T' + log.datetime_string.slice(9)
|
||||
).calendar()
|
||||
log.stdout = 'Loading...'
|
||||
return log
|
||||
})
|
||||
}
|
||||
|
||||
async function setLogs() {
|
||||
const [liveLog, allLogs] = await Promise.all([getLiveLog(), getLogs()])
|
||||
logs.value = [liveLog, ...allLogs]
|
||||
}
|
||||
|
||||
const logs = ref([])
|
||||
await setLogs()
|
||||
|
||||
const selectedLogIndex = ref(0)
|
||||
const copied = ref(false)
|
||||
|
||||
const copyLog = () => {
|
||||
if (logs.value[selectedLogIndex.value]) {
|
||||
navigator.clipboard.writeText(logs.value[selectedLogIndex.value].stdout)
|
||||
copied.value = true
|
||||
}
|
||||
}
|
||||
|
||||
watch(selectedLogIndex, async (newIndex) => {
|
||||
copied.value = false
|
||||
userScrolled.value = false
|
||||
|
||||
if (newIndex !== 0) {
|
||||
logs.value[newIndex].stdout = 'Loading...'
|
||||
logs.value[newIndex].stdout = await get_stdout_by_datetime(
|
||||
props.instance.uuid,
|
||||
logs.value[newIndex].datetime_string
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const deleteLog = async () => {
|
||||
if (logs.value[selectedLogIndex.value] && selectedLogIndex.value !== 0) {
|
||||
let deleteIndex = selectedLogIndex.value
|
||||
selectedLogIndex.value = deleteIndex - 1
|
||||
await delete_logs_by_datetime(props.instance.uuid, logs.value[deleteIndex].datetime_string)
|
||||
await setLogs()
|
||||
}
|
||||
}
|
||||
|
||||
const logContainer = ref(null)
|
||||
const interval = ref(null)
|
||||
const userScrolled = ref(false)
|
||||
const isAutoScrolling = ref(false)
|
||||
|
||||
function handleUserScroll() {
|
||||
if (!isAutoScrolling.value) {
|
||||
userScrolled.value = true
|
||||
}
|
||||
}
|
||||
|
||||
interval.value = setInterval(async () => {
|
||||
if (logs.value.length > 0) {
|
||||
logs.value[0] = await getLiveLog()
|
||||
|
||||
if (selectedLogIndex.value === 0 && !userScrolled.value) {
|
||||
await nextTick()
|
||||
isAutoScrolling.value = true
|
||||
logContainer.value.scrollTop =
|
||||
logContainer.value.scrollHeight - logContainer.value.offsetHeight
|
||||
setTimeout(() => (isAutoScrolling.value = false), 50)
|
||||
}
|
||||
}
|
||||
}, 250)
|
||||
|
||||
const unlistenProcesses = await process_listener(async (e) => {
|
||||
if (e.event === 'finished') {
|
||||
userScrolled.value = false
|
||||
await setLogs()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
logContainer.value.addEventListener('scroll', handleUserScroll)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
logContainer.value.removeEventListener('scroll', handleUserScroll)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
clearInterval(interval.value)
|
||||
unlistenProcesses()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -94,11 +179,14 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
height: calc(100vh - 11rem);
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
@@ -109,7 +197,7 @@ export default {
|
||||
|
||||
.log-text {
|
||||
width: 100%;
|
||||
aspect-ratio: 2/1;
|
||||
height: 100%;
|
||||
font-family: var(--mono-font);
|
||||
background-color: var(--color-accent-contrast);
|
||||
color: var(--color-contrast);
|
||||
@@ -117,5 +205,6 @@ export default {
|
||||
padding: 1.5rem;
|
||||
overflow: auto;
|
||||
white-space: normal;
|
||||
color-scheme: dark;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1149,10 +1149,10 @@ ofetch@^1.0.1:
|
||||
node-fetch-native "^1.0.2"
|
||||
ufo "^1.1.0"
|
||||
|
||||
omorphia@^0.4.10:
|
||||
version "0.4.10"
|
||||
resolved "https://registry.yarnpkg.com/omorphia/-/omorphia-0.4.10.tgz#93c0e6a08a233f27d76587286e42450af44bb55d"
|
||||
integrity sha512-WgSFosOqoM0IRpzGNYyprfZSRyBLgqs6sTmKRuWo96ZpzrHRWAom2upIm/HAxAC+YBwFni5sgUeBemXYI7wmuw==
|
||||
omorphia@^0.4.13:
|
||||
version "0.4.13"
|
||||
resolved "https://registry.yarnpkg.com/omorphia/-/omorphia-0.4.13.tgz#6141886b9c332e4a28afe31a743f0c85d4a09efe"
|
||||
integrity sha512-Yb76WoM4e42aAq3G/OPxQS6whCu+WIHVBhJxSzmUUycF1Pvf6tJZov+LefneSkk4xcQAjDZsgK8VOVD7q/siig==
|
||||
dependencies:
|
||||
dayjs "^1.11.7"
|
||||
floating-vue "^2.0.0-beta.20"
|
||||
|
||||
Reference in New Issue
Block a user