mirror of
https://github.com/dreamstarsky/runbin.git
synced 2026-05-15 22:33:09 +00:00
add web
This commit is contained in:
9
web/src/App.vue
Normal file
9
web/src/App.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-screen">
|
||||
<NavBar />
|
||||
<router-view :key="$route.fullPath" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import NavBar from './components/NavBar.vue';
|
||||
</script>
|
||||
BIN
web/src/assets/meow.png
Normal file
BIN
web/src/assets/meow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 952 KiB |
1
web/src/assets/run.svg
Normal file
1
web/src/assets/run.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M320-200v-560l440 280-440 280Zm80-280Zm0 134 210-134-210-134v268Z"/></svg>
|
||||
|
After Width: | Height: | Size: 190 B |
1
web/src/assets/save.svg
Normal file
1
web/src/assets/save.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M840-680v480q0 33-23.5 56.5T760-120H200q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h480l160 160Zm-80 34L646-760H200v560h560v-446ZM480-240q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35ZM240-560h360v-160H240v160Zm-40-86v446-560 114Z"/></svg>
|
||||
|
After Width: | Height: | Size: 388 B |
1
web/src/assets/vue.svg
Normal file
1
web/src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
43
web/src/components/HelloWorld.vue
Normal file
43
web/src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
msg: String,
|
||||
})
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
13
web/src/components/NavBar.vue
Normal file
13
web/src/components/NavBar.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div class="bg-black text-lg flex flex-row **:items-center p-4">
|
||||
<div class="w-10 h-10 rounded-full overflow-hidden">
|
||||
<img :src="MeowLogo" alt="Logo">
|
||||
</div>
|
||||
<div class="text-white ml-4 font-bold text-xl leading-10">
|
||||
Meow Paste
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import MeowLogo from '../assets/meow.png'
|
||||
</script>
|
||||
6
web/src/main.js
Normal file
6
web/src/main.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
createApp(App).use(router).mount('#app')
|
||||
209
web/src/pages/CodePage.vue
Normal file
209
web/src/pages/CodePage.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<div class="bg-[#282c34] text-md flex justify-center items-center w-[100vw] flex-1 min-h-0">
|
||||
<div class="w-3/4 h-3/4 bg-[#282c34] text-sm scale-133 flex">
|
||||
<div class="w-3/4 h-full min-w-0 overflow-auto">
|
||||
<div ref="editorContainer" class="w-full h-full">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 bg-stone-900 min-w-0 text-xs">
|
||||
<div class="h-1/2 text-white flex flex-col">
|
||||
<div class="bg-stone-700 p-1 px-2 flex items-center">
|
||||
<div>
|
||||
输入
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
</div>
|
||||
<div class="h-4 w-4 mr-2" @click="handleRun(false)">
|
||||
<img :src="saveIcon" alt="run">
|
||||
</div>
|
||||
<div class="h-4 w-4" @click="handleRun(true)">
|
||||
<img :src="runIcon" alt="run">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 min-h-0 ">
|
||||
<div class="w-full h-full overflow-hidden">
|
||||
<textarea name="stdin" id="stdin" class="w-full h-full resize-none outline-none border-0 p-2 whitespace-nowrap"
|
||||
placeholder="请输入测试样例" v-model="stdin"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-1/2 text-white flex flex-col">
|
||||
<div class="bg-stone-700 p-1 px-2">
|
||||
输出
|
||||
</div>
|
||||
<div class="flex-1 min-h-0 overflow-auto">
|
||||
<div class="w-full h-full p-2">
|
||||
<!-- {{ stdout === '' ? 'Empty' : stdout }} -->
|
||||
<div v-show="time !== 0">
|
||||
运行时间:{{ time }} ms
|
||||
</div>
|
||||
{{ status !== 'completed' ? status : (stdout === '' ? 'Empty' : stdout) }}
|
||||
<div v-show="stderr !== ''" class="text-red-500">
|
||||
{{ stderr }}
|
||||
</div>
|
||||
<div v-show="log !== ''" class="text-red-500">
|
||||
{{ log }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, defineProps } from 'vue'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { cpp } from '@codemirror/lang-cpp'
|
||||
import { basicSetup } from 'codemirror'
|
||||
import { languageServer } from 'codemirror-languageserver';
|
||||
import { keymap } from '@codemirror/view'
|
||||
import { indentMore, indentLess, insertTab } from "@codemirror/commands";
|
||||
import { oneDark } from "@codemirror/theme-one-dark";
|
||||
import runIcon from '../assets/run.svg'
|
||||
import saveIcon from '../assets/save.svg'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
}
|
||||
});
|
||||
|
||||
const stdin = ref('')
|
||||
const stdout = ref('')
|
||||
const status = ref('completed')
|
||||
const stderr = ref('')
|
||||
const time = ref(0)
|
||||
const log = ref('')
|
||||
|
||||
const serverUri = window.CONFIG.LSP_SERVER;
|
||||
const backend = window.CONFIG.BACKEND;
|
||||
const ls = languageServer({
|
||||
serverUri,
|
||||
rootUri: 'file:///main.cpp',
|
||||
workspaceFolders: [],
|
||||
documentUri: `file:///main.cpp`,
|
||||
languageId: 'cpp'
|
||||
});
|
||||
|
||||
const editorContainer = ref<HTMLElement | null>(null)
|
||||
let view: EditorView
|
||||
|
||||
function getStatus(id: string) {
|
||||
fetch(backend + `/api/pastes/${props.id}`)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
stdin.value = res.stdin
|
||||
status.value = res.status
|
||||
stdout.value = res.stdout
|
||||
stderr.value = res.stderr
|
||||
time.value = res.execution_time_ms
|
||||
log.value = res.compile_log
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const oldCode = localStorage.getItem('code')
|
||||
stdin.value = localStorage.getItem('stdin') || ''
|
||||
const state = EditorState.create({
|
||||
doc: oldCode || `#include <bits/stdc++.h>\n\nint main() {\n\n}`,
|
||||
extensions: [
|
||||
basicSetup,
|
||||
cpp(),
|
||||
ls,
|
||||
keymap.of([
|
||||
{ key: "Tab", run: indentMore },
|
||||
{ key: "Shift-Tab", run: indentLess },
|
||||
// 如果需要在行中插入真实 Tab:
|
||||
{ key: "Mod-Tab", run: insertTab },
|
||||
]),
|
||||
oneDark,
|
||||
]
|
||||
})
|
||||
|
||||
view = new EditorView({
|
||||
state,
|
||||
parent: editorContainer.value as HTMLElement
|
||||
})
|
||||
if (props.id !== null) {
|
||||
fetch(backend + `/api/pastes/${props.id}`)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: view.state.doc.length,
|
||||
insert: res.code
|
||||
}
|
||||
})
|
||||
stdin.value = res.stdin
|
||||
status.value = res.status
|
||||
stdout.value = res.stdout
|
||||
stderr.value = res.stderr
|
||||
time.value = res.execution_time_ms
|
||||
log.value = res.compile_log
|
||||
if (status.value === 'pending' || status.value === 'running') {
|
||||
let timer = setInterval(() => {
|
||||
getStatus(props.id)
|
||||
if (status.value !== 'pending' && status.value !== 'running') {
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
const handleRun = (isRun: boolean) => {
|
||||
console.log('run')
|
||||
fetch(backend + '/api/pastes', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
code: view.state.doc.toString(),
|
||||
stdin: isRun ? stdin.value : '',
|
||||
language: 'c++20',
|
||||
run: isRun
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res.message === 'Created') {
|
||||
const pasteid = res.paste_id;
|
||||
router.push({
|
||||
name: 'code',
|
||||
params: {
|
||||
id: pasteid
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
localStorage.setItem('code', view.state.doc.toString())
|
||||
localStorage.setItem('stdin', stdin.value)
|
||||
}, 1000)
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
21
web/src/router/index.js
Normal file
21
web/src/router/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
createRouter,
|
||||
createWebHistory
|
||||
} from 'vue-router'
|
||||
import CodePage from '../pages/CodePage.vue'
|
||||
const routes = [{
|
||||
path: '/code/:id?',
|
||||
name: 'code',
|
||||
component: CodePage,
|
||||
props: true
|
||||
}, {
|
||||
path: '/',
|
||||
redirect: '/code',
|
||||
}]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
25
web/src/style.css
Normal file
25
web/src/style.css
Normal file
@@ -0,0 +1,25 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* 整个滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 4px; /* 滚动条宽度 */
|
||||
height: 4px; /* 水平滚动条高度 */
|
||||
}
|
||||
|
||||
/* 滚动条轨道 */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1c1917; /* 轨道颜色 */
|
||||
border-radius: 10px; /* 圆角 */
|
||||
}
|
||||
|
||||
/* 滚动条滑块 */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #888; /* 滑块颜色 */
|
||||
border-radius: 10px; /* 圆角 */
|
||||
}
|
||||
|
||||
/* 鼠标悬停在滑块上 */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555; /* 悬停时颜色变深 */
|
||||
cursor: pointer;
|
||||
}
|
||||
Reference in New Issue
Block a user