update
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>中文Blockly Python代码生成平台</title>
|
<title>草履虫</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
32
package-lock.json
generated
32
package-lock.json
generated
@@ -8,8 +8,8 @@
|
|||||||
"name": "blockly",
|
"name": "blockly",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blockly/theme-modern": "^7.0.4",
|
|
||||||
"blockly": "^12.3.1",
|
"blockly": "^12.3.1",
|
||||||
|
"skulpt": "^1.2.0",
|
||||||
"vue": "^3.5.26"
|
"vue": "^3.5.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -80,18 +80,6 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@blockly/theme-modern": {
|
|
||||||
"version": "7.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-7.0.4.tgz",
|
|
||||||
"integrity": "sha512-34Q/oTA/hpqoPJ/7aUNgWdsjhCeo4530zuzF3NPOagPp9AbtEUwdZIdc82iIN8mhDkZjeeT69wC7OjoSoG3TbA==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.17.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"blockly": "^12.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@csstools/color-helpers": {
|
"node_modules/@csstools/color-helpers": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
|
||||||
@@ -913,6 +901,12 @@
|
|||||||
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/jsbi": {
|
||||||
|
"version": "3.2.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/jsbi/-/jsbi-3.2.5.tgz",
|
||||||
|
"integrity": "sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/jsdom": {
|
"node_modules/jsdom": {
|
||||||
"version": "26.1.0",
|
"version": "26.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
|
||||||
@@ -1409,6 +1403,18 @@
|
|||||||
"node": ">=v12.22.7"
|
"node": ">=v12.22.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/skulpt": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/skulpt/-/skulpt-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-T0cv0sdSOXLlIJTuyXSeYJ3TFdWYSZfX2PcBLpRKrKZ3dTbVQXRMiYudSuko2xzcyMif47DS2ShatCwjCbsSsA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jsbi": "^3.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blockly/theme-modern": "^7.0.4",
|
|
||||||
"blockly": "^12.3.1",
|
"blockly": "^12.3.1",
|
||||||
|
"skulpt": "^1.2.0",
|
||||||
"vue": "^3.5.26"
|
"vue": "^3.5.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
150
src/App.vue
150
src/App.vue
@@ -2,15 +2,20 @@
|
|||||||
import { onBeforeUnmount, onMounted, ref } from "vue"
|
import { onBeforeUnmount, onMounted, ref } from "vue"
|
||||||
import * as Blockly from "blockly/core"
|
import * as Blockly from "blockly/core"
|
||||||
import "blockly/blocks"
|
import "blockly/blocks"
|
||||||
import Theme from "@blockly/theme-modern"
|
|
||||||
import { setBlocklyLocale } from "./blockly/locale"
|
import { setBlocklyLocale } from "./blockly/locale"
|
||||||
import { defineBlocks } from "./blockly/blocks"
|
import { defineBlocks } from "./blockly/blocks"
|
||||||
import { toolbox } from "./blockly/toolbox"
|
import { toolbox } from "./blockly/toolbox"
|
||||||
import { initPythonGenerator } from "./blockly/pythonGenerator"
|
import { initPythonGenerator } from "./blockly/pythonGenerator"
|
||||||
|
import { runPython } from "./blockly/skulptRunner"
|
||||||
|
|
||||||
const workspaceHost = ref<HTMLDivElement | null>(null)
|
const workspaceHost = ref<HTMLDivElement | null>(null)
|
||||||
const codePreview = ref("")
|
const codePreview = ref("")
|
||||||
const statusText = ref("准备就绪")
|
const statusText = ref("准备就绪")
|
||||||
|
const idleStatus = statusText.value
|
||||||
|
const runOutput = ref("")
|
||||||
|
const storageKey = "blockly-workspace"
|
||||||
|
let saveTimer: number | null = null
|
||||||
|
let suppressSave = false
|
||||||
|
|
||||||
let workspace: Blockly.WorkspaceSvg | null = null
|
let workspace: Blockly.WorkspaceSvg | null = null
|
||||||
const generator = initPythonGenerator()
|
const generator = initPythonGenerator()
|
||||||
@@ -21,53 +26,79 @@ const updateCode = () => {
|
|||||||
codePreview.value = code.trim() || "# 在左侧拖入方块生成Python代码"
|
codePreview.value = code.trim() || "# 在左侧拖入方块生成Python代码"
|
||||||
}
|
}
|
||||||
|
|
||||||
// const platformTheme = Blockly.Theme.defineTheme('cnPlatform', {
|
|
||||||
// base: Blockly.Themes.Zelos,
|
|
||||||
// componentStyles: {
|
|
||||||
// workspaceBackgroundColour: '#f7f5ef',
|
|
||||||
// toolboxBackgroundColour: '#f2ebe2',
|
|
||||||
// toolboxForegroundColour: '#28343b',
|
|
||||||
// flyoutBackgroundColour: '#fffaf2',
|
|
||||||
// flyoutOpacity: 0.96,
|
|
||||||
// scrollbarColour: '#c3b7aa',
|
|
||||||
// scrollbarOpacity: 0.7,
|
|
||||||
// },
|
|
||||||
// fontStyle: {
|
|
||||||
// family: '"Noto Sans SC", "Source Han Sans SC", "Microsoft YaHei", sans-serif',
|
|
||||||
// size: 13,
|
|
||||||
// weight: 500,
|
|
||||||
// },
|
|
||||||
// blockStyles: {
|
|
||||||
// logic_blocks: { colourPrimary: '#4f7a8f', colourSecondary: '#406574', colourTertiary: '#2f4b57' },
|
|
||||||
// loop_blocks: { colourPrimary: '#4f8f6d', colourSecondary: '#3f7458', colourTertiary: '#2e5944' },
|
|
||||||
// math_blocks: { colourPrimary: '#c6844a', colourSecondary: '#a96f3c', colourTertiary: '#8a592f' },
|
|
||||||
// text_blocks: { colourPrimary: '#b86a80', colourSecondary: '#9c5668', colourTertiary: '#7d4552' },
|
|
||||||
// list_blocks: { colourPrimary: '#6d6fb8', colourSecondary: '#595a99', colourTertiary: '#444575' },
|
|
||||||
// variable_blocks: { colourPrimary: '#aa5d7b', colourSecondary: '#8c4c64', colourTertiary: '#6f3b4e' },
|
|
||||||
// procedure_blocks: { colourPrimary: '#8b6d52', colourSecondary: '#705640', colourTertiary: '#54402f' },
|
|
||||||
// },
|
|
||||||
// categoryStyles: {
|
|
||||||
// logic_category: { colour: '#4f7a8f' },
|
|
||||||
// loop_category: { colour: '#4f8f6d' },
|
|
||||||
// math_category: { colour: '#c6844a' },
|
|
||||||
// text_category: { colour: '#b86a80' },
|
|
||||||
// list_category: { colour: '#6d6fb8' },
|
|
||||||
// variable_category: { colour: '#aa5d7b' },
|
|
||||||
// procedure_category: { colour: '#8b6d52' },
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
Blockly.svgResize(workspace)
|
Blockly.svgResize(workspace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const saveWorkspace = () => {
|
||||||
|
if (!workspace || suppressSave) return
|
||||||
|
if (saveTimer) {
|
||||||
|
window.clearTimeout(saveTimer)
|
||||||
|
}
|
||||||
|
saveTimer = window.setTimeout(() => {
|
||||||
|
const state = Blockly.serialization.workspaces.save(workspace!)
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(state))
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
|
||||||
|
const restoreWorkspace = () => {
|
||||||
|
if (!workspace) return
|
||||||
|
const raw = localStorage.getItem(storageKey)
|
||||||
|
if (!raw) return
|
||||||
|
try {
|
||||||
|
const state = JSON.parse(raw)
|
||||||
|
suppressSave = true
|
||||||
|
Blockly.serialization.workspaces.load(state, workspace)
|
||||||
|
} catch {
|
||||||
|
localStorage.removeItem(storageKey)
|
||||||
|
} finally {
|
||||||
|
suppressSave = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClearWorkspace = () => {
|
||||||
|
if (!workspace) return
|
||||||
|
suppressSave = true
|
||||||
|
workspace.clear()
|
||||||
|
suppressSave = false
|
||||||
|
localStorage.removeItem(storageKey)
|
||||||
|
updateCode()
|
||||||
|
}
|
||||||
|
|
||||||
const handleRun = () => {
|
const handleRun = () => {
|
||||||
statusText.value = "执行环境尚未接入,可先复制代码到Python环境运行"
|
if (!codePreview.value.trim()) {
|
||||||
window.setTimeout(() => {
|
statusText.value = "No code to run."
|
||||||
statusText.value = "准备就绪"
|
window.setTimeout(() => {
|
||||||
}, 2200)
|
statusText.value = idleStatus
|
||||||
|
}, 1600)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
runOutput.value = ""
|
||||||
|
statusText.value = "Running..."
|
||||||
|
|
||||||
|
runPython(codePreview.value, (text) => {
|
||||||
|
runOutput.value += text
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
if (!runOutput.value) {
|
||||||
|
runOutput.value = ""
|
||||||
|
}
|
||||||
|
statusText.value = "运行成功"
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const message = error instanceof Error ? error.message : String(error)
|
||||||
|
runOutput.value =
|
||||||
|
(runOutput.value ? `${runOutput.value}\n` : "") + `Error: ${message}`
|
||||||
|
statusText.value = "运行失败"
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
statusText.value = idleStatus
|
||||||
|
}, 2000)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCopy = async () => {
|
const handleCopy = async () => {
|
||||||
@@ -88,10 +119,10 @@ const handleDownload = () => {
|
|||||||
const url = URL.createObjectURL(blob)
|
const url = URL.createObjectURL(blob)
|
||||||
const link = document.createElement("a")
|
const link = document.createElement("a")
|
||||||
link.href = url
|
link.href = url
|
||||||
link.download = "blockly_program.py"
|
link.download = "demo.py"
|
||||||
link.click()
|
link.click()
|
||||||
URL.revokeObjectURL(url)
|
URL.revokeObjectURL(url)
|
||||||
statusText.value = "已导出为 .py 文件"
|
statusText.value = "已导出代码文件"
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
statusText.value = "准备就绪"
|
statusText.value = "准备就绪"
|
||||||
}, 1800)
|
}, 1800)
|
||||||
@@ -112,12 +143,13 @@ onMounted(() => {
|
|||||||
minScale: 0.5,
|
minScale: 0.5,
|
||||||
},
|
},
|
||||||
trashcan: true,
|
trashcan: true,
|
||||||
renderer: "geras",
|
renderer: "zelos",
|
||||||
theme: Theme,
|
theme: Blockly.Themes.Zelos,
|
||||||
media: "/blockly-media/",
|
|
||||||
})
|
})
|
||||||
workspace.addChangeListener(updateCode)
|
workspace.addChangeListener(updateCode)
|
||||||
|
workspace.addChangeListener(saveWorkspace)
|
||||||
updateCode()
|
updateCode()
|
||||||
|
restoreWorkspace()
|
||||||
window.addEventListener("resize", handleResize)
|
window.addEventListener("resize", handleResize)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -127,6 +159,9 @@ onBeforeUnmount(() => {
|
|||||||
workspace.dispose()
|
workspace.dispose()
|
||||||
workspace = null
|
workspace = null
|
||||||
}
|
}
|
||||||
|
if (saveTimer) {
|
||||||
|
window.clearTimeout(saveTimer)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -134,18 +169,15 @@ onBeforeUnmount(() => {
|
|||||||
<div class="app-shell">
|
<div class="app-shell">
|
||||||
<header class="app-header">
|
<header class="app-header">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
<span class="brand-badge">Blockly</span>
|
<span class="brand-badge">草履虫</span>
|
||||||
<div>
|
<div>
|
||||||
<h1>中文Python可视化编程平台</h1>
|
<h1>拖拽式代码生成网页</h1>
|
||||||
<p>拖拽方块,实时生成可执行的Python代码</p>
|
<p>拖拽方块,实时生成可执行的 Python 代码</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<button class="btn ghost" type="button" @click="handleCopy">
|
<button class="btn ghost" type="button" @click="handleClearWorkspace">
|
||||||
复制代码
|
清除
|
||||||
</button>
|
|
||||||
<button class="btn ghost" type="button" @click="handleDownload">
|
|
||||||
导出 .py
|
|
||||||
</button>
|
</button>
|
||||||
<button class="btn primary" type="button" @click="handleRun">
|
<button class="btn primary" type="button" @click="handleRun">
|
||||||
运行
|
运行
|
||||||
@@ -160,9 +192,6 @@ onBeforeUnmount(() => {
|
|||||||
<h2>工作区</h2>
|
<h2>工作区</h2>
|
||||||
<p>通过分类工具箱选择方块,构建你的程序</p>
|
<p>通过分类工具箱选择方块,构建你的程序</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-tip">
|
|
||||||
<span>实时反馈</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-stage" ref="workspaceHost"></div>
|
<div class="workspace-stage" ref="workspaceHost"></div>
|
||||||
</section>
|
</section>
|
||||||
@@ -170,10 +199,9 @@ onBeforeUnmount(() => {
|
|||||||
<aside class="side-panel">
|
<aside class="side-panel">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<div>
|
<div>
|
||||||
<h2>Python代码</h2>
|
<h2>Python 代码</h2>
|
||||||
<p>符合PEP8风格,支持直接导出</p>
|
<p>由左侧的积木块自动生成</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-tip">预览</div>
|
|
||||||
</div>
|
</div>
|
||||||
<pre class="code-preview"><code>{{ codePreview }}</code></pre>
|
<pre class="code-preview"><code>{{ codePreview }}</code></pre>
|
||||||
<div class="status-bar">
|
<div class="status-bar">
|
||||||
@@ -187,6 +215,8 @@ onBeforeUnmount(() => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="output-label">运行结果</div>
|
||||||
|
<pre class="code-preview output-preview"><code>{{ runOutput }}</code></pre>
|
||||||
</aside>
|
</aside>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ import * as Blockly from 'blockly'
|
|||||||
import * as zhHans from 'blockly/msg/zh-hans'
|
import * as zhHans from 'blockly/msg/zh-hans'
|
||||||
|
|
||||||
export const setBlocklyLocale = () => {
|
export const setBlocklyLocale = () => {
|
||||||
Blockly.setLocale(zhHans)
|
Blockly.setLocale(zhHans as unknown as Record<string, string>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { pythonGenerator } from 'blockly/python'
|
import { pythonGenerator, Order } from 'blockly/python'
|
||||||
import type { Block } from 'blockly/core'
|
import type { Block } from 'blockly/core'
|
||||||
|
|
||||||
export const initPythonGenerator = () => {
|
export const initPythonGenerator = () => {
|
||||||
pythonGenerator.forBlock['cn_print'] = (block: Block, generator) => {
|
pythonGenerator.forBlock['cn_print'] = (block: Block, generator) => {
|
||||||
const text = generator.valueToCode(block, 'TEXT', generator.ORDER_NONE) || "''"
|
const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"
|
||||||
return `print(${text})\n`
|
return `print(${text})\n`
|
||||||
}
|
}
|
||||||
|
|
||||||
pythonGenerator.forBlock['cn_input'] = (block: Block, generator) => {
|
pythonGenerator.forBlock['cn_input'] = (block: Block, generator) => {
|
||||||
const prompt = generator.valueToCode(block, 'PROMPT', generator.ORDER_NONE)
|
const prompt = generator.valueToCode(block, 'PROMPT', Order.NONE)
|
||||||
const code = prompt ? `input(${prompt})` : 'input()'
|
const code = prompt ? `input(${prompt})` : 'input()'
|
||||||
return [code, generator.ORDER_FUNCTION_CALL]
|
return [code, Order.FUNCTION_CALL]
|
||||||
}
|
}
|
||||||
|
|
||||||
pythonGenerator.addReservedWords('input,print')
|
pythonGenerator.addReservedWords('input,print')
|
||||||
|
|||||||
34
src/blockly/skulptRunner.ts
Normal file
34
src/blockly/skulptRunner.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
//@ts-ignore
|
||||||
|
import Sk from "skulpt"
|
||||||
|
|
||||||
|
type OutputHandler = (text: string) => void
|
||||||
|
|
||||||
|
const readBuiltinFile = (path: string) => {
|
||||||
|
if (!Sk?.builtinFiles?.files?.[path]) {
|
||||||
|
throw new Error(`Skulpt file not found: ${path}`)
|
||||||
|
}
|
||||||
|
return Sk.builtinFiles.files[path]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const runPython = async (code: string, onOutput: OutputHandler) => {
|
||||||
|
if (!Sk) {
|
||||||
|
throw new Error("Skulpt failed to load.")
|
||||||
|
}
|
||||||
|
|
||||||
|
Sk.configure({
|
||||||
|
output: (text: string) => onOutput(text),
|
||||||
|
read: readBuiltinFile,
|
||||||
|
inputfun: (prompt: string) => {
|
||||||
|
const response = window.prompt(prompt || "请输入")
|
||||||
|
if (response === null) {
|
||||||
|
throw new Error("用户取消输入")
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
inputfunTakesPrompt: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
await Sk.misceval.asyncToPromise(() =>
|
||||||
|
Sk.importMainWithBody("<stdin>", false, code, true)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -78,11 +78,5 @@ export const toolbox = {
|
|||||||
colour: '#A65D7B',
|
colour: '#A65D7B',
|
||||||
custom: 'VARIABLE',
|
custom: 'VARIABLE',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
kind: 'category',
|
|
||||||
name: '函数',
|
|
||||||
colour: '#8B6D52',
|
|
||||||
custom: 'PROCEDURE',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,15 +115,6 @@ body {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-tip {
|
|
||||||
background: rgba(31, 122, 140, 0.12);
|
|
||||||
color: var(--accent-strong);
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-stage {
|
.workspace-stage {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 520px;
|
min-height: 520px;
|
||||||
@@ -157,6 +148,21 @@ body {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.output-label {
|
||||||
|
padding: 10px 18px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
background: rgba(31, 122, 140, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-preview {
|
||||||
|
flex: 0 0 160px;
|
||||||
|
background: #0a1519;
|
||||||
|
color: #d7f1f6;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
@@ -206,6 +212,11 @@ body {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blocklyToolboxCategory {
|
||||||
|
height: 40px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 980px) {
|
@media (max-width: 980px) {
|
||||||
.app-shell {
|
.app-shell {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
Reference in New Issue
Block a user