This commit is contained in:
@@ -32,7 +32,6 @@ export const status = ref(Status.NotStarted)
|
|||||||
export const loading = ref(false)
|
export const loading = ref(false)
|
||||||
export const turtleRunId = ref(0)
|
export const turtleRunId = ref(0)
|
||||||
export const size = ref(0)
|
export const size = ref(0)
|
||||||
export const debug = ref(false)
|
|
||||||
|
|
||||||
watch(size, (value: number) => {
|
watch(size, (value: number) => {
|
||||||
cache.fontsize.value = value
|
cache.fontsize.value = value
|
||||||
|
|||||||
172
src/desktop/AnalysisPanel.vue
Normal file
172
src/desktop/AnalysisPanel.vue
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { marked } from "marked"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
analysis: string
|
||||||
|
loading: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-spin :show="loading">
|
||||||
|
<div class="analysisPanel" v-html="marked.parse(analysis)"></div>
|
||||||
|
</n-spin>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.analysisPanel {
|
||||||
|
width: 400px;
|
||||||
|
min-height: 60px;
|
||||||
|
max-height: calc(100vh - 200px);
|
||||||
|
overflow: auto;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 简洁 Markdown 样式 */
|
||||||
|
.analysisPanel :deep(h1),
|
||||||
|
.analysisPanel :deep(h2),
|
||||||
|
.analysisPanel :deep(h3),
|
||||||
|
.analysisPanel :deep(h4),
|
||||||
|
.analysisPanel :deep(h5),
|
||||||
|
.analysisPanel :deep(h6) {
|
||||||
|
margin: 16px 0 8px 0;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(h1) {
|
||||||
|
font-size: 1.5em;
|
||||||
|
border-bottom: 2px solid #e5e7eb;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(h2) {
|
||||||
|
font-size: 1.3em;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(h3) {
|
||||||
|
font-size: 1.1em;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(p) {
|
||||||
|
margin: 12px 0;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(ul),
|
||||||
|
.analysisPanel :deep(ol) {
|
||||||
|
margin: 12px 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(li) {
|
||||||
|
margin: 4px 0;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(strong) {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(em) {
|
||||||
|
font-style: italic;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 简洁代码块样式 */
|
||||||
|
.analysisPanel :deep(pre) {
|
||||||
|
background: #f8f9fa;
|
||||||
|
color: #24292f;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 12px 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
border: 1px solid #d0d7de;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(code) {
|
||||||
|
background: #f1f3f4;
|
||||||
|
color: #d63384;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(pre code) {
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 简洁引用块样式 */
|
||||||
|
.analysisPanel :deep(blockquote) {
|
||||||
|
border-left: 4px solid #d0d7de;
|
||||||
|
background: #f6f8fa;
|
||||||
|
margin: 16px 0;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
color: #656d76;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 简洁表格样式 */
|
||||||
|
.analysisPanel :deep(table) {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 16px 0;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #d0d7de;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(th),
|
||||||
|
.analysisPanel :deep(td) {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #d0d7de;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(th) {
|
||||||
|
background: #f6f8fa;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #24292f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(td) {
|
||||||
|
color: #24292f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 简洁链接样式 */
|
||||||
|
.analysisPanel :deep(a) {
|
||||||
|
color: #0969da;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysisPanel :deep(a:hover) {
|
||||||
|
color: #0969da;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 简洁分割线 */
|
||||||
|
.analysisPanel :deep(hr) {
|
||||||
|
border: none;
|
||||||
|
height: 1px;
|
||||||
|
background: #d0d7de;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
44
src/desktop/CodeSection.vue
Normal file
44
src/desktop/CodeSection.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import copyTextToClipboard from "copy-text-to-clipboard"
|
||||||
|
import { useMessage } from "naive-ui"
|
||||||
|
import CodeEditor from "../components/CodeEditor.vue"
|
||||||
|
import { code, input, reset, size } from "../composables/code"
|
||||||
|
import { debug } from "../api"
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
function copy() {
|
||||||
|
copyTextToClipboard(code.value)
|
||||||
|
message.success("已经复制好了")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDebug() {
|
||||||
|
const inputs = input.value ? input.value.split("\n") : []
|
||||||
|
const res = await debug(code.value, inputs)
|
||||||
|
console.log(res.data)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CodeEditor
|
||||||
|
label="代码区"
|
||||||
|
icon="streamline-emojis:lemon"
|
||||||
|
:font-size="size"
|
||||||
|
v-model="code.value"
|
||||||
|
:language="code.language"
|
||||||
|
>
|
||||||
|
<template #actions>
|
||||||
|
<n-button
|
||||||
|
quaternary
|
||||||
|
type="error"
|
||||||
|
:disabled="!code.value"
|
||||||
|
v-if="code.language === 'python'"
|
||||||
|
@click="handleDebug"
|
||||||
|
>
|
||||||
|
调试
|
||||||
|
</n-button>
|
||||||
|
<n-button quaternary type="primary" @click="copy">复制</n-button>
|
||||||
|
<n-button quaternary @click="reset">清空</n-button>
|
||||||
|
</template>
|
||||||
|
</CodeEditor>
|
||||||
|
</template>
|
||||||
@@ -1,182 +1,33 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import copyTextToClipboard from "copy-text-to-clipboard"
|
import { code } from "../composables/code"
|
||||||
import { useMessage } from "naive-ui"
|
import CodeSection from "./CodeSection.vue"
|
||||||
import { computed, watch, useTemplateRef } from "vue"
|
import InputSection from "./InputSection.vue"
|
||||||
import { marked } from "marked"
|
import OutputSection from "./OutputSection.vue"
|
||||||
import { debug as debugApi } from "../api"
|
import TurtleSection from "./TurtleSection.vue"
|
||||||
// @ts-ignore
|
|
||||||
import * as Sk from "skulpt"
|
|
||||||
import CodeEditor from "../components/CodeEditor.vue"
|
|
||||||
import {
|
|
||||||
analysis,
|
|
||||||
loading,
|
|
||||||
getAIAnalysis,
|
|
||||||
showAnalysis,
|
|
||||||
} from "../composables/analysis"
|
|
||||||
import {
|
|
||||||
clearInput,
|
|
||||||
code,
|
|
||||||
debug,
|
|
||||||
input,
|
|
||||||
output,
|
|
||||||
reset,
|
|
||||||
size,
|
|
||||||
status,
|
|
||||||
turtleRunId,
|
|
||||||
} from "../composables/code"
|
|
||||||
import { Status } from "../types"
|
|
||||||
|
|
||||||
const showInputClearBtn = computed(() => !!input.value)
|
|
||||||
const message = useMessage()
|
|
||||||
|
|
||||||
function copy() {
|
|
||||||
copyTextToClipboard(code.value)
|
|
||||||
message.success("已经复制好了")
|
|
||||||
}
|
|
||||||
function handleDebug() {
|
|
||||||
debug.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleDebugNew() {
|
|
||||||
const inputs = input.value ? input.value.split("\n") : []
|
|
||||||
const res = await debugApi(code.value, inputs)
|
|
||||||
console.log(res.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
const turtleCanvas = useTemplateRef("turtle")
|
|
||||||
|
|
||||||
function builtinRead(x: any) {
|
|
||||||
if (
|
|
||||||
Sk.builtinFiles === undefined ||
|
|
||||||
Sk.builtinFiles["files"][x] === undefined
|
|
||||||
)
|
|
||||||
throw "文件没有找到:'" + x + "'"
|
|
||||||
return Sk.builtinFiles["files"][x]
|
|
||||||
}
|
|
||||||
|
|
||||||
function runSkulptTurtle() {
|
|
||||||
const canvas = turtleCanvas.value
|
|
||||||
if (!canvas) return
|
|
||||||
canvas.innerHTML = ""
|
|
||||||
Sk.configure({
|
|
||||||
output: console.log,
|
|
||||||
read: builtinRead,
|
|
||||||
inputfun: function () {
|
|
||||||
return input.value
|
|
||||||
},
|
|
||||||
__future__: Sk.python3,
|
|
||||||
})
|
|
||||||
Sk.TurtleGraphics = {
|
|
||||||
target: canvas,
|
|
||||||
width: canvas.clientWidth,
|
|
||||||
height: canvas.clientHeight,
|
|
||||||
}
|
|
||||||
Sk.misceval
|
|
||||||
.asyncToPromise(function () {
|
|
||||||
return Sk.importMainWithBody("<stdin>", false, code.value, true)
|
|
||||||
})
|
|
||||||
.catch((err: any) => {
|
|
||||||
output.value += String(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(turtleRunId, () => runSkulptTurtle())
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-layout-content class="container">
|
<n-layout-content class="container">
|
||||||
<n-split direction="horizontal" :min="1 / 3" :max="4 / 5">
|
<n-split direction="horizontal" :min="1 / 3" :max="4 / 5">
|
||||||
<template #1>
|
<template #1>
|
||||||
<CodeEditor
|
<CodeSection />
|
||||||
label="代码区"
|
|
||||||
icon="streamline-emojis:lemon"
|
|
||||||
:font-size="size"
|
|
||||||
v-model="code.value"
|
|
||||||
:language="code.language"
|
|
||||||
>
|
|
||||||
<template #actions>
|
|
||||||
<n-button
|
|
||||||
quaternary
|
|
||||||
type="error"
|
|
||||||
:disabled="!code.value"
|
|
||||||
v-if="code.language === 'python'"
|
|
||||||
@click="handleDebug"
|
|
||||||
>
|
|
||||||
调试
|
|
||||||
</n-button>
|
|
||||||
|
|
||||||
<n-button
|
|
||||||
quaternary
|
|
||||||
type="error"
|
|
||||||
:disabled="!code.value"
|
|
||||||
v-if="false && code.language === 'python'"
|
|
||||||
@click="handleDebugNew"
|
|
||||||
>
|
|
||||||
调试(新)
|
|
||||||
</n-button>
|
|
||||||
<n-button quaternary type="primary" @click="copy">复制</n-button>
|
|
||||||
<n-button quaternary @click="reset">清空</n-button>
|
|
||||||
</template>
|
|
||||||
</CodeEditor>
|
|
||||||
</template>
|
</template>
|
||||||
<template #2>
|
<template #2>
|
||||||
<n-split
|
<n-split
|
||||||
|
v-if="code.language !== 'turtle'"
|
||||||
direction="vertical"
|
direction="vertical"
|
||||||
:default-size="1 / 3"
|
:default-size="1 / 3"
|
||||||
:min="1 / 5"
|
:min="1 / 5"
|
||||||
:max="3 / 5"
|
:max="3 / 5"
|
||||||
>
|
>
|
||||||
<template #1>
|
<template #1>
|
||||||
<CodeEditor
|
<InputSection />
|
||||||
icon="streamline-emojis:four-leaf-clover"
|
|
||||||
label="输入框"
|
|
||||||
:font-size="size"
|
|
||||||
v-model="input"
|
|
||||||
>
|
|
||||||
<template #actions>
|
|
||||||
<n-button
|
|
||||||
quaternary
|
|
||||||
type="primary"
|
|
||||||
@click="clearInput"
|
|
||||||
v-if="showInputClearBtn"
|
|
||||||
>
|
|
||||||
清空
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
</CodeEditor>
|
|
||||||
</template>
|
</template>
|
||||||
<template #2>
|
<template #2>
|
||||||
<CodeEditor
|
<OutputSection />
|
||||||
v-if="code.language !== 'turtle'"
|
|
||||||
icon="streamline-emojis:hibiscus"
|
|
||||||
label="输出框"
|
|
||||||
v-model="output"
|
|
||||||
readonly
|
|
||||||
:font-size="size"
|
|
||||||
>
|
|
||||||
<template #actions>
|
|
||||||
<n-tag v-if="status === Status.Accepted" type="success">
|
|
||||||
运行成功
|
|
||||||
</n-tag>
|
|
||||||
<n-tag v-if="showAnalysis" type="warning">运行失败</n-tag>
|
|
||||||
<n-popover v-if="showAnalysis" trigger="click" placement="left">
|
|
||||||
<template #trigger>
|
|
||||||
<n-button quaternary type="error" @click="getAIAnalysis">
|
|
||||||
推测原因
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
<n-spin :show="loading">
|
|
||||||
<div
|
|
||||||
class="analysisPanel"
|
|
||||||
v-html="marked.parse(analysis)"
|
|
||||||
></div>
|
|
||||||
</n-spin>
|
|
||||||
</n-popover>
|
|
||||||
</template>
|
|
||||||
</CodeEditor>
|
|
||||||
<div v-else ref="turtle" class="canvas"></div>
|
|
||||||
</template>
|
</template>
|
||||||
</n-split>
|
</n-split>
|
||||||
|
<TurtleSection v-else />
|
||||||
</template>
|
</template>
|
||||||
</n-split>
|
</n-split>
|
||||||
</n-layout-content>
|
</n-layout-content>
|
||||||
@@ -186,163 +37,4 @@ watch(turtleRunId, () => runSkulptTurtle())
|
|||||||
.container {
|
.container {
|
||||||
height: calc(100vh - 60px);
|
height: calc(100vh - 60px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.canvas {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel {
|
|
||||||
width: 400px;
|
|
||||||
min-height: 60px;
|
|
||||||
max-height: calc(100vh - 200px);
|
|
||||||
overflow: auto;
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #374151;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 简洁 Markdown 样式 */
|
|
||||||
.analysisPanel :deep(h1),
|
|
||||||
.analysisPanel :deep(h2),
|
|
||||||
.analysisPanel :deep(h3),
|
|
||||||
.analysisPanel :deep(h4),
|
|
||||||
.analysisPanel :deep(h5),
|
|
||||||
.analysisPanel :deep(h6) {
|
|
||||||
margin: 16px 0 8px 0;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1f2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(h1) {
|
|
||||||
font-size: 1.5em;
|
|
||||||
border-bottom: 2px solid #e5e7eb;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(h2) {
|
|
||||||
font-size: 1.3em;
|
|
||||||
color: #374151;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(h3) {
|
|
||||||
font-size: 1.1em;
|
|
||||||
color: #4b5563;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(p) {
|
|
||||||
margin: 12px 0;
|
|
||||||
color: #374151;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(ul),
|
|
||||||
.analysisPanel :deep(ol) {
|
|
||||||
margin: 12px 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(li) {
|
|
||||||
margin: 4px 0;
|
|
||||||
color: #374151;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(strong) {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1f2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(em) {
|
|
||||||
font-style: italic;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 简洁代码块样式 */
|
|
||||||
.analysisPanel :deep(pre) {
|
|
||||||
background: #f8f9fa;
|
|
||||||
color: #24292f;
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 12px 0;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.5;
|
|
||||||
border: 1px solid #d0d7de;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(code) {
|
|
||||||
background: #f1f3f4;
|
|
||||||
color: #d63384;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(pre code) {
|
|
||||||
background: transparent;
|
|
||||||
color: inherit;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 简洁引用块样式 */
|
|
||||||
.analysisPanel :deep(blockquote) {
|
|
||||||
border-left: 4px solid #d0d7de;
|
|
||||||
background: #f6f8fa;
|
|
||||||
margin: 16px 0;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-radius: 0 6px 6px 0;
|
|
||||||
color: #656d76;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 简洁表格样式 */
|
|
||||||
.analysisPanel :deep(table) {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin: 16px 0;
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 6px;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid #d0d7de;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(th),
|
|
||||||
.analysisPanel :deep(td) {
|
|
||||||
padding: 12px;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #d0d7de;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(th) {
|
|
||||||
background: #f6f8fa;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #24292f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(td) {
|
|
||||||
color: #24292f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 简洁链接样式 */
|
|
||||||
.analysisPanel :deep(a) {
|
|
||||||
color: #0969da;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.analysisPanel :deep(a:hover) {
|
|
||||||
color: #0969da;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 简洁分割线 */
|
|
||||||
.analysisPanel :deep(hr) {
|
|
||||||
border: none;
|
|
||||||
height: 1px;
|
|
||||||
background: #d0d7de;
|
|
||||||
margin: 16px 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="loading" v-if="loading">正在加载中...(第一次打开会有点慢)</div>
|
|
||||||
<div v-if="!loading">
|
|
||||||
<p class="tip">提醒:</p>
|
|
||||||
<p>1. 点击【下一步】开始调试(也可以拖动进度条)</p>
|
|
||||||
<p>
|
|
||||||
2. 点击
|
|
||||||
<n-button text type="primary" @click="close">修改代码</n-button>
|
|
||||||
完成修改后可再次调试
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<iframe
|
|
||||||
width="100%"
|
|
||||||
height="360"
|
|
||||||
frameborder="0"
|
|
||||||
:src="src"
|
|
||||||
ref="main"
|
|
||||||
></iframe>
|
|
||||||
</template>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import qs from "query-string"
|
|
||||||
import { onMounted, ref, useTemplateRef } from "vue"
|
|
||||||
import { code, debug } from "../composables/code"
|
|
||||||
import { useDark } from "@vueuse/core"
|
|
||||||
|
|
||||||
const src = ref("")
|
|
||||||
const loading = ref(true)
|
|
||||||
const main = useTemplateRef("main")
|
|
||||||
const isDark = useDark()
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const url = import.meta.env.PUBLIC_PYVIZ_URL
|
|
||||||
const base = url + "/iframe-embed.html"
|
|
||||||
|
|
||||||
const part1 = qs.stringify({
|
|
||||||
code: code.value,
|
|
||||||
codeDivWidth: 300,
|
|
||||||
})
|
|
||||||
const part2 =
|
|
||||||
"&cumulative=false&curInstr=0&heapPrimitives=nevernest&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=true&"
|
|
||||||
const part3 = qs.stringify({
|
|
||||||
dark: isDark.value,
|
|
||||||
})
|
|
||||||
const query = part1 + part2 + part3
|
|
||||||
src.value = base + "#" + query
|
|
||||||
|
|
||||||
main.value!.addEventListener("load", () => {
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
function close() {
|
|
||||||
debug.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
|
||||||
.loading {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
27
src/desktop/InputSection.vue
Normal file
27
src/desktop/InputSection.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from "vue"
|
||||||
|
import CodeEditor from "../components/CodeEditor.vue"
|
||||||
|
import { clearInput, input, size } from "../composables/code"
|
||||||
|
|
||||||
|
const showInputClearBtn = computed(() => !!input.value)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CodeEditor
|
||||||
|
icon="streamline-emojis:four-leaf-clover"
|
||||||
|
label="输入框"
|
||||||
|
:font-size="size"
|
||||||
|
v-model="input"
|
||||||
|
>
|
||||||
|
<template #actions>
|
||||||
|
<n-button
|
||||||
|
quaternary
|
||||||
|
type="primary"
|
||||||
|
@click="clearInput"
|
||||||
|
v-if="showInputClearBtn"
|
||||||
|
>
|
||||||
|
清空
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</CodeEditor>
|
||||||
|
</template>
|
||||||
35
src/desktop/OutputSection.vue
Normal file
35
src/desktop/OutputSection.vue
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import CodeEditor from "../components/CodeEditor.vue"
|
||||||
|
import { output, size, status } from "../composables/code"
|
||||||
|
import { Status } from "../types"
|
||||||
|
import {
|
||||||
|
analysis,
|
||||||
|
loading,
|
||||||
|
getAIAnalysis,
|
||||||
|
showAnalysis,
|
||||||
|
} from "../composables/analysis"
|
||||||
|
import AnalysisPanel from "./AnalysisPanel.vue"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CodeEditor
|
||||||
|
icon="streamline-emojis:hibiscus"
|
||||||
|
label="输出框"
|
||||||
|
v-model="output"
|
||||||
|
readonly
|
||||||
|
:font-size="size"
|
||||||
|
>
|
||||||
|
<template #actions>
|
||||||
|
<n-tag v-if="status === Status.Accepted" type="success"> 运行成功 </n-tag>
|
||||||
|
<n-tag v-if="showAnalysis" type="warning">运行失败</n-tag>
|
||||||
|
<n-popover v-if="showAnalysis" trigger="click" placement="left">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button quaternary type="error" @click="getAIAnalysis">
|
||||||
|
推测原因
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
<AnalysisPanel :analysis="analysis" :loading="loading" />
|
||||||
|
</n-popover>
|
||||||
|
</template>
|
||||||
|
</CodeEditor>
|
||||||
|
</template>
|
||||||
56
src/desktop/TurtleSection.vue
Normal file
56
src/desktop/TurtleSection.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useTemplateRef, watch } from "vue"
|
||||||
|
// @ts-ignore
|
||||||
|
import * as Sk from "skulpt"
|
||||||
|
import { code, input, output, turtleRunId } from "../composables/code"
|
||||||
|
|
||||||
|
const turtleCanvas = useTemplateRef("turtle")
|
||||||
|
|
||||||
|
function builtinRead(x: any) {
|
||||||
|
if (
|
||||||
|
Sk.builtinFiles === undefined ||
|
||||||
|
Sk.builtinFiles["files"][x] === undefined
|
||||||
|
)
|
||||||
|
throw "文件没有找到:'" + x + "'"
|
||||||
|
return Sk.builtinFiles["files"][x]
|
||||||
|
}
|
||||||
|
|
||||||
|
function runSkulptTurtle() {
|
||||||
|
const canvas = turtleCanvas.value
|
||||||
|
if (!canvas) return
|
||||||
|
canvas.innerHTML = ""
|
||||||
|
Sk.configure({
|
||||||
|
output: console.log,
|
||||||
|
read: builtinRead,
|
||||||
|
inputfun: function () {
|
||||||
|
return input.value
|
||||||
|
},
|
||||||
|
__future__: Sk.python3,
|
||||||
|
})
|
||||||
|
Sk.TurtleGraphics = {
|
||||||
|
target: canvas,
|
||||||
|
width: canvas.clientWidth,
|
||||||
|
height: canvas.clientHeight,
|
||||||
|
}
|
||||||
|
Sk.misceval
|
||||||
|
.asyncToPromise(function () {
|
||||||
|
return Sk.importMainWithBody("<stdin>", false, code.value, true)
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
output.value += String(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(turtleRunId, () => runSkulptTurtle())
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="turtle" class="canvas"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -19,22 +19,12 @@
|
|||||||
>
|
>
|
||||||
<Query />
|
<Query />
|
||||||
</n-modal>
|
</n-modal>
|
||||||
<n-modal
|
|
||||||
v-model:show="debug"
|
|
||||||
preset="card"
|
|
||||||
style="width: 700px"
|
|
||||||
:mask-closable="false"
|
|
||||||
title="可视化调试(测试版)"
|
|
||||||
>
|
|
||||||
<Debug />
|
|
||||||
</n-modal>
|
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useMagicKeys, whenever } from "@vueuse/core"
|
import { useMagicKeys, whenever } from "@vueuse/core"
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import { debug, run } from "../composables/code"
|
import { run } from "../composables/code"
|
||||||
import Content from "./Content.vue"
|
import Content from "./Content.vue"
|
||||||
import Debug from "./Debug.vue"
|
|
||||||
import File from "./File.vue"
|
import File from "./File.vue"
|
||||||
import Header from "./Header.vue"
|
import Header from "./Header.vue"
|
||||||
import Query from "./Query.vue"
|
import Query from "./Query.vue"
|
||||||
|
|||||||
Reference in New Issue
Block a user