From 010696cc37fdba425d07e7cd54c028bd3c597319 Mon Sep 17 00:00:00 2001 From: xiaoxustudio Date: Thu, 12 Mar 2026 00:05:42 +0800 Subject: [PATCH 01/24] feat: add createFrame and removeFrame --- packages/parser/src/config/scriptConfig.ts | 2 + .../parser/src/interface/sceneInterface.ts | 2 + .../Core/controller/scene/sceneInterface.ts | 2 + .../src/Core/controller/stage/resetStage.ts | 5 +- .../src/Core/gameScripts/createFrame.ts | 77 +++++++++++++++++++ .../src/Core/gameScripts/removeFrame.ts | 34 ++++++++ .../webgal/src/Core/parser/sceneParser.ts | 4 + packages/webgal/src/Stage/Iframe/Iframe.tsx | 9 +++ .../Stage/Iframe/IframeContainer.module.scss | 9 +++ .../src/Stage/Iframe/IframeContainer.tsx | 15 ++++ packages/webgal/src/Stage/Stage.tsx | 5 +- packages/webgal/src/store/stageInterface.ts | 11 +++ packages/webgal/src/store/stageReducer.ts | 11 +++ 13 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 packages/webgal/src/Core/gameScripts/createFrame.ts create mode 100644 packages/webgal/src/Core/gameScripts/removeFrame.ts create mode 100644 packages/webgal/src/Stage/Iframe/Iframe.tsx create mode 100644 packages/webgal/src/Stage/Iframe/IframeContainer.module.scss create mode 100644 packages/webgal/src/Stage/Iframe/IframeContainer.tsx diff --git a/packages/parser/src/config/scriptConfig.ts b/packages/parser/src/config/scriptConfig.ts index 1e1db77e9..4d1d54f3c 100644 --- a/packages/parser/src/config/scriptConfig.ts +++ b/packages/parser/src/config/scriptConfig.ts @@ -39,6 +39,8 @@ export const SCRIPT_CONFIG = [ { scriptString: 'applyStyle', scriptType: commandType.applyStyle }, { scriptString: 'wait', scriptType: commandType.wait }, { scriptString: 'callSteam', scriptType: commandType.callSteam }, + { scriptString: 'createFrame', scriptType: commandType.createFrame }, + { scriptString: 'removeFrame', scriptType: commandType.removeFrame }, ]; export const ADD_NEXT_ARG_LIST = [ commandType.bgm, diff --git a/packages/parser/src/interface/sceneInterface.ts b/packages/parser/src/interface/sceneInterface.ts index 1b05024c8..a4eab23c4 100644 --- a/packages/parser/src/interface/sceneInterface.ts +++ b/packages/parser/src/interface/sceneInterface.ts @@ -40,6 +40,8 @@ export enum commandType { applyStyle, wait, callSteam, // 调用Steam功能 + createFrame, // 创建框架 + removeFrame, // 移除框架 } /** diff --git a/packages/webgal/src/Core/controller/scene/sceneInterface.ts b/packages/webgal/src/Core/controller/scene/sceneInterface.ts index 3e29d0ee2..05b12e11a 100644 --- a/packages/webgal/src/Core/controller/scene/sceneInterface.ts +++ b/packages/webgal/src/Core/controller/scene/sceneInterface.ts @@ -40,6 +40,8 @@ export enum commandType { applyStyle, wait, callSteam, // 调用Steam功能 + createFrame, // 创建框架 + removeFrame, // 移除框架 } /** diff --git a/packages/webgal/src/Core/controller/stage/resetStage.ts b/packages/webgal/src/Core/controller/stage/resetStage.ts index b72573834..ade360838 100644 --- a/packages/webgal/src/Core/controller/stage/resetStage.ts +++ b/packages/webgal/src/Core/controller/stage/resetStage.ts @@ -1,4 +1,4 @@ -import { initState, resetStageState, setStage } from '@/store/stageReducer'; +import { initState, resetStageState, setStage, stageActions } from '@/store/stageReducer'; import { webgalStore } from '@/store/store'; import cloneDeep from 'lodash/cloneDeep'; import { WebGAL } from '@/Core/WebGAL'; @@ -27,4 +27,7 @@ export const resetStage = (resetBacklog: boolean, resetSceneAndVar = true) => { if (!resetSceneAndVar) { webgalStore.dispatch(setStage({ key: 'GameVar', value: currentVars })); } + + // 清空frames + webgalStore.dispatch(stageActions.resetFrame()); }; diff --git a/packages/webgal/src/Core/gameScripts/createFrame.ts b/packages/webgal/src/Core/gameScripts/createFrame.ts new file mode 100644 index 000000000..7ef2c305d --- /dev/null +++ b/packages/webgal/src/Core/gameScripts/createFrame.ts @@ -0,0 +1,77 @@ +import { ISentence } from '@/Core/controller/scene/sceneInterface'; +import { IPerform } from '@/Core/Modules/perform/performInterface'; +import { getNumberArgByKey, getStringArgByKey } from '../util/getSentenceArg'; +import { IIFrame } from '@/store/stageInterface'; +import { webgalStore } from '@/store/store'; +import { stageActions } from '@/store/stageReducer'; + +const allSandboxProperties = { + 'allow-forms': 'allowForms', // 允许iframe内提交表单 + 'allow-scripts': 'allowScripts', // 允许iframe内执行JavaScript脚本(包括定时器、事件等) + 'allow-same-origin': 'allowSameOrigin', // 允许iframe内容拥有同源身份,可访问自身Cookie/LocalStorage等 + 'allow-top-navigation': 'allowTopNavigation', // 允许iframe内的链接跳转到父页面(主页面)的上下文 + 'allow-popups': 'allowPopups', // 允许iframe通过window.open()等方式弹出新窗口 + 'allow-modals': 'allowModals', // 允许iframe弹出模态窗口(如alert()、confirm()、prompt()) + 'allow-pointer-lock': 'allowPointerLock', // 允许iframe使用Pointer Lock API(如游戏鼠标锁定) + 'allow-popups-to-escape-sandbox': 'allowPopupsToEscapeSandbox', // 允许iframe弹出的新窗口不受当前沙箱限制 + 'allow-downloads': 'allowDownloads', // 允许iframe内触发文件下载操作 + 'allow-presentation': 'allowPresentation', // 允许iframe使用Presentation API(投屏/演示功能) + 'allow-top-navigation-by-user-activation': 'allowTopNavigationByUserActivation', // 仅允许用户主动触发(如点击)的顶级导航操作 + 'allow-storage-access-by-user-activation': 'allowStorageAccessByUserActivation', // 允许用户主动触发后访问父页面的存储权限 + 'allow-orientation-lock': 'allowOrientationLock', // 允许iframe使用Screen Orientation API锁定屏幕方向 +}; + +/** + * 创建框架 + * @param sentence + */ +export const createFrame = (sentence: ISentence): IPerform => { + const src = sentence.content; + const id = getStringArgByKey(sentence, 'id') ?? ''; + const width = getNumberArgByKey(sentence, 'width') ?? undefined; + const height = getNumberArgByKey(sentence, 'height') ?? undefined; + if (!id || !src) { + return { + performName: 'none', + duration: 0, + isHoldOn: false, + stopFunction: () => {}, + blockingNext: () => false, + blockingAuto: () => true, + stopTimeout: undefined, + }; + } + let rawSrc = src; + // 处理src + if (!rawSrc.startsWith('http') || !rawSrc.startsWith('https')) { + rawSrc = './game/' + rawSrc; + } + const frameData: IIFrame = { + id, + src: rawSrc, + sandbox: '', + width, + height, + isActive: true, + isDestroy: false, + }; + + for (const [key, value] of Object.entries(allSandboxProperties)) { + const v = getStringArgByKey(sentence, value) ?? ''; + if (v) { + frameData.sandbox += key + ' '; + } + } + + webgalStore.dispatch(stageActions.addFrame(frameData)); + + return { + performName: 'none', + duration: 0, + isHoldOn: false, + stopFunction: () => {}, + blockingNext: () => false, + blockingAuto: () => true, + stopTimeout: undefined, + }; +}; diff --git a/packages/webgal/src/Core/gameScripts/removeFrame.ts b/packages/webgal/src/Core/gameScripts/removeFrame.ts new file mode 100644 index 000000000..aa7b2980c --- /dev/null +++ b/packages/webgal/src/Core/gameScripts/removeFrame.ts @@ -0,0 +1,34 @@ +import { ISentence } from '@/Core/controller/scene/sceneInterface'; +import { IPerform } from '@/Core/Modules/perform/performInterface'; +import { stageActions } from '@/store/stageReducer'; +import { webgalStore } from '@/store/store'; + +/** + * 移除框架 + * @param sentence + */ +export const removeFrame = (sentence: ISentence): IPerform => { + const id = sentence.content; + if (!id) { + return { + performName: 'none', + duration: 0, + isHoldOn: false, + stopFunction: () => {}, + blockingNext: () => false, + blockingAuto: () => true, + stopTimeout: undefined, + }; + } + webgalStore.dispatch(stageActions.removeFrame(id)); + + return { + performName: 'none', + duration: 0, + isHoldOn: false, + stopFunction: () => {}, + blockingNext: () => false, + blockingAuto: () => true, + stopTimeout: undefined, + }; +}; diff --git a/packages/webgal/src/Core/parser/sceneParser.ts b/packages/webgal/src/Core/parser/sceneParser.ts index 85b3ebea3..44ee3afee 100644 --- a/packages/webgal/src/Core/parser/sceneParser.ts +++ b/packages/webgal/src/Core/parser/sceneParser.ts @@ -28,6 +28,8 @@ import { setTransition } from '@/Core/gameScripts/setTransition'; import { unlockBgm } from '@/Core/gameScripts/unlockBgm'; import { unlockCg } from '@/Core/gameScripts/unlockCg'; import { callSteam } from '@/Core/gameScripts/callSteam'; +import { createFrame } from '@/Core/gameScripts/createFrame'; +import { removeFrame } from '@/Core/gameScripts/removeFrame'; import { end } from '../gameScripts/end'; import { jumpLabel } from '../gameScripts/jumpLabel'; import { pixiInit } from '../gameScripts/pixi/pixiInit'; @@ -74,6 +76,8 @@ export const SCRIPT_TAG_MAP = defineScripts({ applyStyle: ScriptConfig(commandType.applyStyle, applyStyle, { next: true }), wait: ScriptConfig(commandType.wait, wait), callSteam: ScriptConfig(commandType.callSteam, callSteam, { next: true }), + createFrame: ScriptConfig(commandType.createFrame, createFrame), + removeFrame: ScriptConfig(commandType.removeFrame, removeFrame), }); export const SCRIPT_CONFIG: IConfigInterface[] = Object.values(SCRIPT_TAG_MAP); diff --git a/packages/webgal/src/Stage/Iframe/Iframe.tsx b/packages/webgal/src/Stage/Iframe/Iframe.tsx new file mode 100644 index 000000000..e61f84a79 --- /dev/null +++ b/packages/webgal/src/Stage/Iframe/Iframe.tsx @@ -0,0 +1,9 @@ +import { IIFrame } from '@/store/stageInterface'; + +export default function Iframe({ id, sandbox, src, width, height }: IIFrame) { + if (!src) { + return null; + } + + return