This commit is contained in:
@@ -32,7 +32,6 @@ export const status = ref(Status.NotStarted)
|
||||
export const loading = ref(false)
|
||||
export const turtleRunId = ref(0)
|
||||
export const size = ref(0)
|
||||
export const debug = ref(false)
|
||||
|
||||
watch(size, (value: number) => {
|
||||
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>
|
||||
import copyTextToClipboard from "copy-text-to-clipboard"
|
||||
import { useMessage } from "naive-ui"
|
||||
import { computed, watch, useTemplateRef } from "vue"
|
||||
import { marked } from "marked"
|
||||
import { debug as debugApi } from "../api"
|
||||
// @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())
|
||||
import { code } from "../composables/code"
|
||||
import CodeSection from "./CodeSection.vue"
|
||||
import InputSection from "./InputSection.vue"
|
||||
import OutputSection from "./OutputSection.vue"
|
||||
import TurtleSection from "./TurtleSection.vue"
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-layout-content class="container">
|
||||
<n-split direction="horizontal" :min="1 / 3" :max="4 / 5">
|
||||
<template #1>
|
||||
<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="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>
|
||||
<CodeSection />
|
||||
</template>
|
||||
<template #2>
|
||||
<n-split
|
||||
v-if="code.language !== 'turtle'"
|
||||
direction="vertical"
|
||||
:default-size="1 / 3"
|
||||
:min="1 / 5"
|
||||
:max="3 / 5"
|
||||
>
|
||||
<template #1>
|
||||
<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>
|
||||
<InputSection />
|
||||
</template>
|
||||
<template #2>
|
||||
<CodeEditor
|
||||
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>
|
||||
<OutputSection />
|
||||
</template>
|
||||
</n-split>
|
||||
<TurtleSection v-else />
|
||||
</template>
|
||||
</n-split>
|
||||
</n-layout-content>
|
||||
@@ -186,163 +37,4 @@ watch(turtleRunId, () => runSkulptTurtle())
|
||||
.container {
|
||||
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>
|
||||
|
||||
@@ -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 />
|
||||
</n-modal>
|
||||
<n-modal
|
||||
v-model:show="debug"
|
||||
preset="card"
|
||||
style="width: 700px"
|
||||
:mask-closable="false"
|
||||
title="可视化调试(测试版)"
|
||||
>
|
||||
<Debug />
|
||||
</n-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useMagicKeys, whenever } from "@vueuse/core"
|
||||
import { ref } from "vue"
|
||||
import { debug, run } from "../composables/code"
|
||||
import { run } from "../composables/code"
|
||||
import Content from "./Content.vue"
|
||||
import Debug from "./Debug.vue"
|
||||
import File from "./File.vue"
|
||||
import Header from "./Header.vue"
|
||||
import Query from "./Query.vue"
|
||||
|
||||
Reference in New Issue
Block a user