feat:部署前配置
This commit is contained in:
@@ -32,8 +32,8 @@ const StageWrapperCppClang = () => {
|
||||
const [wasmModule, setWasmModule] = useState(null);
|
||||
const [syntaxErrorModalVisible, setSyntaxErrorModalVisible] = useState(false);
|
||||
const [syntaxErrors, setSyntaxErrors] = useState([]);
|
||||
const [apiDrawerOpen, setApiDrawerOpen] = useState(false);
|
||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||
const [apiSelectedCategory, setApiSelectedCategory] = useState('player');
|
||||
const [isApiPanelReady, setIsApiPanelReady] = useState(() => window.iscreate === 1);
|
||||
const [overlayActive, setOverlayActive] = useState(false);
|
||||
const MAX_CODE_LINES = 500;
|
||||
|
||||
@@ -41,19 +41,24 @@ const StageWrapperCppClang = () => {
|
||||
const editorRef = useRef(null);
|
||||
const lastValidCppCodeRef = useRef(cppCode);
|
||||
|
||||
// 计算CodeMirror宽度的函数
|
||||
const getCodeMirrorWidth = () => {
|
||||
if (!apiDrawerOpen) return '54vw';
|
||||
|
||||
// 根据屏幕宽度调整
|
||||
if (windowWidth <= 900) {
|
||||
return '19vw'; // 54vw - 35vw = 19vw
|
||||
} else if (windowWidth <= 1200) {
|
||||
return '29vw'; // 54vw - 25vw = 29vw
|
||||
} else {
|
||||
return '34vw'; // 54vw - 20vw = 34vw
|
||||
}
|
||||
};
|
||||
// 游戏引擎加载完成前,API 面板显示加载态并屏蔽点击(5 秒兜底自动解除)
|
||||
useEffect(() => {
|
||||
if (isApiPanelReady) return undefined;
|
||||
const timer = setInterval(() => {
|
||||
if (window.iscreate === 1) {
|
||||
setIsApiPanelReady(true);
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 300);
|
||||
const fallback = setTimeout(() => {
|
||||
setIsApiPanelReady(true);
|
||||
clearInterval(timer);
|
||||
}, 5000);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
clearTimeout(fallback);
|
||||
};
|
||||
}, [isApiPanelReady]);
|
||||
|
||||
// API参考数据
|
||||
const apiReference = {
|
||||
@@ -238,17 +243,6 @@ const StageWrapperCppClang = () => {
|
||||
|
||||
useEffect(() => {
|
||||
initializeEmscripten();
|
||||
|
||||
// 添加窗口大小变化监听器
|
||||
const handleResize = () => {
|
||||
setWindowWidth(window.innerWidth);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 初始化基于Emscripten的C++执行环境
|
||||
@@ -1118,27 +1112,18 @@ const StageWrapperCppClang = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// API参考抽屉组件
|
||||
// API参考面板(与 Python 一样常驻在编辑区左侧)
|
||||
const ApiDrawer = () => {
|
||||
const [selectedCategory, setSelectedCategory] = useState('syntax');
|
||||
const selectedCategory = apiSelectedCategory;
|
||||
const setSelectedCategory = setApiSelectedCategory;
|
||||
|
||||
return (
|
||||
<div className={`${styles.apiDrawer} ${apiDrawerOpen ? styles.apiDrawerOpen : ''}`}>
|
||||
{/* 抽屉头部 */}
|
||||
<div className={styles.apiDrawer}>
|
||||
<div className={styles.apiDrawerHeader}>
|
||||
<h3>🔧 API参考</h3>
|
||||
<button
|
||||
className={styles.apiDrawerClose}
|
||||
onClick={() => setApiDrawerOpen(false)}
|
||||
title="关闭API参考"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<h3>🔧 C++ API参考</h3>
|
||||
</div>
|
||||
|
||||
{/* 抽屉内容 */}
|
||||
<div className={styles.apiDrawerContent}>
|
||||
{/* 分类导航 */}
|
||||
<div className={styles.apiDrawerCategories}>
|
||||
{Object.entries(apiReference).map(([key, category]) => (
|
||||
<button
|
||||
@@ -1152,7 +1137,6 @@ const StageWrapperCppClang = () => {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* API列表 */}
|
||||
<div className={styles.apiDrawerList}>
|
||||
{apiReference[selectedCategory]?.apis.map((api, index) => (
|
||||
<div key={index} className={styles.apiDrawerItem}>
|
||||
@@ -1176,6 +1160,12 @@ const StageWrapperCppClang = () => {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{!isApiPanelReady && (
|
||||
<div className={styles.apiDrawerLoading}>
|
||||
<div className={styles.apiDrawerLoadingSpinner}></div>
|
||||
<div className={styles.apiDrawerLoadingText}>游戏引擎加载中,完成后即可插入代码…</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1209,10 +1199,6 @@ const StageWrapperCppClang = () => {
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭API参考窗口
|
||||
setApiDrawerOpen(false);
|
||||
|
||||
// 聚焦编辑器
|
||||
view.focus();
|
||||
}
|
||||
}
|
||||
@@ -1223,166 +1209,137 @@ const StageWrapperCppClang = () => {
|
||||
<ErrorModal />
|
||||
<CompilationModal />
|
||||
<SyntaxErrorModal />
|
||||
<div onWheel={(e) => e.stopPropagation()} style={{
|
||||
height: '100vh',
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
}}>
|
||||
{/* API抽屉标签 */}
|
||||
<div
|
||||
className={`${styles.apiDrawerTab} ${apiDrawerOpen ? styles.apiDrawerTabOpen : ''}`}
|
||||
onClick={() => setApiDrawerOpen(!apiDrawerOpen)}
|
||||
title={apiDrawerOpen ? "关闭API参考" : "打开API参考"}
|
||||
>
|
||||
<div className={styles.apiDrawerTabIcon}>
|
||||
{apiDrawerOpen ? '▶' : '◀'}
|
||||
</div>
|
||||
<div className={styles.apiDrawerTabText}>
|
||||
{apiDrawerOpen ? '关闭' : 'API'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* API抽屉 */}
|
||||
<ApiDrawer />
|
||||
|
||||
{/* CodeMirror编辑器 */}
|
||||
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
|
||||
<CodeMirror
|
||||
ref={editorRef}
|
||||
<div className={styles.editorWithApi}>
|
||||
<ApiDrawer />
|
||||
<div
|
||||
className={styles.editorPane}
|
||||
onWheel={(e) => e.stopPropagation()}
|
||||
>
|
||||
<CodeMirror
|
||||
ref={editorRef}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
backgroundColor: '#010101',
|
||||
overflow: 'auto',
|
||||
fontSize: '20px'
|
||||
}}
|
||||
value={cppCode}
|
||||
theme={abcdef}
|
||||
extensions={[
|
||||
cpp(),
|
||||
createCppCompletions(),
|
||||
keymap.of(completionKeymap),
|
||||
keymap.of(snippetKeymap),
|
||||
classname({
|
||||
add: (lineNumber) => {
|
||||
if (lineNumber === highlightedLine) {
|
||||
return 'highlighted-line';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
EditorView.theme({
|
||||
'&': {
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
},
|
||||
'.cm-editor': {
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
},
|
||||
'.cm-scroller': {
|
||||
height: '100%',
|
||||
overflow: 'auto'
|
||||
},
|
||||
'.cm-content': {
|
||||
minHeight: '100%',
|
||||
padding: '10px'
|
||||
},
|
||||
'.highlighted-line': {
|
||||
backgroundColor: '#ffeb3b80',
|
||||
borderLeft: '4px solid #ffeb3b',
|
||||
borderRight: '2px solid #ffeb3b',
|
||||
boxShadow: '0 0 10px rgba(255, 235, 59, 0.3)',
|
||||
animation: 'pulse 1s ease-in-out'
|
||||
},
|
||||
'@keyframes pulse': {
|
||||
'0%': { backgroundColor: '#ffeb3b80' },
|
||||
'50%': { backgroundColor: '#ffeb3ba0' },
|
||||
'100%': { backgroundColor: '#ffeb3b80' }
|
||||
},
|
||||
fontSize: "20px"
|
||||
})
|
||||
]}
|
||||
basicSetup={{
|
||||
lineNumbers: true,
|
||||
highlightActiveLine: true,
|
||||
highlightSelectionMatches: true,
|
||||
searchKeymap: true,
|
||||
autocompletion: true
|
||||
}}
|
||||
onChange={(value) => {
|
||||
const lines = value.split(/\r\n|\r|\n/);
|
||||
if (lines.length > MAX_CODE_LINES) {
|
||||
setErrorMessage(`代码不能超过${MAX_CODE_LINES}行!当前已有${lines.length}行,请删除多余代码后再编辑。`);
|
||||
setErrorModalVisible(true);
|
||||
if (editorRef.current && editorRef.current.view) {
|
||||
const view = editorRef.current.view;
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: view.state.doc.length,
|
||||
insert: lastValidCppCodeRef.current
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
lastValidCppCodeRef.current = value;
|
||||
setCppCode(value);
|
||||
updateBlockCount(value);
|
||||
}}
|
||||
/>
|
||||
{overlayActive && (
|
||||
<div
|
||||
style={{
|
||||
width: getCodeMirrorWidth(),
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
backgroundColor: '#010101',
|
||||
fontSize: '20px',
|
||||
transition: 'width 0.3s ease'
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'rgba(0,0,0,0.25)',
|
||||
cursor: 'not-allowed',
|
||||
zIndex: 10,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
userSelect: 'none'
|
||||
}}
|
||||
value={cppCode}
|
||||
theme={abcdef}
|
||||
extensions={[
|
||||
cpp(),
|
||||
createCppCompletions(),
|
||||
keymap.of(completionKeymap),
|
||||
keymap.of(snippetKeymap),
|
||||
classname({
|
||||
add: (lineNumber) => {
|
||||
if (lineNumber === highlightedLine) {
|
||||
return 'highlighted-line';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
EditorView.theme({
|
||||
'&': {
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
},
|
||||
'.cm-editor': {
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
},
|
||||
'.cm-scroller': {
|
||||
height: '100%',
|
||||
overflow: 'auto'
|
||||
},
|
||||
'.cm-content': {
|
||||
minHeight: '100%',
|
||||
padding: '10px'
|
||||
},
|
||||
'.highlighted-line': {
|
||||
backgroundColor: '#ffeb3b80',
|
||||
borderLeft: '4px solid #ffeb3b',
|
||||
borderRight: '2px solid #ffeb3b',
|
||||
boxShadow: '0 0 10px rgba(255, 235, 59, 0.3)',
|
||||
animation: 'pulse 1s ease-in-out'
|
||||
},
|
||||
'@keyframes pulse': {
|
||||
'0%': { backgroundColor: '#ffeb3b80' },
|
||||
'50%': { backgroundColor: '#ffeb3ba0' },
|
||||
'100%': { backgroundColor: '#ffeb3b80' }
|
||||
},
|
||||
fontSize: "20px"
|
||||
})
|
||||
]}
|
||||
basicSetup={{
|
||||
lineNumbers: true,
|
||||
highlightActiveLine: true,
|
||||
highlightSelectionMatches: true,
|
||||
searchKeymap: true,
|
||||
autocompletion: true
|
||||
}}
|
||||
onChange={(value) => {
|
||||
const lines = value.split(/\r\n|\r|\n/);
|
||||
if (lines.length > MAX_CODE_LINES) {
|
||||
setErrorMessage(`代码不能超过${MAX_CODE_LINES}行!当前已有${lines.length}行,请删除多余代码后再编辑。`);
|
||||
setErrorModalVisible(true);
|
||||
// 使用 CodeMirror dispatch 直接回滚编辑器内容
|
||||
if (editorRef.current && editorRef.current.view) {
|
||||
const view = editorRef.current.view;
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: view.state.doc.length,
|
||||
insert: lastValidCppCodeRef.current
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
lastValidCppCodeRef.current = value;
|
||||
setCppCode(value);
|
||||
// 计算代码行数并更新window.blockcount
|
||||
updateBlockCount(value);
|
||||
}}
|
||||
/>
|
||||
{overlayActive && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'rgba(0,0,0,0.25)',
|
||||
cursor: 'not-allowed',
|
||||
zIndex: 10,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
userSelect: 'none'
|
||||
}}
|
||||
onWheel={(e) => e.preventDefault()}
|
||||
onTouchMove={(e) => e.preventDefault()}
|
||||
onClick={(e) => e.preventDefault()}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
>
|
||||
<div style={{
|
||||
color: '#fff',
|
||||
fontSize: '28px',
|
||||
fontWeight: 'bold',
|
||||
textShadow: '0 2px 8px rgba(0,0,0,0.6)',
|
||||
letterSpacing: '4px',
|
||||
animation: 'blink 1.5s ease-in-out infinite'
|
||||
}}>
|
||||
⏳ 执行中...
|
||||
</div>
|
||||
<style>{`
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
`}</style>
|
||||
onWheel={(e) => e.preventDefault()}
|
||||
onTouchMove={(e) => e.preventDefault()}
|
||||
onClick={(e) => e.preventDefault()}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
>
|
||||
<div style={{
|
||||
color: '#fff',
|
||||
fontSize: '28px',
|
||||
fontWeight: 'bold',
|
||||
textShadow: '0 2px 8px rgba(0,0,0,0.6)',
|
||||
letterSpacing: '4px',
|
||||
animation: 'blink 1.5s ease-in-out infinite'
|
||||
}}>
|
||||
⏳ 执行中...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<style>{`
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -378,6 +378,23 @@
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
/* API参考 + 编辑器并排:参考在左,编辑器靠右(与 Python 一致) */
|
||||
.editorWithApi {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
width: 54vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.editorPane {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* API抽屉标签样式 */
|
||||
.apiDrawerTab {
|
||||
position: fixed;
|
||||
@@ -486,19 +503,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* API抽屉样式 */
|
||||
/* API面板样式 */
|
||||
.apiDrawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: -20vw;
|
||||
/* 初始隐藏在右侧 */
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 20vw;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%);
|
||||
border-left: 2px solid #444;
|
||||
box-shadow: -5px 0 20px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1000;
|
||||
transition: right 0.3s ease;
|
||||
border-right: 2px solid #444;
|
||||
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<img class="level-card-cover" src="images/level1_cover.png" alt="" />
|
||||
<div class="level-card-text">
|
||||
<!-- <div class="lang-name">图形化</div> -->
|
||||
<div class="level-card-head">Level A</div><span class="lang-name">(图形化)</span>
|
||||
<div class="level-card-head">Level A <span style='font-size: 0.7em'>丝路青年</span></div><span class="lang-name">(图形化)</span>
|
||||
<div class="level-card-sub">编程基础,循环入门</div>
|
||||
<div class="level-card-desc"></div>
|
||||
</div>
|
||||
|
||||
@@ -106,12 +106,12 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
// Update Level Card Text
|
||||
const levelInfo = {
|
||||
1: { title: "Level A <span style='font-size: 0.7em'>丝路青年</span>", sub: "编程基础,循环入门" },
|
||||
2: { title: "Level B <span style='font-size: 0.7em'>红色之旅</span>", sub: "变量入门,循环进阶,变量进阶" },
|
||||
3: { title: "Level C <span style='font-size: 0.7em'>数智时代</span>", sub: "变量进阶,单条件分支入门,双条件分支入门,复杂路线规划" },
|
||||
4: { title: "Level D <span style='font-size: 0.7em'>国宝秘境</span>", sub: "条件分支进阶,循环嵌套入门,循环嵌套进阶,变量、循环与条件综合应用" },
|
||||
5: { title: "Level E <span style='font-size: 0.7em'>雪域征途</span>", sub: "循环嵌套进阶,无参数函数入门,单参数函数入门" },
|
||||
6: { title: "Level F <span style='font-size: 0.7em'>红色之旅</span>", sub: "综合运用与项目实战" }
|
||||
1: { title: "Level A <span style='font-size: 0.7em'>丝路青年</span>", sub: "编程基础,循环入门", cover: 1 },
|
||||
2: { title: "Level B <span style='font-size: 0.7em'>丝路青年</span>", sub: "变量入门,循环进阶,变量进阶", cover: 1 },
|
||||
3: { title: "Level C <span style='font-size: 0.7em'>红色之旅</span>", sub: "变量进阶,单条件分支入门,双条件分支入门,复杂路线规划", cover: 2 },
|
||||
4: { title: "Level D <span style='font-size: 0.7em'>红色之旅</span>", sub: "条件分支进阶,循环嵌套入门,循环嵌套进阶,变量、循环与条件综合应用", cover: 2 },
|
||||
5: { title: "Level E <span style='font-size: 0.7em'>数智时代</span>", sub: "循环嵌套进阶,无参数函数入门,单参数函数入门", cover: 3 },
|
||||
6: { title: "Level F <span style='font-size: 0.7em'>数智时代</span>", sub: "综合运用与项目实战", cover: 3 }
|
||||
};
|
||||
const lv = index + 1;
|
||||
const titleEl = document.querySelector('.level-card-head');
|
||||
@@ -124,7 +124,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
// Update Cover Image
|
||||
const coverEl = document.querySelector('.level-card-cover');
|
||||
if (coverEl) {
|
||||
coverEl.src = `images/level${lv}_cover.png`;
|
||||
const coverId = (levelInfo[lv] && levelInfo[lv].cover) || lv;
|
||||
coverEl.src = `images/level${coverId}_cover.png`;
|
||||
}
|
||||
|
||||
gamelvid = (index + 1).toString();
|
||||
|
||||
Reference in New Issue
Block a user