feat: 更新大版本

This commit is contained in:
Evan
2026-09-11 09:32:07 +08:00
parent 8dfd42397f
commit 37e0413576
380 changed files with 227 additions and 23 deletions

View File

@@ -0,0 +1,49 @@
.overlay {
position: absolute;
inset: 0;
z-index: 50;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
background: rgba(8, 8, 8, 0.92);
pointer-events: auto;
opacity: 1;
transition: opacity 0.28s ease-out, visibility 0.28s ease-out;
}
.overlay.hidden {
opacity: 0;
pointer-events: none;
visibility: hidden;
}
.text {
color: #f2f2f2;
font-size: 15px;
line-height: 1.5;
letter-spacing: 0.5px;
}
.barTrack {
width: min(72%, 280px);
height: 10px;
background: rgba(255, 255, 255, 0.16);
border-radius: 999px;
overflow: hidden;
}
.barFill {
height: 100%;
background: linear-gradient(90deg, #4fc3f7 0%, #29b6f6 100%);
border-radius: 999px;
transition: width 0.25s linear;
}
.percent {
color: #e0e0e0;
font-size: 18px;
font-weight: 600;
font-variant-numeric: tabular-nums;
}

View File

@@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './game-loading-overlay.css';
const GameLoadingOverlay = ({ visible, progress }) => {
const pct = Math.max(0, Math.min(100, Math.round(Number(progress) || 0)));
return (
<div
className={`${styles.overlay}${visible ? '' : ` ${styles.hidden}`}`}
aria-hidden={!visible}
>
<div className={styles.text}>正在加载中</div>
<div className={styles.barTrack}>
<div className={styles.barFill} style={{ width: `${pct}%` }} />
</div>
<div className={styles.percent}>{pct}%</div>
</div>
);
};
GameLoadingOverlay.propTypes = {
visible: PropTypes.bool,
progress: PropTypes.number
};
GameLoadingOverlay.defaultProps = {
visible: true,
progress: 0
};
export default GameLoadingOverlay;

View File

@@ -0,0 +1,47 @@
import { useCallback, useEffect, useRef, useState } from 'react';
const alreadyReady = () => typeof window !== 'undefined' && window.iscreate === 1;
const useGameLoadProgress = () => {
const [progress, setProgress] = useState(() => (alreadyReady() ? 100 : 0));
const [visible, setVisible] = useState(() => !alreadyReady());
const hidingRef = useRef(false);
useEffect(() => {
if (!visible) return undefined;
const timer = setInterval(() => {
if (window.iscreate === 1) {
if (!hidingRef.current) {
hidingRef.current = true;
setProgress(100);
setTimeout(() => setVisible(false), 280);
}
clearInterval(timer);
return;
}
setProgress(prev => (prev >= 90 && prev < 99 ? prev + 1 : prev));
}, 300);
return () => clearInterval(timer);
}, [visible]);
const onUnityProgress = useCallback(unityProgress => {
const mapped = Math.round(Math.min(90, Number(unityProgress) * 90));
if (!Number.isFinite(mapped)) return;
setProgress(prev => (mapped > prev ? mapped : prev));
}, []);
const onUnityInstanceCreated = useCallback(() => {
setProgress(prev => Math.max(prev, 90));
}, []);
return {
progress,
visible,
onUnityProgress,
onUnityInstanceCreated
};
};
export default useGameLoadProgress;

View File

@@ -15,6 +15,8 @@ import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from '../../playground/devtool-control.js';
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
import { debug } from 'scratch-vm/src/util/log.js';
import GameLoadingOverlay from '../game-loading-overlay/game-loading-overlay.jsx';
import useGameLoadProgress from '../game-loading-overlay/use-game-load-progress.js';
const MAX_LEVELS = 500; // 最大关卡数据数量
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
@@ -48,6 +50,16 @@ const UnityComponent = function (props) {
const [getBlockNum, setBlockNum] = useState(0);
const [testDal, setTestDal] = useState(-1);
const canvasRef = useRef(null);
const {
progress: gameLoadProgress,
visible: gameLoadVisible,
onUnityProgress,
onUnityInstanceCreated
} = useGameLoadProgress();
const onUnityProgressRef = useRef(onUnityProgress);
const onUnityInstanceCreatedRef = useRef(onUnityInstanceCreated);
onUnityProgressRef.current = onUnityProgress;
onUnityInstanceCreatedRef.current = onUnityInstanceCreated;
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
const [isLockNextButton, setIsLockNextButton] = useState(true);
@@ -1274,8 +1286,9 @@ const UnityComponent = function (props) {
window.iscreate = 0;
const boot = () => {
createUnityInstance(canvas, config, (progress) => {
// console.log(`加载进度: ${progress * 100}%`);
onUnityProgressRef.current(progress);
}).then((unityInstance) => {
onUnityInstanceCreatedRef.current();
window.unityInstance = unityInstance;
window.waitForUnityStepFinish = waitForUnityStepFinish;
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
@@ -1339,6 +1352,7 @@ const UnityComponent = function (props) {
}}
>
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
<GameLoadingOverlay visible={gameLoadVisible} progress={gameLoadProgress} />

View File

@@ -1160,12 +1160,13 @@ const StageWrapperCppClang = () => {
))}
</div>
</div>
{!isApiPanelReady && (
<div className={styles.apiDrawerLoading}>
<div className={styles.apiDrawerLoadingSpinner}></div>
<div className={styles.apiDrawerLoadingText}>游戏引擎加载中完成后即可插入代码</div>
</div>
)}
<div
className={`${styles.apiDrawerLoading}${isApiPanelReady ? ` ${styles.apiDrawerLoadingHidden}` : ''}`}
aria-hidden={isApiPanelReady}
>
<div className={styles.apiDrawerLoadingSpinner}></div>
<div className={styles.apiDrawerLoadingText}>正在加载中完成后即可插入代码</div>
</div>
</div>
);
};
@@ -1186,6 +1187,12 @@ const StageWrapperCppClang = () => {
// 移除snippet标记转换为普通文本
insertCode = code.replace(/#{([^}]*)}/g, '$1');
}
if (cursor > 0 && view.state.doc.sliceString(cursor - 1, cursor) !== '\n') {
insertCode = '\n' + insertCode;
}
if (!insertCode.endsWith('\n')) {
insertCode += '\n';
}
// 插入代码
view.dispatch({
@@ -1210,7 +1217,7 @@ const StageWrapperCppClang = () => {
<CompilationModal />
<SyntaxErrorModal />
<div className={styles.editorWithApi}>
<ApiDrawer />
{ApiDrawer()}
<div
className={styles.editorPane}
onWheel={(e) => e.stopPropagation()}

View File

@@ -531,6 +531,10 @@
cursor: not-allowed;
}
.apiDrawerLoadingHidden {
display: none;
}
.apiDrawerLoadingSpinner {
width: 38px;
height: 38px;
@@ -538,6 +542,7 @@
border-top-color: #ba68c8;
border-radius: 50%;
animation: apiDrawerLoadingSpin 0.9s linear infinite;
will-change: transform;
}
.apiDrawerLoadingText {
@@ -549,6 +554,9 @@
}
@keyframes apiDrawerLoadingSpin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}

View File

@@ -15,6 +15,8 @@ import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from '../../playground/devtool-control.js';
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
import { debug } from 'scratch-vm/src/util/log.js';
import GameLoadingOverlay from '../game-loading-overlay/game-loading-overlay.jsx';
import useGameLoadProgress from '../game-loading-overlay/use-game-load-progress.js';
const MAX_LEVELS = 500; // 最大关卡数据数量
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
@@ -48,6 +50,16 @@ const UnityComponent = function (props) {
const [getBlockNum, setBlockNum] = useState(0);
const [testDal, setTestDal] = useState(-1);
const canvasRef = useRef(null);
const {
progress: gameLoadProgress,
visible: gameLoadVisible,
onUnityProgress,
onUnityInstanceCreated
} = useGameLoadProgress();
const onUnityProgressRef = useRef(onUnityProgress);
const onUnityInstanceCreatedRef = useRef(onUnityInstanceCreated);
onUnityProgressRef.current = onUnityProgress;
onUnityInstanceCreatedRef.current = onUnityInstanceCreated;
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
const [isLockNextButton, setIsLockNextButton] = useState(true);
@@ -1259,8 +1271,9 @@ const UnityComponent = function (props) {
window.iscreate = 0;
const boot = () => {
createUnityInstance(canvas, config, (progress) => {
// console.log(`加载进度: ${progress * 100}%`);
onUnityProgressRef.current(progress);
}).then((unityInstance) => {
onUnityInstanceCreatedRef.current();
window.unityInstance = unityInstance;
window.waitForUnityStepFinish = waitForUnityStepFinish;
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
@@ -1324,6 +1337,7 @@ const UnityComponent = function (props) {
}}
>
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
<GameLoadingOverlay visible={gameLoadVisible} progress={gameLoadProgress} />

View File

@@ -183,6 +183,10 @@
cursor: not-allowed;
}
.apiDrawerLoadingHidden {
display: none;
}
.apiDrawerLoadingSpinner {
width: 38px;
height: 38px;
@@ -190,6 +194,7 @@
border-top-color: #ba68c8;
border-radius: 50%;
animation: apiDrawerLoadingSpin 0.9s linear infinite;
will-change: transform;
}
.apiDrawerLoadingText {
@@ -201,6 +206,9 @@
}
@keyframes apiDrawerLoadingSpin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}

View File

@@ -1287,12 +1287,13 @@ sys.settrace(trace_func)`;
))}
</div>
</div>
{!isApiPanelReady && (
<div className={styles.apiDrawerLoading}>
<div className={styles.apiDrawerLoadingSpinner}></div>
<div className={styles.apiDrawerLoadingText}>游戏引擎加载中完成后即可插入代码</div>
</div>
)}
<div
className={`${styles.apiDrawerLoading}${isApiPanelReady ? ` ${styles.apiDrawerLoadingHidden}` : ''}`}
aria-hidden={isApiPanelReady}
>
<div className={styles.apiDrawerLoadingSpinner}></div>
<div className={styles.apiDrawerLoadingText}>正在加载中完成后即可插入代码</div>
</div>
</div>
);
};
@@ -1305,12 +1306,22 @@ sys.settrace(trace_func)`;
const { state } = view;
const { selection } = state;
const cursor = selection.main.head;
const withSafeNewlines = (text) => {
let next = text;
if (cursor > 0 && view.state.doc.sliceString(cursor - 1, cursor) !== '\n') {
next = '\n' + next;
}
if (!next.endsWith('\n')) {
next += '\n';
}
return next;
};
// 检查代码是否包含snippet占位符
if (code.includes('#{')) {
// 使用snippet功能插入代码
try {
const snippetTemplate = snippet(code);
const snippetTemplate = snippet(withSafeNewlines(code));
const completion = snippetTemplate(view, {
from: cursor,
to: cursor
@@ -1320,7 +1331,7 @@ sys.settrace(trace_func)`;
view.dispatch(completion);
} else {
// 如果snippet创建失败使用简化的插入方式
const cleanCode = code.replace(/#{([^}]*)}/g, '$1');
const cleanCode = withSafeNewlines(code.replace(/#{([^}]*)}/g, '$1'));
view.dispatch({
changes: {
from: cursor,
@@ -1332,7 +1343,7 @@ sys.settrace(trace_func)`;
} catch (error) {
console.warn('Snippet insertion failed, falling back to plain text:', error);
// 回退到普通插入移除snippet标记
const cleanCode = code.replace(/#{([^}]*)}/g, '$1');
const cleanCode = withSafeNewlines(code.replace(/#{([^}]*)}/g, '$1'));
view.dispatch({
changes: {
from: cursor,
@@ -1342,15 +1353,15 @@ sys.settrace(trace_func)`;
});
}
} else {
// 普通代码插入
const insertText = withSafeNewlines(code);
view.dispatch({
changes: {
from: cursor,
to: cursor,
insert: code
insert: insertText
},
selection: {
anchor: cursor + code.length
anchor: cursor + insertText.length
}
});
}
@@ -1365,7 +1376,7 @@ sys.settrace(trace_func)`;
<>
<ErrorModal />
<div className={styles.editorWithApi}>
<ApiDrawer />
{ApiDrawer()}
<div
className={styles.editorPane}
onWheel={(e) => e.stopPropagation()}

View File

@@ -15,6 +15,8 @@ import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from '../../playground/devtool-control.js';
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
import { debug } from 'scratch-vm/src/util/log.js';
import GameLoadingOverlay from '../game-loading-overlay/game-loading-overlay.jsx';
import useGameLoadProgress from '../game-loading-overlay/use-game-load-progress.js';
const MAX_LEVELS = 500; // 最大关卡数据数量
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
@@ -248,6 +250,16 @@ const UnityComponent = function (props) {
const [getBlockNum, setBlockNum] = useState(0);
const [testDal, setTestDal] = useState(-1);
const canvasRef = useRef(null);
const {
progress: gameLoadProgress,
visible: gameLoadVisible,
onUnityProgress,
onUnityInstanceCreated
} = useGameLoadProgress();
const onUnityProgressRef = useRef(onUnityProgress);
const onUnityInstanceCreatedRef = useRef(onUnityInstanceCreated);
onUnityProgressRef.current = onUnityProgress;
onUnityInstanceCreatedRef.current = onUnityInstanceCreated;
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
const [isLockNextButton, setIsLockNextButton] = useState(true);
@@ -1522,8 +1534,9 @@ const UnityComponent = function (props) {
window.iscreate = 0;
const boot = () => {
createUnityInstance(canvas, config, (progress) => {
// console.log(`加载进度: ${progress * 100}%`);
onUnityProgressRef.current(progress);
}).then((unityInstance) => {
onUnityInstanceCreatedRef.current();
window.unityInstance = unityInstance;
window.waitForUnityStepFinish = waitForUnityStepFinish;
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
@@ -1587,6 +1600,7 @@ const UnityComponent = function (props) {
}}
>
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
<GameLoadingOverlay visible={gameLoadVisible} progress={gameLoadProgress} />
<section
className="u-align-center u-black u-clearfix u-container-align-center u-container-style u-dialog-block u-opacity u-opacity-50 u-valign-middle-md u-valign-middle-xl u-dialog-section-8"