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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user