204 lines
5.3 KiB
Vue
204 lines
5.3 KiB
Vue
<template>
|
|
<div class="mt-1 columns is-multiline">
|
|
<div class="column is-12 is-clearfix">
|
|
<h1 class="title is-4">
|
|
<span class="icon-text">
|
|
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
|
<span>Terminal</span>
|
|
</span>
|
|
</h1>
|
|
<div class="subtitle is-6 is-unselectable">
|
|
You can use this terminal window to execute non-interactive commands. The interface is jailed to the
|
|
<code>yt-dlp</code>
|
|
</div>
|
|
</div>
|
|
<div class="column is-12">
|
|
<div class="card">
|
|
<header class="card-header">
|
|
<p class="card-header-title">
|
|
<span class="icon"><i class="fa-solid fa-terminal" /></span> Console Output
|
|
</p>
|
|
<p class="card-header-icon">
|
|
<span v-tooltip.top="'Clear console window'" class="icon" @click="clearOutput">
|
|
<i class="fa-solid fa-broom" /></span>
|
|
</p>
|
|
</header>
|
|
<section class="card-content p-0 m-0">
|
|
<div ref="terminal_window" style="min-height: 60vh;max-height:70vh;" />
|
|
</section>
|
|
<section class="card-content p-1 m-1">
|
|
<div class="field is-grouped">
|
|
<div class="control">
|
|
<span class="icon-text input is-unselectable">
|
|
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
|
<span>yt-dlp</span>
|
|
</span>
|
|
</div>
|
|
<div class="control is-expanded">
|
|
<input type="text" class="input" v-model="command" placeholder="--help" autocomplete="off"
|
|
ref="command_input" @keydown.enter="runCommand" :disabled="isLoading" id="command">
|
|
</div>
|
|
<p class="control">
|
|
<button class="button is-primary" type="button" :disabled="isLoading || '' === command"
|
|
@click="runCommand">
|
|
<span class="icon">
|
|
<i class="fa-solid fa-spinner" spin v-if="isLoading" />
|
|
<i class="fa-solid fa-paper-plane" v-else />
|
|
</span>
|
|
</button>
|
|
</p>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import "@xterm/xterm/css/xterm.css"
|
|
import { Terminal } from "@xterm/xterm"
|
|
import { FitAddon } from "@xterm/addon-fit"
|
|
import { useStorage } from '@vueuse/core'
|
|
|
|
const terminal = ref()
|
|
const terminalFit = ref()
|
|
const command = ref('')
|
|
const terminal_window = ref()
|
|
const command_input = ref()
|
|
const isLoading = ref(false)
|
|
const config = useConfigStore()
|
|
const socket = useSocketStore()
|
|
const bg_enable = useStorage('random_bg', true)
|
|
const bg_opacity = useStorage('random_bg_opacity', 0.85)
|
|
|
|
watch(() => isLoading.value, async value => {
|
|
if (value) {
|
|
return
|
|
}
|
|
command.value = ''
|
|
await nextTick();
|
|
focusInput()
|
|
}, { immediate: true })
|
|
|
|
watch(() => config.app.basic_mode, async () => {
|
|
if (!config.app.basic_mode) {
|
|
return
|
|
}
|
|
await navigateTo('/')
|
|
})
|
|
|
|
watch(() => config.app.console_enabled, async () => {
|
|
if (config.app.console_enabled) {
|
|
return
|
|
}
|
|
await navigateTo('/')
|
|
})
|
|
|
|
const reSizeTerminal = () => {
|
|
if (!terminal.value) {
|
|
return
|
|
}
|
|
terminalFit.value.fit()
|
|
}
|
|
|
|
const runCommand = async () => {
|
|
if ('' === command.value) {
|
|
return
|
|
}
|
|
|
|
if (command.value.startsWith('yt-dlp')) {
|
|
command.value = command.value.replace(/^yt-dlp/, '').trim()
|
|
await nextTick()
|
|
if ('' === command.value) {
|
|
return
|
|
}
|
|
}
|
|
|
|
if (!terminal.value) {
|
|
terminal.value = new Terminal({
|
|
fontSize: 14,
|
|
fontFamily: "'JetBrains Mono', monospace",
|
|
cursorBlink: false,
|
|
cursorStyle: 'underline',
|
|
cols: 108,
|
|
rows: 10,
|
|
buffer: 1000,
|
|
disableStdin: true,
|
|
scrollback: 1000,
|
|
})
|
|
terminalFit.value = new FitAddon()
|
|
terminal.value.loadAddon(terminalFit.value)
|
|
terminal.value.open(terminal_window.value)
|
|
terminalFit.value.fit();
|
|
}
|
|
|
|
if ('clear' === command.value) {
|
|
clearOutput(true)
|
|
return
|
|
}
|
|
|
|
socket.emit('cli_post', command.value)
|
|
isLoading.value = true
|
|
terminal.value.writeln(`user@YTPTube ~`)
|
|
terminal.value.writeln(`$ yt-dlp ${command.value}`)
|
|
}
|
|
|
|
const clearOutput = async (withCommand = false) => {
|
|
if (terminal.value) {
|
|
terminal.value.clear()
|
|
}
|
|
|
|
if (true === withCommand) {
|
|
command.value = ''
|
|
}
|
|
|
|
focusInput()
|
|
}
|
|
|
|
const focusInput = () => {
|
|
if (!command_input.value) {
|
|
return
|
|
}
|
|
command_input.value.focus()
|
|
}
|
|
|
|
const writer = s => {
|
|
if (!terminal.value) {
|
|
return
|
|
}
|
|
|
|
const data = JSON.parse(s)
|
|
|
|
terminal.value.writeln(data.line)
|
|
}
|
|
|
|
const loader = () => isLoading.value = false
|
|
|
|
watch(() => config.app.basic_mode, async () => {
|
|
if (!config.app.basic_mode) {
|
|
return
|
|
}
|
|
await navigateTo('/')
|
|
})
|
|
|
|
onMounted(async () => {
|
|
window.addEventListener('resize', reSizeTerminal);
|
|
focusInput()
|
|
socket.off('cli_close', loader)
|
|
socket.off('cli_output', writer)
|
|
socket.on('cli_close', loader)
|
|
socket.on('cli_output', writer)
|
|
if (bg_enable.value) {
|
|
document.querySelector('body').setAttribute("style", `opacity: 1.0`)
|
|
}
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
socket.off('cli_close', loader)
|
|
socket.off('cli_output', writer)
|
|
window.removeEventListener('resize', reSizeTerminal)
|
|
if (bg_enable.value) {
|
|
document.querySelector('body').setAttribute("style", `opacity: ${bg_opacity.value}`)
|
|
}
|
|
});
|
|
</script>
|