feat: 更新大版本
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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} />
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user