diff --git a/packages/parser/.prettierrc b/packages/parser/.prettierrc new file mode 100644 index 000000000..33f6d8eaf --- /dev/null +++ b/packages/parser/.prettierrc @@ -0,0 +1,19 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "as-needed", + "jsxSingleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "always", + "requirePragma": false, + "insertPragma": false, + "proseWrap": "preserve", + "htmlWhitespaceSensitivity": "css", + "endOfLine": "lf", + "embeddedLanguageFormatting": "auto" +} diff --git a/packages/parser/src/config/scriptConfig.ts b/packages/parser/src/config/scriptConfig.ts index 93d1db519..ef6213aab 100644 --- a/packages/parser/src/config/scriptConfig.ts +++ b/packages/parser/src/config/scriptConfig.ts @@ -1,4 +1,4 @@ -import { commandType } from "../interface/sceneInterface"; +import { commandType } from '../interface/sceneInterface'; export const SCRIPT_CONFIG = [ { scriptString: 'intro', scriptType: commandType.intro }, @@ -6,7 +6,7 @@ export const SCRIPT_CONFIG = [ { scriptString: 'changeFigure', scriptType: commandType.changeFigure }, { scriptString: 'miniAvatar', scriptType: commandType.miniAvatar }, { scriptString: 'changeScene', scriptType: commandType.changeScene }, - { scriptString: 'choose', scriptType: commandType.choose}, + { scriptString: 'choose', scriptType: commandType.choose }, { scriptString: 'end', scriptType: commandType.end }, { scriptString: 'bgm', scriptType: commandType.bgm }, { scriptString: 'playVideo', scriptType: commandType.video }, @@ -17,8 +17,8 @@ export const SCRIPT_CONFIG = [ { scriptString: 'setFilter', scriptType: commandType.setFilter }, { scriptString: 'pixiInit', scriptType: commandType.pixiInit }, { scriptString: 'pixiPerform', scriptType: commandType.pixi }, - { scriptString: 'label', scriptType: commandType.label}, - { scriptString: 'jumpLabel', scriptType: commandType.jumpLabel}, + { scriptString: 'label', scriptType: commandType.label }, + { scriptString: 'jumpLabel', scriptType: commandType.jumpLabel }, { scriptString: 'setVar', scriptType: commandType.setVar }, { scriptString: 'callScene', scriptType: commandType.callScene }, { scriptString: 'showVars', scriptType: commandType.showVars }, @@ -27,7 +27,7 @@ export const SCRIPT_CONFIG = [ { scriptString: 'say', scriptType: commandType.say }, { scriptString: 'filmMode', scriptType: commandType.filmMode }, { scriptString: 'callScene', scriptType: commandType.callScene }, - { scriptString: 'setTextbox', scriptType: commandType.setTextbox}, + { scriptString: 'setTextbox', scriptType: commandType.setTextbox }, { scriptString: 'setAnimation', scriptType: commandType.setAnimation }, { scriptString: 'playEffect', scriptType: commandType.playEffect }, ]; @@ -44,3 +44,6 @@ export const ADD_NEXT_ARG_LIST = [ commandType.filmMode, commandType.playEffect, ]; + +export type ConfigMap = Map; +export type ConfigItem = { scriptString: string; scriptType: commandType }; diff --git a/packages/parser/src/configParser/configParser.ts b/packages/parser/src/configParser/configParser.ts index fb0f2b8ee..12b686b30 100644 --- a/packages/parser/src/configParser/configParser.ts +++ b/packages/parser/src/configParser/configParser.ts @@ -1,29 +1,28 @@ -import {argsParser} from "../scriptParser/argsParser"; +import { argsParser } from '../scriptParser/argsParser'; -interface IOptionItem{ - key:string; - value:string | number | boolean; +interface IOptionItem { + key: string; + value: string | number | boolean; } -interface IConfigItem{ - command:string; - args:string[] - options:IOptionItem[]; +interface IConfigItem { + command: string; + args: string[]; + options: IOptionItem[]; } -export type WebgalConfig = IConfigItem[] +export type WebgalConfig = IConfigItem[]; - -function configLineParser(inputLine:string):IConfigItem{ - const options:Array = []; +function configLineParser(inputLine: string): IConfigItem { + const options: Array = []; let command: string; - let newSentenceRaw = inputLine.split(";")[0]; - if (newSentenceRaw === "") { + let newSentenceRaw = inputLine.split(';')[0]; + if (newSentenceRaw === '') { // 注释提前返回 return { - command:'', - args:[], - options:[] + command: '', + args: [], + options: [], }; } // 截取命令 @@ -35,26 +34,39 @@ function configLineParser(inputLine:string):IConfigItem{ } else { command = newSentenceRaw.substring(0, getCommandResult.index); // 划分命令区域和content区域 - newSentenceRaw = newSentenceRaw.substring(getCommandResult.index + 1, newSentenceRaw.length); + newSentenceRaw = newSentenceRaw.substring( + getCommandResult.index + 1, + newSentenceRaw.length, + ); } // 截取 Options 区域 const getOptionsResult = / -/.exec(newSentenceRaw); // 获取到参数 if (getOptionsResult) { - const optionsRaw = newSentenceRaw.substring(getOptionsResult.index, newSentenceRaw.length); + const optionsRaw = newSentenceRaw.substring( + getOptionsResult.index, + newSentenceRaw.length, + ); newSentenceRaw = newSentenceRaw.substring(0, getOptionsResult.index); - for (const e of argsParser(optionsRaw, (name,_)=>{return name})) { + for (const e of argsParser(optionsRaw, (name, _) => { + return name; + })) { options.push(e); } } return { command, - args:newSentenceRaw.split('|').map(e=>e.trim()).filter(e=>e!==''), - options + args: newSentenceRaw + .split('|') + .map((e) => e.trim()) + .filter((e) => e !== ''), + options, }; } -export function configParser(configText:string):WebgalConfig{ - const configLines = configText.replaceAll(`\r`,'').split('\n'); - return configLines.map(e=>configLineParser(e)).filter(e=>e.command!==''); +export function configParser(configText: string): WebgalConfig { + const configLines = configText.replaceAll(`\r`, '').split('\n'); + return configLines + .map((e) => configLineParser(e)) + .filter((e) => e.command !== ''); } diff --git a/packages/parser/src/index.ts b/packages/parser/src/index.ts index 7db3222cb..ef4f18dc6 100644 --- a/packages/parser/src/index.ts +++ b/packages/parser/src/index.ts @@ -1,42 +1,72 @@ -import {commandType, IAsset} from "./interface/sceneInterface"; -import {fileType} from "./interface/assets"; -import {sceneParser} from "./sceneParser"; -import {ADD_NEXT_ARG_LIST, SCRIPT_CONFIG} from "./config/scriptConfig"; -import {configParser, WebgalConfig} from "./configParser/configParser"; +import { + ADD_NEXT_ARG_LIST, + SCRIPT_CONFIG, + ConfigMap, + ConfigItem, +} from './config/scriptConfig'; +import { configParser, WebgalConfig } from './configParser/configParser'; +import { fileType } from './interface/assets'; +import { IAsset } from './interface/sceneInterface'; +import { sceneParser } from './sceneParser'; export default class SceneParser { - - private readonly assetsPrefetcher; - private readonly assetSetter; - private readonly ADD_NEXT_ARG_LIST; - private readonly SCRIPT_CONFIG; - - constructor(assetsPrefetcher: ((assetList: Array) => void), - assetSetter: (fileName: string, assetType: fileType) => string, - ADD_NEXT_ARG_LIST: Array, SCRIPT_CONFIG: Array) { - this.assetsPrefetcher = assetsPrefetcher; - this.assetSetter = assetSetter; - this.ADD_NEXT_ARG_LIST = ADD_NEXT_ARG_LIST; - this.SCRIPT_CONFIG = SCRIPT_CONFIG; - } - - parse(rawScene: string, sceneName: string, sceneUrl: string + private readonly SCRIPT_CONFIG_MAP: ConfigMap; + constructor( + private readonly assetsPrefetcher: (assetList: IAsset[]) => void, + private readonly assetSetter: ( + fileName: string, + assetType: fileType, + ) => string, + private readonly ADD_NEXT_ARG_LIST: number[], + SCRIPT_CONFIG_INPUT: ConfigItem[] | ConfigMap, ) { - return sceneParser(rawScene, sceneName, sceneUrl, this.assetsPrefetcher, this.assetSetter, this.ADD_NEXT_ARG_LIST, this.SCRIPT_CONFIG); + if (Array.isArray(SCRIPT_CONFIG_INPUT)) { + this.SCRIPT_CONFIG_MAP = new Map(); + SCRIPT_CONFIG_INPUT.forEach((config) => { + this.SCRIPT_CONFIG_MAP.set(config.scriptString, config); + }); + } else { + this.SCRIPT_CONFIG_MAP = SCRIPT_CONFIG_INPUT; + } + } + /** + * 解析场景 + * @param rawScene 原始场景 + * @param sceneName 场景名称 + * @param sceneUrl 场景url + * @return 解析后的场景 + */ + parse(rawScene: string, sceneName: string, sceneUrl: string) { + return sceneParser( + rawScene, + sceneName, + sceneUrl, + this.assetsPrefetcher, + this.assetSetter, + this.ADD_NEXT_ARG_LIST, + this.SCRIPT_CONFIG_MAP, + ); } parseConfig(configText: string) { - return configParser(configText) + return configParser(configText); } stringifyConfig(config: WebgalConfig) { - return config - .reduce( - (previousValue, curr) => - (previousValue + `${curr.command}:${curr.args.join('|')}${curr.options.length <= 0 ? '' : curr.options.reduce((p, c) => (p + ' -' + c.key + '=' + c.value), '')};\n`), - '' - ) + return config.reduce( + (previousValue, curr) => + previousValue + + `${curr.command}:${curr.args.join('|')}${ + curr.options.length <= 0 + ? '' + : curr.options.reduce( + (p, c) => p + ' -' + c.key + '=' + c.value, + '', + ) + };\n`, + '', + ); } } -export {ADD_NEXT_ARG_LIST, SCRIPT_CONFIG}; +export { ADD_NEXT_ARG_LIST, SCRIPT_CONFIG }; diff --git a/packages/parser/src/interface/sceneInterface.ts b/packages/parser/src/interface/sceneInterface.ts index dc77aceaf..1354b33e5 100644 --- a/packages/parser/src/interface/sceneInterface.ts +++ b/packages/parser/src/interface/sceneInterface.ts @@ -1,8 +1,8 @@ /** * 语句类型 */ -import {sceneEntry} from "./runtimeInterface"; -import {fileType} from "./assets"; +import { sceneEntry } from './runtimeInterface'; +import { fileType } from './assets'; export enum commandType { say, // 对话 @@ -36,7 +36,7 @@ export enum commandType { comment, setTransform, setTransition, - getUserInput + getUserInput, } /** diff --git a/packages/parser/src/parser4/index.ts b/packages/parser/src/parser4/index.ts index 4afbf66d9..3456958b9 100644 --- a/packages/parser/src/parser4/index.ts +++ b/packages/parser/src/parser4/index.ts @@ -155,4 +155,4 @@ // defaultRule: "scene" // }; // }()); -export const parser4 = 'unreleased' +export const parser4 = 'unreleased'; diff --git a/packages/parser/src/sceneParser.ts b/packages/parser/src/sceneParser.ts index d4156c3d1..9d2a72b83 100644 --- a/packages/parser/src/sceneParser.ts +++ b/packages/parser/src/sceneParser.ts @@ -1,7 +1,13 @@ -import { IAsset, IScene, ISentence } from "./interface/sceneInterface"; -import { scriptParser } from "./scriptParser/scriptParser"; -import uniqWith from "lodash/uniqWith"; -import { fileType } from "./interface/assets"; +import { + commandType, + IAsset, + IScene, + ISentence, +} from './interface/sceneInterface'; +import { scriptParser } from './scriptParser/scriptParser'; +import uniqWith from 'lodash/uniqWith'; +import { fileType } from './interface/assets'; +import { ConfigMap } from './config/scriptConfig'; /** * 场景解析器 @@ -11,13 +17,19 @@ import { fileType } from "./interface/assets"; * @param assetsPrefetcher * @param assetSetter * @param ADD_NEXT_ARG_LIST - * @param SCRIPT_CONFIG + * @param SCRIPT_CONFIG_MAP * @return {IScene} 解析后的场景 */ -export const sceneParser = (rawScene: string, sceneName: string, sceneUrl: string - , assetsPrefetcher: ((assetList: Array) => void), assetSetter: (fileName: string, assetType: fileType) => string - , ADD_NEXT_ARG_LIST: any, SCRIPT_CONFIG: any): IScene => { - const rawSentenceList = rawScene.split("\n"); // 原始句子列表 +export const sceneParser = ( + rawScene: string, + sceneName: string, + sceneUrl: string, + assetsPrefetcher: (assetList: Array) => void, + assetSetter: (fileName: string, assetType: fileType) => string, + ADD_NEXT_ARG_LIST: commandType[], + SCRIPT_CONFIG_MAP: ConfigMap, +): IScene => { + const rawSentenceList = rawScene.split('\n'); // 原始句子列表 // 去分号留到后面去做了,现在注释要单独处理 const rawSentenceListWithoutEmpty = rawSentenceList; @@ -25,13 +37,20 @@ export const sceneParser = (rawScene: string, sceneName: string, sceneUrl: strin // .filter((sentence) => sentence.trim() !== ""); let assetsList: Array = []; // 场景资源列表 let subSceneList: Array = []; // 子场景列表 - const sentenceList: Array = rawSentenceListWithoutEmpty.map((sentence) => { - const returnSentence: ISentence = scriptParser(sentence, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); - // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList - assetsList = [...assetsList, ...returnSentence.sentenceAssets]; - subSceneList = [...subSceneList, ...returnSentence.subScene]; - return returnSentence; - }); + const sentenceList: Array = rawSentenceListWithoutEmpty.map( + (sentence) => { + const returnSentence: ISentence = scriptParser( + sentence, + assetSetter, + ADD_NEXT_ARG_LIST, + SCRIPT_CONFIG_MAP, + ); + // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList + assetsList = [...assetsList, ...returnSentence.sentenceAssets]; + subSceneList = [...subSceneList, ...returnSentence.subScene]; + return returnSentence; + }, + ); // 开始资源的预加载 assetsList = uniqWith(assetsList); // 去重 @@ -42,6 +61,6 @@ export const sceneParser = (rawScene: string, sceneName: string, sceneUrl: strin sceneUrl: sceneUrl, sentenceList: sentenceList, // 语句列表 assetsList: assetsList, // 资源列表 - subSceneList: subSceneList // 子场景列表 + subSceneList: subSceneList, // 子场景列表 }; }; diff --git a/packages/parser/src/scriptParser/argsParser.ts b/packages/parser/src/scriptParser/argsParser.ts index b2213082f..9cff77451 100644 --- a/packages/parser/src/scriptParser/argsParser.ts +++ b/packages/parser/src/scriptParser/argsParser.ts @@ -1,5 +1,5 @@ -import {arg} from "../interface/sceneInterface"; -import {fileType} from "../interface/assets"; +import { arg } from '../interface/sceneInterface'; +import { fileType } from '../interface/assets'; /** * 参数解析器 @@ -7,16 +7,19 @@ import {fileType} from "../interface/assets"; * @param assetSetter * @return {Array} 解析后的参数列表 */ -export function argsParser(argsRaw: string, assetSetter: (fileName: string, assetType: fileType) => string): Array { +export function argsParser( + argsRaw: string, + assetSetter: (fileName: string, assetType: fileType) => string, +): Array { const returnArrayList: Array = []; // 处理参数 // 不要去空格 - let newArgsRaw = argsRaw.replace(/ /g, " "); + let newArgsRaw = argsRaw.replace(/ /g, ' '); // 分割参数列表 - let rawArgsList: Array = newArgsRaw.split(" -"); + let rawArgsList: Array = newArgsRaw.split(' -'); // 去除空字符串 rawArgsList = rawArgsList.filter((e) => { - return e !== ""; + return e !== ''; }); rawArgsList.forEach((e) => { const equalSignIndex = e.indexOf('='); @@ -29,35 +32,35 @@ export function argsParser(argsRaw: string, assetSetter: (fileName: string, asse // 判断是不是语音参数 if (argName.toLowerCase().match(/.ogg|.mp3|.wav/)) { returnArrayList.push({ - key: "vocal", - value: assetSetter(e, fileType.vocal) + key: 'vocal', + value: assetSetter(e, fileType.vocal), }); } else { // 判断是不是省略参数 if (argValue === undefined) { returnArrayList.push({ key: argName, - value: true + value: true, }); } else { // 是字符串描述的布尔值 - if (argValue === "true" || argValue === "false") { + if (argValue === 'true' || argValue === 'false') { returnArrayList.push({ key: argName, - value: argValue === "true" + value: argValue === 'true', }); } else { // 是数字 if (!isNaN(Number(argValue))) { returnArrayList.push({ key: argName, - value: Number(argValue) + value: Number(argValue), }); } else { // 是普通参数 returnArrayList.push({ key: argName, - value: argValue + value: argValue, }); } } @@ -65,4 +68,4 @@ export function argsParser(argsRaw: string, assetSetter: (fileName: string, asse } }); return returnArrayList; -}; +} diff --git a/packages/parser/src/scriptParser/assetsScanner.ts b/packages/parser/src/scriptParser/assetsScanner.ts index a244eb1ca..cdff61be5 100644 --- a/packages/parser/src/scriptParser/assetsScanner.ts +++ b/packages/parser/src/scriptParser/assetsScanner.ts @@ -8,7 +8,11 @@ import { fileType } from '../interface/assets'; * @param args 参数列表 * @return {Array} 语句携带的参数列表 */ -export const assetsScanner = (command: commandType, content: string, args: Array): Array => { +export const assetsScanner = ( + command: commandType, + content: string, + args: Array, +): Array => { let hasVocalArg = false; const returnAssetsList: Array = []; if (command === commandType.say) { diff --git a/packages/parser/src/scriptParser/commandParser.ts b/packages/parser/src/scriptParser/commandParser.ts index 81390e115..b08655881 100644 --- a/packages/parser/src/scriptParser/commandParser.ts +++ b/packages/parser/src/scriptParser/commandParser.ts @@ -1,25 +1,34 @@ -import { commandType, parsedCommand } from "../interface/sceneInterface"; +import { ConfigMap } from '../config/scriptConfig'; +import { commandType, parsedCommand } from '../interface/sceneInterface'; /** * 处理命令 * @param commandRaw * @param ADD_NEXT_ARG_LIST - * @param SCRIPT_CONFIG + * @param SCRIPT_CONFIG_MAP * @return {parsedCommand} 处理后的命令 */ -export const commandParser = (commandRaw: string, ADD_NEXT_ARG_LIST:any, SCRIPT_CONFIG:any): parsedCommand => { +export const commandParser = ( + commandRaw: string, + ADD_NEXT_ARG_LIST: commandType[], + SCRIPT_CONFIG_MAP: ConfigMap, +): parsedCommand => { let returnCommand: parsedCommand = { type: commandType.say, // 默认是say - additionalArgs: [] + additionalArgs: [], }; // 开始处理命令内容 - const type: commandType = getCommandType(commandRaw, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); + const type: commandType = getCommandType( + commandRaw, + ADD_NEXT_ARG_LIST, + SCRIPT_CONFIG_MAP, + ); returnCommand.type = type; // 如果是对话,加上额外的参数 - if (type === commandType.say && commandRaw!== 'say') { + if (type === commandType.say && commandRaw !== 'say') { returnCommand.additionalArgs.push({ - key: "speaker", - value: commandRaw + key: 'speaker', + value: commandRaw, }); } returnCommand = addNextArg(returnCommand, type, ADD_NEXT_ARG_LIST); @@ -30,27 +39,26 @@ export const commandParser = (commandRaw: string, ADD_NEXT_ARG_LIST:any, SCRIPT_ * 根据command原始值判断是什么命令 * @param command command原始值 * @param ADD_NEXT_ARG_LIST - * @param SCRIPT_CONFIG + * @param SCRIPT_CONFIG_MAP * @return {commandType} 得到的command类型 */ -function getCommandType(command: string, ADD_NEXT_ARG_LIST:any, SCRIPT_CONFIG:any): commandType { - // if (command.match(/if/)) { - // return commandType.if; - // } - const commandMap = new Map(); - SCRIPT_CONFIG.forEach((e:any) => { - commandMap.set(e.scriptString, e.scriptType); - }); - if (commandMap.has(command)) { - return commandMap.get(command); - } else return commandType.say; +function getCommandType( + command: string, + ADD_NEXT_ARG_LIST: commandType[], + SCRIPT_CONFIG_MAP: ConfigMap, +): commandType { + return SCRIPT_CONFIG_MAP.get(command)?.scriptType ?? commandType.say; } -function addNextArg(commandToParse: parsedCommand, thisCommandType: commandType, ADD_NEXT_ARG_LIST:any) { +function addNextArg( + commandToParse: parsedCommand, + thisCommandType: commandType, + ADD_NEXT_ARG_LIST: commandType[], +) { if (ADD_NEXT_ARG_LIST.includes(thisCommandType)) { commandToParse.additionalArgs.push({ - key: "next", - value: true + key: 'next', + value: true, }); } return commandToParse; diff --git a/packages/parser/src/scriptParser/contentParser.ts b/packages/parser/src/scriptParser/contentParser.ts index 2b8c87ba9..fce148224 100644 --- a/packages/parser/src/scriptParser/contentParser.ts +++ b/packages/parser/src/scriptParser/contentParser.ts @@ -1,5 +1,5 @@ -import { commandType } from "../interface/sceneInterface"; -import { fileType } from "../interface/assets"; +import { commandType } from '../interface/sceneInterface'; +import { fileType } from '../interface/assets'; /** * 解析语句内容的函数,主要作用是把文件名改为绝对地址或相对地址(根据使用情况而定) @@ -8,9 +8,13 @@ import { fileType } from "../interface/assets"; * @param assetSetter * @return {string} 解析后的语句内容 */ -export const contentParser = (contentRaw: string, type: commandType, assetSetter: any) => { - if (contentRaw === "none" || contentRaw === "") { - return ""; +export const contentParser = ( + contentRaw: string, + type: commandType, + assetSetter: any, +) => { + if (contentRaw === 'none' || contentRaw === '') { + return ''; } switch (type) { case commandType.playEffect: @@ -41,12 +45,12 @@ export const contentParser = (contentRaw: string, type: commandType, assetSetter }; function getChooseContent(contentRaw: string, assetSetter: any): string { - const chooseList = contentRaw.split("|"); + const chooseList = contentRaw.split('|'); const chooseKeyList: Array = []; const chooseValueList: Array = []; for (const e of chooseList) { - chooseKeyList.push(e.split(":")[0] ?? ""); - chooseValueList.push(e.split(":")[1] ?? ""); + chooseKeyList.push(e.split(':')[0] ?? ''); + chooseValueList.push(e.split(':')[1] ?? ''); } const parsedChooseList = chooseValueList.map((e) => { if (e.match(/\./)) { @@ -55,10 +59,10 @@ function getChooseContent(contentRaw: string, assetSetter: any): string { return e; } }); - let ret = ""; + let ret = ''; for (let i = 0; i < chooseKeyList.length; i++) { if (i !== 0) { - ret = ret + "|"; + ret = ret + '|'; } ret = ret + `${chooseKeyList[i]}:${parsedChooseList[i]}`; } diff --git a/packages/parser/src/scriptParser/scriptParser.ts b/packages/parser/src/scriptParser/scriptParser.ts index a83bf7c68..aa33c6d15 100644 --- a/packages/parser/src/scriptParser/scriptParser.ts +++ b/packages/parser/src/scriptParser/scriptParser.ts @@ -1,18 +1,30 @@ -import { arg, commandType, IAsset, ISentence, parsedCommand } from "../interface/sceneInterface"; -import { commandParser } from "./commandParser"; -import { argsParser } from "./argsParser"; -import { contentParser } from "./contentParser"; -import { assetsScanner } from "./assetsScanner"; -import { subSceneScanner } from "./subSceneScanner"; +import { + arg, + commandType, + IAsset, + ISentence, + parsedCommand, +} from '../interface/sceneInterface'; +import { commandParser } from './commandParser'; +import { argsParser } from './argsParser'; +import { contentParser } from './contentParser'; +import { assetsScanner } from './assetsScanner'; +import { subSceneScanner } from './subSceneScanner'; +import { ConfigMap } from '../config/scriptConfig'; /** * 语句解析器 * @param sentenceRaw 原始语句 * @param assetSetter * @param ADD_NEXT_ARG_LIST - * @param SCRIPT_CONFIG + * @param SCRIPT_CONFIG_MAP */ -export const scriptParser = (sentenceRaw: string, assetSetter: any, ADD_NEXT_ARG_LIST: any, SCRIPT_CONFIG: any): ISentence => { +export const scriptParser = ( + sentenceRaw: string, + assetSetter: any, + ADD_NEXT_ARG_LIST: commandType[], + SCRIPT_CONFIG_MAP: ConfigMap, +): ISentence => { let command: commandType; // 默认为对话 let content: string; // 语句内容 let subScene: Array; // 语句携带的子场景(可能没有) @@ -24,16 +36,16 @@ export const scriptParser = (sentenceRaw: string, assetSetter: any, ADD_NEXT_ARG // 正式开始解析 // 去分号,前面已做,这里不再需要 - let newSentenceRaw = sentenceRaw.split(";")[0]; - if (newSentenceRaw === "") { + let newSentenceRaw = sentenceRaw.split(';')[0]; + if (newSentenceRaw === '') { // 注释提前返回 return { command: commandType.comment, // 语句类型 - commandRaw: "comment", // 命令原始内容,方便调试 - content: sentenceRaw.split(";")[1] ?? "", // 语句内容 - args: [{ key: "next", value: true }], // 参数列表 + commandRaw: 'comment', // 命令原始内容,方便调试 + content: sentenceRaw.split(';')[1] ?? '', // 语句内容 + args: [{ key: 'next', value: true }], // 参数列表 sentenceAssets: [], // 语句携带的资源列表 - subScene: [] // 语句携带的子场景 + subScene: [], // 语句携带的子场景 }; } // 截取命令 @@ -44,11 +56,11 @@ export const scriptParser = (sentenceRaw: string, assetSetter: any, ADD_NEXT_ARG // 没有command,说明这是一条连续对话或单条语句 if (getCommandResult === null) { commandRaw = newSentenceRaw; - parsedCommand = commandParser(commandRaw, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); + parsedCommand = commandParser(commandRaw, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP); command = parsedCommand.type; for (const e of parsedCommand.additionalArgs) { // 由于是连续对话,所以我们去除 speaker 参数。 - if (command === commandType.say && e.key === "speaker") { + if (command === commandType.say && e.key === 'speaker') { continue; } args.push(e); @@ -56,8 +68,11 @@ export const scriptParser = (sentenceRaw: string, assetSetter: any, ADD_NEXT_ARG } else { commandRaw = newSentenceRaw.substring(0, getCommandResult.index); // 划分命令区域和content区域 - newSentenceRaw = newSentenceRaw.substring(getCommandResult.index + 1, newSentenceRaw.length); - parsedCommand = commandParser(commandRaw, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG); + newSentenceRaw = newSentenceRaw.substring( + getCommandResult.index + 1, + newSentenceRaw.length, + ); + parsedCommand = commandParser(commandRaw, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP); command = parsedCommand.type; for (const e of parsedCommand.additionalArgs) { args.push(e); @@ -67,7 +82,10 @@ export const scriptParser = (sentenceRaw: string, assetSetter: any, ADD_NEXT_ARG const getArgsResult = / -/.exec(newSentenceRaw); // 获取到参数 if (getArgsResult) { - const argsRaw = newSentenceRaw.substring(getArgsResult.index, sentenceRaw.length); + const argsRaw = newSentenceRaw.substring( + getArgsResult.index, + sentenceRaw.length, + ); newSentenceRaw = newSentenceRaw.substring(0, getArgsResult.index); for (const e of argsParser(argsRaw, assetSetter)) { args.push(e); @@ -82,6 +100,6 @@ export const scriptParser = (sentenceRaw: string, assetSetter: any, ADD_NEXT_ARG content: content, // 语句内容 args: args, // 参数列表 sentenceAssets: sentenceAssets, // 语句携带的资源列表 - subScene: subScene // 语句携带的子场景 + subScene: subScene, // 语句携带的子场景 }; }; diff --git a/packages/parser/src/scriptParser/subSceneScanner.ts b/packages/parser/src/scriptParser/subSceneScanner.ts index d6f1fbeb3..96e964c38 100644 --- a/packages/parser/src/scriptParser/subSceneScanner.ts +++ b/packages/parser/src/scriptParser/subSceneScanner.ts @@ -3,16 +3,22 @@ * @param content 语句内容 * @return {Array} 子场景列表 */ -import { commandType } from "../interface/sceneInterface"; +import { commandType } from '../interface/sceneInterface'; -export const subSceneScanner = (command: commandType, content: string): Array => { +export const subSceneScanner = ( + command: commandType, + content: string, +): Array => { const subSceneList: Array = []; - if (command === commandType.changeScene || command === commandType.callScene) { + if ( + command === commandType.changeScene || + command === commandType.callScene + ) { subSceneList.push(content); } if (command === commandType.choose) { - const chooseList = content.split("|"); - const chooseValue = chooseList.map((e) => e.split(":")[1] ?? ""); + const chooseList = content.split('|'); + const chooseValue = chooseList.map((e) => e.split(':')[1] ?? ''); chooseValue.forEach((e) => { if (e.match(/\./)) { subSceneList.push(e); diff --git a/packages/webgal/package.json b/packages/webgal/package.json index 4edd62143..2f24d0396 100644 --- a/packages/webgal/package.json +++ b/packages/webgal/package.json @@ -1,7 +1,7 @@ { "name": "webgal", "private": true, - "version": "4.4.11", + "version": "4.4.12", "scripts": { "dev": "vite --host --port 3000", "build": "cross-env NODE_ENV=production tsc && vite build --base=./", @@ -9,6 +9,7 @@ "lint": "eslint src/** --fix" }, "dependencies": { + "@emotion/css": "^11.11.2", "@icon-park/react": "^1.4.2", "@reduxjs/toolkit": "^1.8.1", "angular-expressions": "^1.1.5", @@ -51,6 +52,6 @@ "prettier": "^2.6.2", "rollup-plugin-visualizer": "^5.6.0", "typescript": "^4.5.4", - "vite": "^4.4.9" + "vite": "^4.5.2" } } diff --git a/packages/webgal/public/game/config.txt b/packages/webgal/public/game/config.txt index 156eeaf14..3bc42af83 100644 --- a/packages/webgal/public/game/config.txt +++ b/packages/webgal/public/game/config.txt @@ -3,4 +3,3 @@ Game_key:0f87dstRg; Title_img:WebGAL_New_Enter_Image.png; Title_bgm:s_Title.mp3; Game_Logo:WebGalEnter.png; -Textbox_theme:imss; diff --git a/packages/webgal/public/game/template/Stage/TextBox/textbox.scss b/packages/webgal/public/game/template/Stage/TextBox/textbox.scss new file mode 100644 index 000000000..eb3f1d796 --- /dev/null +++ b/packages/webgal/public/game/template/Stage/TextBox/textbox.scss @@ -0,0 +1,63 @@ +.TextBox_main { + +} + +.TextBox_textElement_start { + +} + +.TextBox_textElement_Settled { + +} + +.text { + +} + +.outer { + +} + +.inner { + +} + +.TextBox_showName { + +} + +.outerName { + +} + +.innerName { + +} + +.miniAvatarContainer { + +} + +.miniAvatarImg { + +} + +@keyframes showSoftly { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes TextDelayShow { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} diff --git a/packages/webgal/public/game/template/UI/Title/title.scss b/packages/webgal/public/game/template/UI/Title/title.scss new file mode 100644 index 000000000..48073c96b --- /dev/null +++ b/packages/webgal/public/game/template/UI/Title/title.scss @@ -0,0 +1,58 @@ +.Title_main { + width: 100%; + height: 100%; + position: absolute; + z-index: 13; +} + +.Title_buttonList { + font-family: "思源宋体", serif; + display: flex; + position: absolute; + left: 0; + min-width: 25%; + height: 100%; + justify-content: center; + align-items: flex-start; + flex-flow: column; + transition: background 0.75s; + padding-left: 120px; +} + +.Title_button { + font-weight: bold; + text-align: center; + flex: 0 1 auto; + cursor: pointer; + padding: 1em 2em 1em 2em; + margin: 20px 0; + transition: all 0.33s; + background: rgba(255, 255, 255, 0.15); + backdrop-filter: blur(5px); + border-radius: 4px; + transform: skewX(-10deg); + background: linear-gradient(to right, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.1)); + + &:hover { + text-shadow: 0 0 10px rgba(255, 255, 255, 1); + padding: 1em 6em 1em 3em; + } +} + +.Title_button_text { + color: transparent; + background: linear-gradient(135deg, #fdfbfb 0%, #dcddde 100%); + -webkit-background-clip: text; + padding: 0 0.5em 0 0.5em; + font-size: 200%; + font-family: WebgalUI, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + letter-spacing: 0.15em; +} + +.Title_backup_background { + width: 100%; + height: 100%; + position: absolute; + z-index: 13; + background: linear-gradient(135deg, #fdfbfb 0%, #dcddde 100%); +} diff --git a/packages/webgal/public/game/template/template.json b/packages/webgal/public/game/template/template.json new file mode 100644 index 000000000..d7885e992 --- /dev/null +++ b/packages/webgal/public/game/template/template.json @@ -0,0 +1,4 @@ +{ + "name":"Default Template", + "webgal-version":"4.4.12" +} diff --git a/packages/webgal/src/App.tsx b/packages/webgal/src/App.tsx index 872325615..2ed11e21d 100644 --- a/packages/webgal/src/App.tsx +++ b/packages/webgal/src/App.tsx @@ -6,37 +6,36 @@ import Menu from '@/UI/Menu/Menu'; import { Stage } from '@/Stage/Stage'; import { BottomControlPanel } from '@/UI/BottomControlPanel/BottomControlPanel'; import { Backlog } from '@/UI/Backlog/Backlog'; -import { Provider } from 'react-redux'; -import { webgalStore } from './store/store'; import { Extra } from '@/UI/Extra/Extra'; import { BottomControlPanelFilm } from '@/UI/BottomControlPanel/BottomControlPanelFilm'; import GlobalDialog from '@/UI/GlobalDialog/GlobalDialog'; import DevPanel from '@/UI/DevPanel/DevPanel'; import Translation from '@/UI/Translation/Translation'; import { PanicOverlay } from '@/UI/PanicOverlay/PanicOverlay'; +import { useFullScreen } from './hooks/useFullScreen'; function App() { useEffect(() => { initializeScript(); }, []); + useFullScreen(); + // Provider用于对各组件提供状态 return (
- - - - - - - - <Logo /> - <Extra /> - <Menu /> - <GlobalDialog /> - <PanicOverlay /> - <DevPanel /> - </Provider> + <Translation /> + <Stage /> + <BottomControlPanel /> + <BottomControlPanelFilm /> + <Backlog /> + <Title /> + <Logo /> + <Extra /> + <Menu /> + <GlobalDialog /> + <PanicOverlay /> + <DevPanel /> </div> ); } diff --git a/packages/webgal/src/Core/Modules/events.ts b/packages/webgal/src/Core/Modules/events.ts new file mode 100644 index 000000000..af8b08eae --- /dev/null +++ b/packages/webgal/src/Core/Modules/events.ts @@ -0,0 +1,32 @@ +import mitt from 'mitt'; + +interface IWebgalEvent<T> { + on: (callback: (message?: T) => void, id?: string) => void; + off: (callback: (message?: T) => void, id?: string) => void; + emit: (message?: T, id?: string) => void; +} + +export class Events { + public textSettle = formEvent('text-settle'); + public userInteractNext = formEvent('__NEXT'); + public fullscreenDbClick = formEvent('fullscreen-dbclick'); + public styleUpdate = formEvent('style-update'); +} + +const eventBus = mitt(); + +function formEvent<T>(eventName: string): IWebgalEvent<T> { + return { + on: (callback, id?) => { + // @ts-ignore + eventBus.on(`${eventName}-${id ?? ''}`, callback); + }, + emit: (message?, id?) => { + eventBus.emit(`${eventName}-${id ?? ''}`, message); + }, + off: (callback, id?) => { + // @ts-ignore + eventBus.off(`${eventName}-${id ?? ''}`, callback); + }, + }; +} diff --git a/packages/webgal/src/Core/controller/customUI/scss2cssinjsParser.ts b/packages/webgal/src/Core/controller/customUI/scss2cssinjsParser.ts new file mode 100644 index 000000000..b31fd66eb --- /dev/null +++ b/packages/webgal/src/Core/controller/customUI/scss2cssinjsParser.ts @@ -0,0 +1,39 @@ +export interface IWebGALStyleObj { + classNameStyles: Record<string, string>; + others: string; +} + +export function scss2cssinjsParser(scssString: string): IWebGALStyleObj { + const [classNameStyles, others] = parseCSS(scssString); + return { + classNameStyles, + others, + }; +} + +/** + * GPT 4 写的,临时用,以后要重构!!! + * TODO:用人类智能重构,要是用着一直没问题,也不是不可以 trust AI + * @param css + */ +function parseCSS(css: string): [Record<string, string>, string] { + const result: Record<string, string> = {}; + let specialRules = ''; + let matches; + // 匹配类选择器和其内容,包括一层嵌套 + const classRegex = /\.([^{\s]+)\s*{([^}]*?(?:\{[^}]*?\}[^}]*?)?)}/g; + // 更新特殊规则正则表达式以正确处理一层嵌套,并确保大括号成对出现 + const specialRegex = /(@[^{]+{\s*(?:[^{}]*{[^}]*}[^{}]*)+\s*})/g; + + // 提取类和样式 + while ((matches = classRegex.exec(css)) !== null) { + result[matches[1]] = matches[2].trim().replace(/\s*;\s*/g, ';\n'); + } + + // 提取特殊规则,并确保包含所有的大括号 + while ((matches = specialRegex.exec(css)) !== null) { + specialRules += matches[1].trim() + '\n'; + } + + return [result, specialRules.trim()]; +} diff --git a/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts b/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts index 55ee242b3..c26b21db0 100644 --- a/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts +++ b/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts @@ -16,7 +16,7 @@ export const nextSentence = () => { /** * 发送 “发生点击下一句” 事件。 */ - WebGAL.eventBus.emit('__NEXT'); + WebGAL.events.userInteractNext.emit(); // 如果当前显示标题,那么不进行下一句 const GUIState = webgalStore.getState().GUI; diff --git a/packages/webgal/src/Core/controller/gamePlay/runScript.ts b/packages/webgal/src/Core/controller/gamePlay/runScript.ts index 1ee286c2b..e76b76174 100644 --- a/packages/webgal/src/Core/controller/gamePlay/runScript.ts +++ b/packages/webgal/src/Core/controller/gamePlay/runScript.ts @@ -1,15 +1,8 @@ import { ISentence } from '@/Core/controller/scene/sceneInterface'; -import { say } from '../../gameScripts/say'; import { initPerform, IPerform } from '@/Core/Modules/perform/performInterface'; import { WebGAL } from '@/Core/WebGAL'; -import { SCRIPT_CONFIG } from '@/Core/parser/sceneParser'; - -/** - * 规范函数的类型 - * @type {(sentence: ISentence) => IPerform} - */ -type scriptFunction = (sentence: ISentence) => IPerform; +import { scriptRegistry, SCRIPT_TAG_MAP, ScriptFunction } from '@/Core/parser/sceneParser'; /** * 语句调用器,真正执行语句的调用,并自动将演出在指定时间卸载 @@ -17,18 +10,7 @@ type scriptFunction = (sentence: ISentence) => IPerform; */ export const runScript = (script: ISentence) => { let perform: IPerform = initPerform; - let funcToRun: scriptFunction = say; // 默认是say - - // 建立语句类型到执行函数的映射 - const scriptToFuncMap = new Map(); - SCRIPT_CONFIG.forEach((e) => { - scriptToFuncMap.set(e.scriptType, e.scriptFunction); - }); - - // 根据脚本类型切换函数 - if (scriptToFuncMap.has(script.command)) { - funcToRun = scriptToFuncMap.get(script.command) as scriptFunction; - } + const funcToRun: ScriptFunction = scriptRegistry[script.command]?.scriptFunction ?? SCRIPT_TAG_MAP.say.scriptFunction; // 默认是say // 调用脚本对应的函数 perform = funcToRun(script); diff --git a/packages/webgal/src/Core/controller/scene/sceneInterface.ts b/packages/webgal/src/Core/controller/scene/sceneInterface.ts index 0bfae7900..a4c556dbf 100644 --- a/packages/webgal/src/Core/controller/scene/sceneInterface.ts +++ b/packages/webgal/src/Core/controller/scene/sceneInterface.ts @@ -37,6 +37,7 @@ export enum commandType { setTransform, setTransition, getUserInput, + applyStyle, } /** diff --git a/packages/webgal/src/Core/gameScripts/applyStyle.ts b/packages/webgal/src/Core/gameScripts/applyStyle.ts new file mode 100644 index 000000000..ca7e85d7e --- /dev/null +++ b/packages/webgal/src/Core/gameScripts/applyStyle.ts @@ -0,0 +1,30 @@ +import { ISentence } from '@/Core/controller/scene/sceneInterface'; +import { IPerform } from '@/Core/Modules/perform/performInterface'; +import { webgalStore } from '@/store/store'; +import { stageActions } from '@/store/stageReducer'; + +/** + * 语句执行的模板代码 + * @param sentence + */ +export const applyStyle = (sentence: ISentence): IPerform => { + const { content } = sentence; + const applyStyleSegments = content.split(','); + for (const applyStyleSegment of applyStyleSegments) { + const splitSegment = applyStyleSegment.split('->'); + if (splitSegment.length >= 2) { + const classNameToBeChange = splitSegment[0]; + const classNameChangeTo = splitSegment[1]; + webgalStore.dispatch(stageActions.replaceUIlable([classNameToBeChange, classNameChangeTo])); + } + } + return { + performName: 'none', + duration: 0, + isHoldOn: false, + stopFunction: () => {}, + blockingNext: () => false, + blockingAuto: () => true, + stopTimeout: undefined, // 暂时不用,后面会交给自动清除 + }; +}; diff --git a/packages/webgal/src/Core/gameScripts/end.ts b/packages/webgal/src/Core/gameScripts/end.ts index 2d17cfe67..adb2bc139 100644 --- a/packages/webgal/src/Core/gameScripts/end.ts +++ b/packages/webgal/src/Core/gameScripts/end.ts @@ -7,8 +7,9 @@ import { resetStage } from '@/Core/controller/stage/resetStage'; import { webgalStore } from '@/store/store'; import { setVisibility } from '@/store/GUIReducer'; import { playBgm } from '@/Core/controller/stage/playBgm'; - import { WebGAL } from '@/Core/WebGAL'; +import { resetFastSave } from '@/store/userDataReducer'; +import { syncStorageFast } from '@/Core/controller/storage/storageController'; /** * 结束游戏 @@ -19,8 +20,14 @@ export const end = (sentence: ISentence): IPerform => { const dispatch = webgalStore.dispatch; // 重新获取初始场景 const sceneUrl: string = assetSetter('start.txt', fileType.scene); - // 场景写入到运行时 + // 为了在 scriptExecutor 自增 sentenceId 后再重置场景 + setTimeout(() => { + WebGAL.sceneManager.resetScene(); + }, 5); + dispatch(resetFastSave()); + syncStorageFast(); sceneFetcher(sceneUrl).then((rawScene) => { + // 场景写入到运行时 WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, 'start.txt', sceneUrl); }); dispatch(setVisibility({ component: 'showTitle', visibility: true })); diff --git a/packages/webgal/src/Core/gameScripts/getUserInput/index.tsx b/packages/webgal/src/Core/gameScripts/getUserInput/index.tsx index 685af3fe8..68fb584bd 100644 --- a/packages/webgal/src/Core/gameScripts/getUserInput/index.tsx +++ b/packages/webgal/src/Core/gameScripts/getUserInput/index.tsx @@ -37,7 +37,9 @@ export const getUserInput = (sentence: ISentence): IPerform => { onClick={() => { const userInput: HTMLInputElement = document.getElementById('user-input') as HTMLInputElement; if (userInput) { - webgalStore.dispatch(setStageVar({ key: varKey, value: userInput?.value ?? '' })); + webgalStore.dispatch( + setStageVar({ key: varKey, value: (userInput?.value ?? '') === '' ? ' ' : userInput?.value ?? '' }), + ); } playSeClick(); WebGAL.gameplay.performController.unmountPerform('userInput'); diff --git a/packages/webgal/src/Core/gameScripts/intro.tsx b/packages/webgal/src/Core/gameScripts/intro.tsx index f7f559cee..294526f8f 100644 --- a/packages/webgal/src/Core/gameScripts/intro.tsx +++ b/packages/webgal/src/Core/gameScripts/intro.tsx @@ -137,7 +137,7 @@ export const intro = (sentence: ISentence): IPerform => { /** * 接受 next 事件 */ - WebGAL.eventBus.on('__NEXT', toNextIntroElement); + WebGAL.events.userInteractNext.on(toNextIntroElement); const showIntro = introArray.map((e, i) => ( <div @@ -171,7 +171,7 @@ export const intro = (sentence: ISentence): IPerform => { if (introContainer) { introContainer.style.display = 'none'; } - WebGAL.eventBus.off('__NEXT', toNextIntroElement); + WebGAL.events.userInteractNext.off(toNextIntroElement); }, blockingNext: () => isBlocking, blockingAuto: () => isBlocking, diff --git a/packages/webgal/src/Core/gameScripts/playEffect.ts b/packages/webgal/src/Core/gameScripts/playEffect.ts index d789c40cf..68e1ded27 100644 --- a/packages/webgal/src/Core/gameScripts/playEffect.ts +++ b/packages/webgal/src/Core/gameScripts/playEffect.ts @@ -63,19 +63,18 @@ export const playEffect = (sentence: ISentence): IPerform => { skipNextCollect: true, stopFunction: () => { // 演出已经结束了,所以不用播放效果音了 - seElement.oncanplay = () => {}; seElement.pause(); }, blockingNext: () => false, blockingAuto: () => { + // loop 的话就不 block auto + if (isLoop) return false; return !isOver; }, stopTimeout: undefined, // 暂时不用,后面会交给自动清除 }; resolve(perform); - seElement.oncanplay = () => { - seElement?.play(); - }; + seElement?.play(); seElement.onended = () => { for (const e of WebGAL.gameplay.performController.performList) { if (e.performName === performInitName) { diff --git a/packages/webgal/src/Core/gameScripts/playVideo.tsx b/packages/webgal/src/Core/gameScripts/playVideo.tsx index ded813d0f..1d137fe14 100644 --- a/packages/webgal/src/Core/gameScripts/playVideo.tsx +++ b/packages/webgal/src/Core/gameScripts/playVideo.tsx @@ -63,9 +63,7 @@ export const playVideo = (sentence: ISentence): IPerform => { endPerform(); }; // 双击可跳过视频 - WebGAL.eventBus.on('fullscreen-dbclick', () => { - skipVideo(); - }); + WebGAL.events.fullscreenDbClick.on(skipVideo); // 播放并作为一个特别演出加入 const perform = { performName: performInitName, @@ -73,10 +71,7 @@ export const playVideo = (sentence: ISentence): IPerform => { isOver: false, isHoldOn: false, stopFunction: () => { - /** - * 不要播放视频了,因为演出已经没有了。 - */ - VocalControl.oncanplay = () => {}; + WebGAL.events.fullscreenDbClick.off(skipVideo); /** * 恢复音量 */ @@ -99,23 +94,22 @@ export const playVideo = (sentence: ISentence): IPerform => { goNextWhenOver: true, }; resolve(perform); - VocalControl.oncanplay = () => { - /** - * 把bgm和语音的音量设为0 - */ - const vocalVol = 0; - const bgmVol = 0; - const bgmElement: any = document.getElementById('currentBgm'); - if (bgmElement) { - bgmElement.volume = bgmVol.toString(); - } - const vocalElement: any = document.getElementById('currentVocal'); - if (bgmElement) { - vocalElement.volume = vocalVol.toString(); - } + /** + * 把bgm和语音的音量设为0 + */ + const vocalVol2 = 0; + const bgmVol2 = 0; + const bgmElement: any = document.getElementById('currentBgm'); + if (bgmElement) { + bgmElement.volume = bgmVol2.toString(); + } + const vocalElement: any = document.getElementById('currentVocal'); + if (bgmElement) { + vocalElement.volume = vocalVol2.toString(); + } + + VocalControl?.play(); - VocalControl?.play(); - }; VocalControl.onended = () => { endPerform(); }; diff --git a/packages/webgal/src/Core/gameScripts/say.ts b/packages/webgal/src/Core/gameScripts/say.ts index 4af9aeb31..5267c3476 100644 --- a/packages/webgal/src/Core/gameScripts/say.ts +++ b/packages/webgal/src/Core/gameScripts/say.ts @@ -98,7 +98,7 @@ export const say = (sentence: ISentence): IPerform => { duration: sentenceDelay + endDelay, isHoldOn: false, stopFunction: () => { - WebGAL.eventBus.emit('text-settle'); + WebGAL.events.textSettle.emit(); }, blockingNext: () => false, blockingAuto: () => true, diff --git a/packages/webgal/src/Core/gameScripts/showVars.ts b/packages/webgal/src/Core/gameScripts/showVars.ts index 47cb78d57..1e677d176 100644 --- a/packages/webgal/src/Core/gameScripts/showVars.ts +++ b/packages/webgal/src/Core/gameScripts/showVars.ts @@ -25,7 +25,7 @@ export const showVars = (sentence: ISentence): IPerform => { dispatch(setStage({ key: 'showName', value: '展示变量' })); logger.debug('展示变量:', allVar); setTimeout(() => { - WebGAL.eventBus.emit('text-settle'); + WebGAL.events.textSettle.emit(); }, 0); const performInitName: string = getRandomPerformName(); const endDelay = 750 - userDataState.optionData.textSpeed * 250; @@ -34,7 +34,7 @@ export const showVars = (sentence: ISentence): IPerform => { duration: endDelay, isHoldOn: false, stopFunction: () => { - WebGAL.eventBus.emit('text-settle'); + WebGAL.events.textSettle.emit(); }, blockingNext: () => false, blockingAuto: () => true, diff --git a/packages/webgal/src/Core/gameScripts/vocal/index.ts b/packages/webgal/src/Core/gameScripts/vocal/index.ts index af9edf817..e4e34661d 100644 --- a/packages/webgal/src/Core/gameScripts/vocal/index.ts +++ b/packages/webgal/src/Core/gameScripts/vocal/index.ts @@ -88,8 +88,6 @@ export const playVocal = (sentence: ISentence) => { isOver: false, isHoldOn: false, stopFunction: () => { - // 演出已经结束了,所以不用播放语音了 - VocalControl.oncanplay = () => {}; clearInterval(audioContextWrapper.audioLevelInterval); VocalControl.pause(); key = key ? key : `fig-${pos}`; @@ -114,78 +112,77 @@ export const playVocal = (sentence: ISentence) => { stopTimeout: undefined, // 暂时不用,后面会交给自动清除 }; WebGAL.gameplay.performController.arrangeNewPerform(perform, sentence, false); - VocalControl.oncanplay = () => { - key = key ? key : `fig-${pos}`; - const animationItem = figureAssociatedAnimation.find((tid) => tid.targetId === key); - if (animationItem) { - let maxAudioLevel = 0; + key = key ? key : `fig-${pos}`; + const animationItem = figureAssociatedAnimation.find((tid) => tid.targetId === key); + if (animationItem) { + let maxAudioLevel = 0; - const foundFigure = freeFigure.find((figure) => figure.key === key); + const foundFigure = freeFigure.find((figure) => figure.key === key); - if (foundFigure) { - pos = foundFigure.basePosition; - } - - if (!audioContextWrapper.audioContext) { - let audioContext: AudioContext | null; - audioContext = new AudioContext(); - audioContextWrapper.analyser = audioContext.createAnalyser(); - audioContextWrapper.analyser.fftSize = 256; - audioContextWrapper.dataArray = new Uint8Array(audioContextWrapper.analyser.frequencyBinCount); - } + if (foundFigure) { + pos = foundFigure.basePosition; + } - if (!audioContextWrapper.analyser) { - audioContextWrapper.analyser = audioContextWrapper.audioContext.createAnalyser(); - audioContextWrapper.analyser.fftSize = 256; - } + if (!audioContextWrapper.audioContext) { + let audioContext: AudioContext | null; + audioContext = new AudioContext(); + audioContextWrapper.analyser = audioContext.createAnalyser(); + audioContextWrapper.analyser.fftSize = 256; + audioContextWrapper.dataArray = new Uint8Array(audioContextWrapper.analyser.frequencyBinCount); + } - bufferLength = audioContextWrapper.analyser.frequencyBinCount; - audioContextWrapper.dataArray = new Uint8Array(bufferLength); - let vocalControl = document.getElementById('currentVocal') as HTMLMediaElement; + if (!audioContextWrapper.analyser) { + audioContextWrapper.analyser = audioContextWrapper.audioContext.createAnalyser(); + audioContextWrapper.analyser.fftSize = 256; + } - if (!audioContextWrapper.source) { - audioContextWrapper.source = audioContextWrapper.audioContext.createMediaElementSource(vocalControl); - audioContextWrapper.source.connect(audioContextWrapper.analyser); - } + bufferLength = audioContextWrapper.analyser.frequencyBinCount; + audioContextWrapper.dataArray = new Uint8Array(bufferLength); + let vocalControl = document.getElementById('currentVocal') as HTMLMediaElement; - audioContextWrapper.analyser.connect(audioContextWrapper.audioContext.destination); - - // Lip-snc Animation - audioContextWrapper.audioLevelInterval = setInterval(() => { - const audioLevel = getAudioLevel( - audioContextWrapper.analyser!, - audioContextWrapper.dataArray!, - bufferLength, - ); - const { OPEN_THRESHOLD, HALF_OPEN_THRESHOLD } = updateThresholds(audioLevel); - - performMouthAnimation({ - audioLevel, - OPEN_THRESHOLD, - HALF_OPEN_THRESHOLD, - currentMouthValue, - lerpSpeed, - key, - animationItem, - pos, - }); - }, 50); - - // blinkAnimation - let animationEndTime: number; - - // 10sec - animationEndTime = Date.now() + 10000; - performBlinkAnimation({ key, animationItem, pos, animationEndTime }); - - // 10sec - setTimeout(() => { - clearTimeout(audioContextWrapper.blinkTimerID); - }, 10000); + if (!audioContextWrapper.source) { + audioContextWrapper.source = audioContextWrapper.audioContext.createMediaElementSource(vocalControl); + audioContextWrapper.source.connect(audioContextWrapper.analyser); } - VocalControl?.play(); - }; + audioContextWrapper.analyser.connect(audioContextWrapper.audioContext.destination); + + // Lip-snc Animation + audioContextWrapper.audioLevelInterval = setInterval(() => { + const audioLevel = getAudioLevel( + audioContextWrapper.analyser!, + audioContextWrapper.dataArray!, + bufferLength, + ); + const { OPEN_THRESHOLD, HALF_OPEN_THRESHOLD } = updateThresholds(audioLevel); + + performMouthAnimation({ + audioLevel, + OPEN_THRESHOLD, + HALF_OPEN_THRESHOLD, + currentMouthValue, + lerpSpeed, + key, + animationItem, + pos, + }); + }, 50); + + // blinkAnimation + let animationEndTime: number; + + // 10sec + animationEndTime = Date.now() + 10000; + performBlinkAnimation({ key, animationItem, pos, animationEndTime }); + + // 10sec + setTimeout(() => { + clearTimeout(audioContextWrapper.blinkTimerID); + }, 10000); + } + + VocalControl?.play(); + VocalControl.onended = () => { for (const e of WebGAL.gameplay.performController.performList) { if (e.performName === performInitName) { diff --git a/packages/webgal/src/Core/initializeScript.ts b/packages/webgal/src/Core/initializeScript.ts index 80f648936..51f8c3c02 100644 --- a/packages/webgal/src/Core/initializeScript.ts +++ b/packages/webgal/src/Core/initializeScript.ts @@ -24,8 +24,8 @@ export const isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); // 判断是否 export const initializeScript = (): void => { // 打印初始log信息 logger.info(__INFO.version); - logger.info('Github: https://github.com/MakinoharaShoko/WebGAL '); - logger.info('Made with ❤ by MakinoharaShoko'); + logger.info('Github: https://github.com/OpenWebGAL/WebGAL '); + logger.info('Made with ❤ by OpenWebGAL'); // 激活强制缩放 // 在调整窗口大小时重新计算宽高,设计稿按照 1600*900。 if (isIOS) { diff --git a/packages/webgal/src/Core/parser/sceneParser.ts b/packages/webgal/src/Core/parser/sceneParser.ts index 9bc54b274..74a19f419 100644 --- a/packages/webgal/src/Core/parser/sceneParser.ts +++ b/packages/webgal/src/Core/parser/sceneParser.ts @@ -1,100 +1,79 @@ -import { IScene } from '../controller/scene/sceneInterface'; -import { logger } from '../util/logger'; -import { assetsPrefetcher } from '@/Core/util/prefetcher/assetsPrefetcher'; import { assetSetter } from '@/Core/util/gameAssetsAccess/assetSetter'; +import { assetsPrefetcher } from '@/Core/util/prefetcher/assetsPrefetcher'; import SceneParser from 'webgal-parser'; - -import { commandType, ISentence } from '@/Core/controller/scene/sceneInterface'; -import { intro } from '@/Core/gameScripts/intro'; +import { commandType, IScene } from '../controller/scene/sceneInterface'; +import { logger } from '../util/logger'; +import { bgm } from '@/Core/gameScripts/bgm'; +import { callSceneScript } from '@/Core/gameScripts/callSceneScript'; import { changeBg } from '@/Core/gameScripts/changeBg'; import { changeFigure } from '@/Core/gameScripts/changeFigure'; -import { miniAvatar } from '@/Core/gameScripts/miniAvatar'; import { changeSceneScript } from '@/Core/gameScripts/changeSceneScript'; import { choose } from '@/Core/gameScripts/choose'; -import { end } from '../gameScripts/end'; -import { bgm } from '@/Core/gameScripts/bgm'; +import { comment } from '@/Core/gameScripts/comment'; +import { filmMode } from '@/Core/gameScripts/filmMode'; +import { getUserInput } from '@/Core/gameScripts/getUserInput'; +import { intro } from '@/Core/gameScripts/intro'; +import { label } from '@/Core/gameScripts/label'; +import { miniAvatar } from '@/Core/gameScripts/miniAvatar'; +import { pixi } from '@/Core/gameScripts/pixi'; +import { playEffect } from '@/Core/gameScripts/playEffect'; import { playVideo } from '@/Core/gameScripts/playVideo'; +import { setAnimation } from '@/Core/gameScripts/setAnimation'; import { setComplexAnimation } from '@/Core/gameScripts/setComplexAnimation'; import { setFilter } from '@/Core/gameScripts/setFilter'; -import { pixiInit } from '../gameScripts/pixi/pixiInit'; -import { pixi } from '@/Core/gameScripts/pixi'; -import { label } from '@/Core/gameScripts/label'; -import { jumpLabel } from '../gameScripts/jumpLabel'; -import { setVar } from '../gameScripts/setVar'; -import { showVars } from '../gameScripts/showVars'; -import { unlockCg } from '@/Core/gameScripts/unlockCg'; -import { unlockBgm } from '@/Core/gameScripts/unlockBgm'; -import { say } from '../gameScripts/say'; -import { filmMode } from '@/Core/gameScripts/filmMode'; -import { callSceneScript } from '@/Core/gameScripts/callSceneScript'; -import { setTextbox } from '@/Core/gameScripts/setTextbox'; -import { setAnimation } from '@/Core/gameScripts/setAnimation'; -import { playEffect } from '@/Core/gameScripts/playEffect'; import { setTempAnimation } from '@/Core/gameScripts/setTempAnimation'; -import { comment } from '@/Core/gameScripts/comment'; -import { IPerform } from '@/Core/Modules/perform/performInterface'; +import { setTextbox } from '@/Core/gameScripts/setTextbox'; import { setTransform } from '@/Core/gameScripts/setTransform'; import { setTransition } from '@/Core/gameScripts/setTransition'; -import { getUserInput } from '@/Core/gameScripts/getUserInput'; +import { unlockBgm } from '@/Core/gameScripts/unlockBgm'; +import { unlockCg } from '@/Core/gameScripts/unlockCg'; +import { end } from '../gameScripts/end'; +import { jumpLabel } from '../gameScripts/jumpLabel'; +import { pixiInit } from '../gameScripts/pixi/pixiInit'; +import { say } from '../gameScripts/say'; +import { setVar } from '../gameScripts/setVar'; +import { showVars } from '../gameScripts/showVars'; +import { defineScripts, IConfigInterface, ScriptConfig, ScriptFunction, scriptRegistry } from './utils'; +import { applyStyle } from '@/Core/gameScripts/applyStyle'; -interface IConfigInterface { - scriptString: string; - scriptType: commandType; - scriptFunction: (sentence: ISentence) => IPerform; -} +export const SCRIPT_TAG_MAP = defineScripts({ + intro: ScriptConfig(commandType.intro, intro), + changeBg: ScriptConfig(commandType.changeBg, changeBg), + changeFigure: ScriptConfig(commandType.changeFigure, changeFigure), + miniAvatar: ScriptConfig(commandType.miniAvatar, miniAvatar, { next: true }), + changeScene: ScriptConfig(commandType.changeScene, changeSceneScript), + choose: ScriptConfig(commandType.choose, choose), + end: ScriptConfig(commandType.end, end), + bgm: ScriptConfig(commandType.bgm, bgm, { next: true }), + playVideo: ScriptConfig(commandType.video, playVideo), + setComplexAnimation: ScriptConfig(commandType.setComplexAnimation, setComplexAnimation), + setFilter: ScriptConfig(commandType.setFilter, setFilter), + pixiInit: ScriptConfig(commandType.pixiInit, pixiInit, { next: true }), + pixiPerform: ScriptConfig(commandType.pixi, pixi, { next: true }), + label: ScriptConfig(commandType.label, label, { next: true }), + jumpLabel: ScriptConfig(commandType.jumpLabel, jumpLabel), + setVar: ScriptConfig(commandType.setVar, setVar, { next: true }), + showVars: ScriptConfig(commandType.showVars, showVars), + unlockCg: ScriptConfig(commandType.unlockCg, unlockCg, { next: true }), + unlockBgm: ScriptConfig(commandType.unlockBgm, unlockBgm, { next: true }), + say: ScriptConfig(commandType.say, say), + filmMode: ScriptConfig(commandType.filmMode, filmMode, { next: true }), + callScene: ScriptConfig(commandType.callScene, callSceneScript), + setTextbox: ScriptConfig(commandType.setTextbox, setTextbox), + setAnimation: ScriptConfig(commandType.setAnimation, setAnimation), + playEffect: ScriptConfig(commandType.playEffect, playEffect, { next: true }), + setTempAnimation: ScriptConfig(commandType.setTempAnimation, setTempAnimation), + __commment: ScriptConfig(commandType.comment, comment, { next: true }), + setTransform: ScriptConfig(commandType.setTransform, setTransform), + setTransition: ScriptConfig(commandType.setTransition, setTransition, { next: true }), + getUserInput: ScriptConfig(commandType.getUserInput, getUserInput), + applyStyle: ScriptConfig(commandType.applyStyle, applyStyle, { next: true }), + // if: ScriptConfig(commandType.if, undefined, { next: true }), +}); -export const SCRIPT_CONFIG: IConfigInterface[] = [ - { scriptString: 'intro', scriptType: commandType.intro, scriptFunction: intro }, - { scriptString: 'changeBg', scriptType: commandType.changeBg, scriptFunction: changeBg }, - { scriptString: 'changeFigure', scriptType: commandType.changeFigure, scriptFunction: changeFigure }, - { scriptString: 'miniAvatar', scriptType: commandType.miniAvatar, scriptFunction: miniAvatar }, - { scriptString: 'changeScene', scriptType: commandType.changeScene, scriptFunction: changeSceneScript }, - { scriptString: 'choose', scriptType: commandType.choose, scriptFunction: choose }, - { scriptString: 'end', scriptType: commandType.end, scriptFunction: end }, - { scriptString: 'bgm', scriptType: commandType.bgm, scriptFunction: bgm }, - { scriptString: 'playVideo', scriptType: commandType.video, scriptFunction: playVideo }, - { - scriptString: 'setComplexAnimation', - scriptType: commandType.setComplexAnimation, - scriptFunction: setComplexAnimation, - }, - { scriptString: 'setFilter', scriptType: commandType.setFilter, scriptFunction: setFilter }, - { scriptString: 'pixiInit', scriptType: commandType.pixiInit, scriptFunction: pixiInit }, - { scriptString: 'pixiPerform', scriptType: commandType.pixi, scriptFunction: pixi }, - { scriptString: 'label', scriptType: commandType.label, scriptFunction: label }, - { scriptString: 'jumpLabel', scriptType: commandType.jumpLabel, scriptFunction: jumpLabel }, - { scriptString: 'setVar', scriptType: commandType.setVar, scriptFunction: setVar }, - { scriptString: 'callScene', scriptType: commandType.callScene, scriptFunction: changeSceneScript }, - { scriptString: 'showVars', scriptType: commandType.showVars, scriptFunction: showVars }, - { scriptString: 'unlockCg', scriptType: commandType.unlockCg, scriptFunction: unlockCg }, - { scriptString: 'unlockBgm', scriptType: commandType.unlockBgm, scriptFunction: unlockBgm }, - { scriptString: 'say', scriptType: commandType.say, scriptFunction: say }, - { scriptString: 'filmMode', scriptType: commandType.filmMode, scriptFunction: filmMode }, - { scriptString: 'callScene', scriptType: commandType.callScene, scriptFunction: callSceneScript }, - { scriptString: 'setTextbox', scriptType: commandType.setTextbox, scriptFunction: setTextbox }, - { scriptString: 'setAnimation', scriptType: commandType.setAnimation, scriptFunction: setAnimation }, - { scriptString: 'playEffect', scriptType: commandType.playEffect, scriptFunction: playEffect }, - { scriptString: 'setTempAnimation', scriptType: commandType.setTempAnimation, scriptFunction: setTempAnimation }, - { scriptString: '__commment', scriptType: commandType.comment, scriptFunction: comment }, - { scriptString: 'setTransform', scriptType: commandType.setTransform, scriptFunction: setTransform }, - { scriptString: 'setTransition', scriptType: commandType.setTransition, scriptFunction: setTransition }, - { scriptString: 'getUserInput', scriptType: commandType.getUserInput, scriptFunction: getUserInput }, -]; -export const ADD_NEXT_ARG_LIST = [ - commandType.bgm, - commandType.pixi, - commandType.pixiInit, - commandType.label, - commandType.if, - commandType.miniAvatar, - commandType.setVar, - commandType.unlockBgm, - commandType.unlockCg, - commandType.filmMode, - commandType.playEffect, - commandType.comment, - commandType.setTransition, -]; +export const SCRIPT_CONFIG: IConfigInterface[] = Object.values(SCRIPT_TAG_MAP); + +export const ADD_NEXT_ARG_LIST = SCRIPT_CONFIG.filter((config) => config.next).map((config) => config.scriptType); /** * 场景解析器 @@ -110,3 +89,5 @@ export const sceneParser = (rawScene: string, sceneName: string, sceneUrl: strin logger.info(`解析场景:${sceneName},数据为:`, parsedScene); return parsedScene; }; + +export { scriptRegistry, type ScriptFunction }; diff --git a/packages/webgal/src/Core/parser/utils.ts b/packages/webgal/src/Core/parser/utils.ts new file mode 100644 index 000000000..2c3ee5b3e --- /dev/null +++ b/packages/webgal/src/Core/parser/utils.ts @@ -0,0 +1,41 @@ +import { commandType, ISentence } from '@/Core/controller/scene/sceneInterface'; +import { IPerform } from '@/Core/Modules/perform/performInterface'; + +/** + * 规范函数的类型 + * @type {(sentence: ISentence) => IPerform} + */ +export type ScriptFunction = (sentence: ISentence) => IPerform; + +export interface ScriptConfig { + scriptType: commandType; + scriptFunction: ScriptFunction; + next?: boolean; +} + +export interface IConfigInterface extends ScriptConfig { + scriptString: string; +} + +export function ScriptConfig( + scriptType: commandType, + scriptFunction: ScriptFunction, + config?: Omit<ScriptConfig, 'scriptType' | 'scriptFunction'>, +): ScriptConfig { + return { scriptType, scriptFunction, ...config }; +} + +export const scriptRegistry: Record<commandType, IConfigInterface> = {} as any; + +export function defineScripts<R extends Record<string, Omit<IConfigInterface, 'scriptString'>>>( + record: R, +): { + [K in keyof R]: IConfigInterface; +} { + // eslint-disable-next-line + const result = {} as Record<keyof R, IConfigInterface>; + for (const [scriptString, config] of Object.entries(record)) { + result[scriptString as keyof R] = scriptRegistry[config.scriptType] = { scriptString, ...config }; + } + return result; +} diff --git a/packages/webgal/src/Core/util/coreInitialFunction/infoFetcher.ts b/packages/webgal/src/Core/util/coreInitialFunction/infoFetcher.ts index 23c846bb9..854677689 100644 --- a/packages/webgal/src/Core/util/coreInitialFunction/infoFetcher.ts +++ b/packages/webgal/src/Core/util/coreInitialFunction/infoFetcher.ts @@ -3,7 +3,7 @@ import { logger } from '../logger'; import { assetSetter, fileType } from '../gameAssetsAccess/assetSetter'; import { getStorage } from '../../controller/storage/storageController'; import { webgalStore } from '@/store/store'; -import { setGuiAsset, setLogoImage, setThemeConfigItem } from '@/store/GUIReducer'; +import { setGuiAsset, setLogoImage } from '@/store/GUIReducer'; import { setEbg } from '@/Core/gameScripts/changeBg/setEbg'; import { initKey } from '@/Core/controller/storage/fastSaveLoad'; import { WebgalParser } from '@/Core/parser/sceneParser'; @@ -61,10 +61,6 @@ export const infoFetcher = (url: string) => { getStorage(); break; } - - case 'Textbox_theme': { - dispatch(setThemeConfigItem({ key: 'textbox', value: args[0] })); - } } }); } diff --git a/packages/webgal/src/Core/util/match.ts b/packages/webgal/src/Core/util/match.ts index 3a66b277f..b83f66f83 100644 --- a/packages/webgal/src/Core/util/match.ts +++ b/packages/webgal/src/Core/util/match.ts @@ -1,39 +1,35 @@ -type Case<T, R> = [T, () => R]; - class Matcher<T, R = any> { - private cases: Array<Case<T, R>> = []; - private readonly subject: T; - private defaultCase?: () => R; + private subject: T; + private result: R | undefined; + private isEnd = false; public constructor(subject: T) { this.subject = subject; } public with(pattern: T, fn: () => R): this { - this.cases.push([pattern, fn]); + if (!this.isEnd && this.subject === pattern) { + this.result = fn(); + this.isEnd = true; + } return this; } public endsWith(pattern: T, fn: () => R) { - this.cases.push([pattern, fn]); + if (!this.isEnd && this.subject === pattern) { + this.result = fn(); + this.isEnd = true; + } return this.evaluate(); } public default(fn: () => R) { - this.defaultCase = fn; + if (!this.isEnd) this.result = fn(); return this.evaluate(); } private evaluate(): R | undefined { - for (const [pattern, action] of this.cases) { - if (pattern === this.subject) { - return action(); - } - } - if (this.defaultCase) { - return this.defaultCase(); - } - return undefined; + return this.result; } } diff --git a/packages/webgal/src/Core/util/pixiPerformManager/initRegister.ts b/packages/webgal/src/Core/util/pixiPerformManager/initRegister.ts index 28c98b9d7..2003b782f 100644 --- a/packages/webgal/src/Core/util/pixiPerformManager/initRegister.ts +++ b/packages/webgal/src/Core/util/pixiPerformManager/initRegister.ts @@ -1,3 +1,3 @@ import '../../gameScripts/pixi/performs/cherryBlossoms'; import '../../gameScripts/pixi/performs/rain'; -import '../../gameScripts/pixi/performs/snow'; \ No newline at end of file +import '../../gameScripts/pixi/performs/snow'; diff --git a/packages/webgal/src/Core/util/syncWithEditor/webSocketFunc.ts b/packages/webgal/src/Core/util/syncWithEditor/webSocketFunc.ts index 25b54a12b..150520746 100644 --- a/packages/webgal/src/Core/util/syncWithEditor/webSocketFunc.ts +++ b/packages/webgal/src/Core/util/syncWithEditor/webSocketFunc.ts @@ -49,6 +49,9 @@ export const webSocketFunc = () => { const sentence = scene.sentenceList[0]; runScript(sentence); } + if (message.command === DebugCommand.REFETCH_TEMPLATE_FILES) { + WebGAL.events.styleUpdate.emit(); + } }; socket.onerror = (e) => { logger.info('当前没有连接到 Terre 编辑器'); diff --git a/packages/webgal/src/Core/webgalCore.ts b/packages/webgal/src/Core/webgalCore.ts index d9b7021ff..f1becd487 100644 --- a/packages/webgal/src/Core/webgalCore.ts +++ b/packages/webgal/src/Core/webgalCore.ts @@ -3,6 +3,7 @@ import mitt from 'mitt'; import { SceneManager } from '@/Core/Modules/scene'; import { AnimationManager } from '@/Core/Modules/animations'; import { Gameplay } from './Modules/gamePlay'; +import { Events } from '@/Core/Modules/events'; export class WebgalCore { public sceneManager = new SceneManager(); @@ -11,5 +12,5 @@ export class WebgalCore { public gameplay = new Gameplay(); public gameName = ''; public gameKey = ''; - public eventBus = mitt(); + public events = new Events(); } diff --git a/packages/webgal/src/Stage/Stage.tsx b/packages/webgal/src/Stage/Stage.tsx index fa01ee1a1..b782a7777 100644 --- a/packages/webgal/src/Stage/Stage.tsx +++ b/packages/webgal/src/Stage/Stage.tsx @@ -126,7 +126,7 @@ export const Stage: FC = () => { nextSentence(); }} onDoubleClick={() => { - WebGAL.eventBus.emit('fullscreen-dbclick'); + WebGAL.events.fullscreenDbClick.emit(); }} id="FullScreenClick" style={{ width: '100%', height: '100%', position: 'absolute', zIndex: '12', top: '0' }} diff --git a/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx new file mode 100644 index 000000000..642d61d53 --- /dev/null +++ b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx @@ -0,0 +1,158 @@ +import styles from './textbox.module.scss'; +import { ReactNode, useEffect } from 'react'; +import { WebGAL } from '@/Core/WebGAL'; +import { ITextboxProps } from './types'; +import useApplyStyle from '@/hooks/useApplyStyle'; + +export default function IMSSTextbox(props: ITextboxProps) { + const { + textArray, + textDelay, + currentConcatDialogPrev, + currentDialogKey, + isText, + isSafari, + isFirefox: boolean, + fontSize, + miniAvatar, + showName, + font, + textDuration, + isUseStroke, + textboxOpacity, + } = props; + + const applyStyle = useApplyStyle('Stage/TextBox/textbox.scss'); + + useEffect(() => { + function settleText() { + const textElements = document.querySelectorAll('.Textelement_start'); + const textArray = [...textElements]; + textArray.forEach((e) => { + e.className = applyStyle('TextBox_textElement_Settled', styles.TextBox_textElement_Settled); + }); + } + WebGAL.events.textSettle.on(settleText); + return () => { + WebGAL.events.textSettle.off(settleText); + }; + }, []); + + let allTextIndex = 0; + const textElementList = textArray.map((line, index) => { + const textLine = line.map((e, index) => { + // if (e === '<br />') { + // return <br key={`br${index}`} />; + // } + let delay = allTextIndex * textDelay; + allTextIndex++; + let prevLength = currentConcatDialogPrev.length; + if (currentConcatDialogPrev !== '' && index >= prevLength) { + delay = delay - prevLength * textDelay; + } + if (index < prevLength) { + return ( + <span + // data-text={e} + id={`${delay}`} + className={applyStyle('TextBox_textElement_Settled', styles.TextBox_textElement_Settled)} + key={currentDialogKey + index} + style={{ animationDelay: `${delay}ms`, animationDuration: `${textDuration}ms` }} + > + <span className={styles.zhanwei}> + {e} + <span className={applyStyle('outer', styles.outer)}>{e}</span> + {isUseStroke && <span className={applyStyle('inner', styles.inner)}>{e}</span>} + </span> + </span> + ); + } + return ( + <span + data-text={e} + id={`${delay}`} + className={`${applyStyle('TextBox_textElement_start', styles.TextBox_textElement_start)} Textelement_start`} + key={currentDialogKey + index} + style={{ animationDelay: `${delay}ms`, position: 'relative' }} + > + <span className={styles.zhanwei}> + {e} + <span className={applyStyle('outer', styles.outer)}>{e}</span> + {isUseStroke && <span className={applyStyle('inner', styles.inner)}>{e}</span>} + </span> + </span> + ); + }); + return ( + <div + style={{ + wordBreak: isSafari || props.isFirefox ? 'break-all' : undefined, + display: isSafari ? 'flex' : undefined, + flexWrap: isSafari ? 'wrap' : undefined, + }} + key={`text-line-${index}`} + > + {textLine} + </div> + ); + }); + return ( + <> + {isText && ( + <div + id="textBoxMain" + className={applyStyle('TextBox_main', styles.TextBox_main)} + style={{ + fontFamily: font, + left: miniAvatar === '' ? 25 : undefined, + background: `linear-gradient( + rgba(245, 247, 250, ${textboxOpacity / 100}) 0%, + rgba(189, 198, 222, ${textboxOpacity / 100}) 100% + )`, + }} + > + <div id="miniAvatar" className={applyStyle('miniAvatarContainer', styles.miniAvatarContainer)}> + {miniAvatar !== '' && ( + <img className={applyStyle('miniAvatarImg', styles.miniAvatarImg)} alt="miniAvatar" src={miniAvatar} /> + )} + </div> + {showName !== '' && ( + <div + key={showName} + className={applyStyle('TextBox_showName', styles.TextBox_showName)} + style={{ + fontSize: '200%', + background: `rgba(11, 52, 110, ${(textboxOpacity / 100) * 0.9})`, + border: `4px solid rgba(255, 255, 255, ${(textboxOpacity / 100) * 0.75})`, + boxShadow: `3px 3px 10px rgba(100, 100, 100, ${(textboxOpacity / 100) * 0.5})`, + }} + > + {showName.split('').map((e, i) => { + return ( + <span key={e + i} style={{ position: 'relative' }}> + <span className={styles.zhanwei}> + {e} + <span className={applyStyle('outerName', styles.outerName)}>{e}</span> + {isUseStroke && <span className={applyStyle('innerName', styles.innerName)}>{e}</span>} + </span> + </span> + ); + })} + </div> + )} + <div + className={applyStyle('text', styles.text)} + style={{ + fontSize, + flexFlow: 'column', + overflow: 'hidden', + paddingLeft: '0.1em', + }} + > + {textElementList} + </div> + </div> + )} + </> + ); +} diff --git a/packages/webgal/src/Stage/TextBox/TextBox.tsx b/packages/webgal/src/Stage/TextBox/TextBox.tsx index 06719cf3b..5fc026233 100644 --- a/packages/webgal/src/Stage/TextBox/TextBox.tsx +++ b/packages/webgal/src/Stage/TextBox/TextBox.tsx @@ -1,30 +1,17 @@ -import { FC, ReactNode, useEffect, useState } from 'react'; +import { ReactNode, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { RootState } from '@/store/store'; import { useFontFamily } from '@/hooks/useFontFamily'; import { useTextAnimationDuration, useTextDelay } from '@/hooks/useTextOptions'; import { getTextSize } from '@/UI/getTextSize'; -import StandardTextbox, { ITextboxProps } from '@/Stage/TextBox/themes/standard/StandardTextbox'; -import IMSSTextbox from '@/Stage/TextBox/themes/imss/IMSSTextbox'; -import { IWebGalTextBoxTheme } from '@/Stage/themeInterface'; import { match } from '@/Core/util/match'; import { textSize } from '@/store/userDataInterface'; +import IMSSTextbox from '@/Stage/TextBox/IMSSTextbox'; const userAgent = navigator.userAgent; const isFirefox = /firefox/i.test(userAgent); const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent); -function getTextboxByTheme(theme: IWebGalTextBoxTheme): FC<ITextboxProps> { - switch (theme) { - case 'standard': - return StandardTextbox; - case 'imss': - return IMSSTextbox; - default: - return StandardTextbox; - } -} - export const TextBox = () => { const [isShowStroke, setIsShowStroke] = useState(true); @@ -77,9 +64,8 @@ export const TextBox = () => { const currentConcatDialogPrev = stageState.currentConcatDialogPrev; const currentDialogKey = stageState.currentDialogKey; const miniAvatar = stageState.miniAvatar; - const theme = useSelector((state: RootState) => state.GUI.theme); const textboxOpacity = userDataState.optionData.textboxOpacity; - const Textbox = getTextboxByTheme(theme.textbox); + const Textbox = IMSSTextbox; return ( <Textbox textArray={textArray} @@ -106,7 +92,7 @@ function isCJK(character: string) { return !!character.match(/[\u4e00-\u9fa5]|[\u0800-\u4e00]|[\uac00-\ud7ff]/); } -export function compileSentence(sentence: string, lineLimit: number, ignoreLineLimit?: boolean): ReactNode[] { +export function compileSentence(sentence: string, lineLimit: number, ignoreLineLimit?: boolean): ReactNode[][] { // 先拆行 const lines = sentence.split('|'); // 对每一行进行注音处理 @@ -132,11 +118,7 @@ export function compileSentence(sentence: string, lineLimit: number, ignoreLineL }); return ln; }); - const ret = nodeLines - .slice(0, ignoreLineLimit ? undefined : lineLimit) - .reduce((prev, curr, currentIndex) => [...prev, ...curr, <br key={`br-${currentIndex}`} />], []); - ret.pop(); - return ret; + return nodeLines.slice(0, ignoreLineLimit ? undefined : lineLimit); } /** @@ -169,7 +151,7 @@ export function splitChars(sentence: string) { words.push(word); word = ''; } - words.push(' '); + words.push('\u00a0'); cjkFlag = false; } else if (isCJK(character) && !isPunctuation(character)) { if (!cjkFlag && word) { diff --git a/packages/webgal/src/Stage/TextBox/themes/standard/StandardTextbox.tsx b/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx similarity index 90% rename from packages/webgal/src/Stage/TextBox/themes/standard/StandardTextbox.tsx rename to packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx index 24d253d14..33ca4f88b 100644 --- a/packages/webgal/src/Stage/TextBox/themes/standard/StandardTextbox.tsx +++ b/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx @@ -2,25 +2,7 @@ import styles from './standard.module.scss'; import { textSize } from '@/store/userDataInterface'; import { ReactNode, useEffect } from 'react'; import { WebGAL } from '@/Core/WebGAL'; - -export interface ITextboxProps { - textArray: ReactNode[]; - textDelay: number; - currentConcatDialogPrev: string; - currentDialogKey: string; - isText: boolean; - isSafari: boolean; - isFirefox: boolean; - fontSize: string; - miniAvatar: string; - showName: string; - font: string; - textDuration: number; - textSizeState: number; - lineLimit: number; - isUseStroke: boolean; - textboxOpacity: number; -} +import { ITextboxProps } from '@/Stage/TextBox/types'; export default function StandardTextbox(props: ITextboxProps) { const { @@ -51,9 +33,9 @@ export default function StandardTextbox(props: ITextboxProps) { e.className = styles.TextBox_textElement_Settled; }); } - WebGAL.eventBus.on('text-settle', settleText); + WebGAL.events.textSettle.on(settleText); return () => { - WebGAL.eventBus.off('text-settle', settleText); + WebGAL.events.textSettle.off(settleText); }; }, []); diff --git a/packages/webgal/src/Stage/TextBox/themes/standard/standard.module.scss b/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/standard.module.scss similarity index 100% rename from packages/webgal/src/Stage/TextBox/themes/standard/standard.module.scss rename to packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/standard.module.scss diff --git a/packages/webgal/src/Stage/TextBox/textbox.module.scss b/packages/webgal/src/Stage/TextBox/textbox.module.scss index b7de847ef..ae2a9f23b 100644 --- a/packages/webgal/src/Stage/TextBox/textbox.module.scss +++ b/packages/webgal/src/Stage/TextBox/textbox.module.scss @@ -18,7 +18,7 @@ $height: 330px; right: 25px; min-height: $height; max-height: $height; - background-image: linear-gradient(rgba(245, 247, 250, 0.8) 0%, rgba(189, 198, 222, 0.8) 100%); + // background: linear-gradient(rgba(245, 247, 250, 0.95) 0%, rgba(189, 198, 222, 0.95) 100%); background-blend-mode: darken; //background: white; border-radius: calc($height / 2) 20px 20px calc($height / 2); @@ -33,7 +33,7 @@ $height: 330px; align-items: flex-start; animation: showSoftly 0.7s ease-out forwards; letter-spacing: 0.2em; - backdrop-filter: blur(5px); + //backdrop-filter: blur(5px); transition: left 0.33s; } @@ -41,6 +41,7 @@ $height: 330px; 0% { opacity: 0; } + 100% { opacity: 1; } @@ -65,11 +66,9 @@ $height: 330px; left: 0; top: 0; //background-image: linear-gradient(rgba(255, 255, 255, 1) 0%, rgb(225, 237, 255) 100%); - background-image: linear-gradient( - #0B346E 0%, + background-image: linear-gradient(#0B346E 0%, //#f5f7fa 45%, - #141423 100% - ); + #141423 100%); //background: rgba(255, 255, 255, 1); background-clip: text; -webkit-background-clip: text; @@ -129,16 +128,17 @@ $height: 330px; line-height: 68px; //display: flex; //align-items: center; - background: rgba(11, 52, 110, 0.9); + // background: rgba(11, 52, 110, 0.9); border-radius: 40px; - border: 4px solid rgba(255, 255, 255, 0.75); - box-shadow: 3px 3px 10px rgba(100, 100, 100, 0.5); + // border: 4px solid rgba(255, 255, 255, 0.75); + // box-shadow: 3px 3px 10px rgba(100, 100, 100, 0.5); } @keyframes TextDelayShow { 0% { opacity: 0; } + 100% { opacity: 1; } @@ -195,3 +195,8 @@ $height: 330px; z-index: 1; //text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.75); } + +.text { + line-height: 1.9em; + overflow: hidden; +} diff --git a/packages/webgal/src/Stage/TextBox/themes/imss/IMSSTextbox.tsx b/packages/webgal/src/Stage/TextBox/themes/imss/IMSSTextbox.tsx deleted file mode 100644 index e5594b18e..000000000 --- a/packages/webgal/src/Stage/TextBox/themes/imss/IMSSTextbox.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import styles from './imss.module.scss'; -import { ReactNode, useEffect } from 'react'; -import { WebGAL } from '@/Core/WebGAL'; -import { ITextboxProps } from '../standard/StandardTextbox'; - -export default function IMSSTextbox(props: ITextboxProps) { - const { - textArray, - textDelay, - currentConcatDialogPrev, - currentDialogKey, - isText, - isSafari, - isFirefox: boolean, - fontSize, - miniAvatar, - showName, - font, - textDuration, - isUseStroke, - textboxOpacity, - } = props; - - useEffect(() => { - function settleText() { - const textElements = document.querySelectorAll('.Textelement_start'); - const textArray = [...textElements]; - textArray.forEach((e) => { - e.className = styles.TextBox_textElement_Settled; - }); - } - WebGAL.eventBus.on('text-settle', settleText); - return () => { - WebGAL.eventBus.off('text-settle', settleText); - }; - }, []); - - const textElementList = textArray.map((e, index) => { - // if (e === '<br />') { - // return <br key={`br${index}`} />; - // } - let delay = index * textDelay; - let prevLength = currentConcatDialogPrev.length; - if (currentConcatDialogPrev !== '' && index >= prevLength) { - delay = delay - prevLength * textDelay; - } - if (index < prevLength) { - return ( - <span - // data-text={e} - id={`${delay}`} - className={styles.TextBox_textElement_Settled} - key={currentDialogKey + index} - style={{ animationDelay: `${delay}ms`, animationDuration: `${textDuration}ms` }} - > - <span className={styles.zhanwei}> - {e} - <span className={styles.outer}>{e}</span> - {isUseStroke && <span className={styles.inner}>{e}</span>} - </span> - </span> - ); - } - return ( - <span - data-text={e} - id={`${delay}`} - className={`${styles.TextBox_textElement_start} Textelement_start`} - key={currentDialogKey + index} - style={{ animationDelay: `${delay}ms`, position: 'relative' }} - > - <span className={styles.zhanwei}> - {e} - <span className={styles.outer}>{e}</span> - {isUseStroke && <span className={styles.inner}>{e}</span>} - </span> - </span> - ); - }); - return ( - <> - {isText && ( - <div - id="textBoxMain" - className={styles.TextBox_main} - style={{ - fontFamily: font, - left: miniAvatar === '' ? 25 : undefined, - background: `linear-gradient( - rgba(245, 247, 250, ${textboxOpacity / 100}) 0%, - rgba(189, 198, 222, ${textboxOpacity / 100}) 100% - )`, - }} - > - {/* <div className={styles.nameContainer}>{stageState.showName !== ''}</div> */} - <div id="miniAvatar" className={styles.miniAvatarContainer}> - {miniAvatar !== '' && <img className={styles.miniAvatarImg} alt="miniAvatar" src={miniAvatar} />} - </div> - {showName !== '' && ( - <div - key={showName} - className={styles.TextBox_showName} - style={{ - fontSize: '200%', - background: `rgba(11, 52, 110, ${(textboxOpacity / 100) * 0.9})`, - border: `4px solid rgba(255, 255, 255, ${(textboxOpacity / 100) * 0.75})`, - boxShadow: `3px 3px 10px rgba(100, 100, 100, ${(textboxOpacity / 100) * 0.5})`, - }} - > - {showName.split('').map((e, i) => { - return ( - <span key={e + i} style={{ position: 'relative' }}> - <span className={styles.zhanwei}> - {e} - <span className={styles.outerName}>{e}</span> - {isUseStroke && <span className={styles.innerName}>{e}</span>} - </span> - </span> - ); - })} - </div> - )} - <div - className={styles.text} - style={{ - fontSize, - wordBreak: isSafari || props.isFirefox ? 'break-all' : undefined, - display: isSafari ? 'flex' : undefined, - flexWrap: isSafari ? 'wrap' : undefined, - overflow: 'hidden', - paddingLeft: '0.1em', - WebkitLineClamp: props.lineLimit, - }} - > - {textElementList} - </div> - </div> - )} - </> - ); -} diff --git a/packages/webgal/src/Stage/TextBox/themes/imss/imss.module.scss b/packages/webgal/src/Stage/TextBox/themes/imss/imss.module.scss deleted file mode 100644 index 512c70e1c..000000000 --- a/packages/webgal/src/Stage/TextBox/themes/imss/imss.module.scss +++ /dev/null @@ -1,205 +0,0 @@ -.TextBox_EventHandler { - position: absolute; - width: 100%; - height: 100%; - z-index: 6; - top: 0; -} - -@mixin text_shadow_textElement { - //text-shadow: 0 0 3px rgba(81,168,221,1); -} - -$height: 330px; - -.TextBox_main { - position: absolute; - z-index: 6; - right: 25px; - min-height: $height; - max-height: $height; - // background: linear-gradient(rgba(245, 247, 250, 0.95) 0%, rgba(189, 198, 222, 0.95) 100%); - background-blend-mode: darken; - //background: white; - border-radius: calc($height / 2) 20px 20px calc($height / 2); - bottom: 20px; - left: 275px; - font-weight: bold; - color: white; - padding: 1em 50px 70px 200px; - box-sizing: border-box; - display: flex; - flex-flow: column; - align-items: flex-start; - animation: showSoftly 0.7s ease-out forwards; - letter-spacing: 0.2em; - //backdrop-filter: blur(5px); - transition: left 0.33s; -} - -@keyframes showSoftly { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} - -//.TextBox_textElement { -// opacity: 0; -// animation: showSoftly 1000ms forwards; -//} - - -.TextBox_textElement_start { - @include text_shadow_textElement; - position: relative; - animation: TextDelayShow 1000ms ease-out forwards; - opacity: 0; -} - -.outer { - position: absolute; - white-space: nowrap; - left: 0; - top: 0; - //background-image: linear-gradient(rgba(255, 255, 255, 1) 0%, rgb(225, 237, 255) 100%); - background-image: linear-gradient(#0B346E 0%, - //#f5f7fa 45%, - #141423 100%); - //background: rgba(255, 255, 255, 1); - background-clip: text; - -webkit-background-clip: text; - color: transparent; - z-index: 2; -} - -.inner { - white-space: nowrap; - position: absolute; - left: 0; - top: 0; - -webkit-text-stroke: 0.1em rgba(255, 255, 255, 1); - z-index: 1; - //text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.75); -} - -.zhanwei { - color: transparent; - white-space: nowrap; -} - -// -//.TextBox_textElement_start::before{ -// animation: TextDelayShow 700ms ease-out forwards; -// opacity: 0; -// content:attr(data-text); -// position: absolute; -//} - -.TextBox_textElement_Settled { - position: relative; - @include text_shadow_textElement; - opacity: 1; -} - -// -//.TextBox_textElement_Settled::before{ -// content:attr(data-text); -// position: absolute; -// opacity: 1; -// -webkit-text-stroke: 1px red; -// z-index: 1; -//} - -.TextBox_showName { - @include text_shadow_textElement; - font-size: 85%; - //border-bottom: 3px solid rgb(176, 176, 176); - //min-width: 25%; - padding: 0 2em 0 2em; - //margin: 0 0 0 0; - position: absolute; - left: 150px; - top: -68px; - height: 80px; - line-height: 68px; - //display: flex; - //align-items: center; - // background: rgba(11, 52, 110, 0.9); - border-radius: 40px; - // border: 4px solid rgba(255, 255, 255, 0.75); - // box-shadow: 3px 3px 10px rgba(100, 100, 100, 0.5); -} - -@keyframes TextDelayShow { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} - -.miniAvatarContainer { - position: absolute; - height: 450px; - width: 450px; - bottom: 0; - left: -250px; - border-radius: 100% 0 0 100%; - overflow: hidden; -} - -.miniAvatarImg { - max-height: 100%; - max-width: 100%; - position: absolute; - bottom: 0; - filter: drop-shadow(15px 0 3px rgba(0, 0, 0, 0.5)); -} - -.nameContainer { - position: absolute; - left: 2em; - top: -3.5em; -} - -.outerName { - position: absolute; - left: 0; - top: 0; - //background-image: linear-gradient(rgba(255, 255, 255, 1) 0%, rgb(225, 237, 255) 100%); - //background-image: linear-gradient( - // #bfd8ff 0%, - // //#f5f7fa 45%, - // #bfbfc7 100% - //); - background: linear-gradient(150deg, rgb(255, 255, 255) 0%, rgb(255, 255, 255) 35%, rgb(165, 212, 228) 100%); - //background: rgba(255, 255, 255, 1); - background-clip: text; - -webkit-background-clip: text; - color: transparent; - z-index: 2; -} - -.innerName { - position: absolute; - left: 0; - top: 0; - //-webkit-text-stroke: 0.1em rgba(0, 0, 0, 0.25); - //-webkit-text-stroke: 0.1em rgba(255, 255, 255, 1); - z-index: 1; - //text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.75); -} - -.text { - line-height: 1.9em; - display: -webkit-box; - -webkit-box-orient: vertical; - //-webkit-line-clamp: 2; - overflow: hidden; -} \ No newline at end of file diff --git a/packages/webgal/src/Stage/TextBox/types.ts b/packages/webgal/src/Stage/TextBox/types.ts new file mode 100644 index 000000000..2a7b8c06d --- /dev/null +++ b/packages/webgal/src/Stage/TextBox/types.ts @@ -0,0 +1,20 @@ +import { ReactNode } from 'react'; + +export interface ITextboxProps { + textArray: ReactNode[][]; + textDelay: number; + currentConcatDialogPrev: string; + currentDialogKey: string; + isText: boolean; + isSafari: boolean; + isFirefox: boolean; + fontSize: string; + miniAvatar: string; + showName: string; + font: string; + textDuration: number; + textSizeState: number; + lineLimit: number; + isUseStroke: boolean; + textboxOpacity: number; +} diff --git a/packages/webgal/src/UI/Backlog/Backlog.tsx b/packages/webgal/src/UI/Backlog/Backlog.tsx index 48fd89d6f..5b19a2460 100644 --- a/packages/webgal/src/UI/Backlog/Backlog.tsx +++ b/packages/webgal/src/UI/Backlog/Backlog.tsx @@ -29,12 +29,18 @@ export const Backlog = () => { const backlogItem = WebGAL.backlogManager.getBacklog()[i]; const showTextArray = compileSentence(backlogItem.currentStageState.showText, 3, true); const showTextArrayReduced = mergeStringsAndKeepObjects(showTextArray); - const showTextElementList = showTextArrayReduced.map((e, index) => { - if (e === '<br />') { - return <br key={`br${index}`} />; - } else { - return e; - } + const showTextElementList = showTextArrayReduced.map((line, index) => { + return ( + <div key={`backlog-line-${index}`}> + {line.map((e, index) => { + if (e === '<br />') { + return <br key={`br${index}`} />; + } else { + return e; + } + })} + </div> + ); }); const singleBacklogView = ( <div @@ -166,7 +172,7 @@ export const Backlog = () => { ); }; -function mergeStringsAndKeepObjects(arr: ReactNode[]) { +function mergeStringsAndKeepObjects(arr: ReactNode[]): ReactNode[][] { let result = []; let currentString = ''; @@ -189,5 +195,5 @@ function mergeStringsAndKeepObjects(arr: ReactNode[]) { result.push(currentString); } - return result; + return result as ReactNode[][]; } diff --git a/packages/webgal/src/UI/Extra/ExtraCg.tsx b/packages/webgal/src/UI/Extra/ExtraCg.tsx index bf14247d3..3f1ebac5e 100644 --- a/packages/webgal/src/UI/Extra/ExtraCg.tsx +++ b/packages/webgal/src/UI/Extra/ExtraCg.tsx @@ -11,6 +11,7 @@ export function ExtraCg() { const cgPerPage = 8; const extraState = useSelector((state: RootState) => state.userData.appreciationData); const pageNumber = Math.ceil(extraState.cg.length / cgPerPage); + // const pageNumber = 10; const currentPage = useValue(1); const { playSeEnter, playSeClick } = useSoundEffect(); diff --git a/packages/webgal/src/UI/Extra/extra.module.scss b/packages/webgal/src/UI/Extra/extra.module.scss index 764083b62..c50c8c7eb 100644 --- a/packages/webgal/src/UI/Extra/extra.module.scss +++ b/packages/webgal/src/UI/Extra/extra.module.scss @@ -203,20 +203,21 @@ flex-flow: row; justify-content: center; align-items: flex-end; - background: rgba(0, 0, 0, 0.1); - border-radius: 4px; + //background: rgba(255, 255, 255, 0.35); + border-radius: 7px; + padding: 12px 15px; } .cgNav { font-size: 170%; //background-color: rgba(0, 0, 0, 0.2); color: white; - padding: 0.25em 1em 0.25em 1em; + padding: 0.12em 1em 0.12em 1em; margin: 0 0.25em 0 0.25em; //width: 20px; text-align: center; - transition: background-color 1s, color 1s; cursor: pointer; + transition: background-color 0.5s, color 0.5s, font-weight 0.5s; border-radius: 7px; } @@ -229,14 +230,13 @@ } .cgNav_active { - background-color: rgba(255, 255, 255, 0.85) !important; - color: #666; + background-color: rgba(0, 92, 175, 0.1) !important; + color: #005CAF; + font-weight: bold; } .cgNav:hover { - background-color: rgba(255, 255, 255, 0.65); - color: #666; - transition: background-color 0.5s, color 0.5s; + background-color: rgba(0, 92, 175, 0.05); } diff --git a/packages/webgal/src/UI/Menu/MenuPanel/MenuIconMap.tsx b/packages/webgal/src/UI/Menu/MenuPanel/MenuIconMap.tsx index 4287563bb..460c2d2f2 100644 --- a/packages/webgal/src/UI/Menu/MenuPanel/MenuIconMap.tsx +++ b/packages/webgal/src/UI/Menu/MenuPanel/MenuIconMap.tsx @@ -19,10 +19,10 @@ export const MenuIconMap = (props: IMenuPanel) => { returnIcon = <SettingTwo theme="outline" size="1.2em" fill={props.iconColor} strokeWidth={2} />; break; case 'title': - returnIcon = <Home theme="outline" size="1.2em" fill="rgba(185,185,185,1)" strokeWidth={2} />; + returnIcon = <Home theme="outline" size="1.2em" fill={props.iconColor} strokeWidth={2} />; break; case 'exit': - returnIcon = <Logout theme="outline" size="1.2em" fill="rgba(185,185,185,1)" strokeWidth={2} />; + returnIcon = <Logout theme="outline" size="1.2em" fill={props.iconColor} strokeWidth={2} />; break; default: returnIcon = <div />; diff --git a/packages/webgal/src/UI/Menu/MenuPanel/MenuPanel.tsx b/packages/webgal/src/UI/Menu/MenuPanel/MenuPanel.tsx index 37a9fd393..85e1100d6 100644 --- a/packages/webgal/src/UI/Menu/MenuPanel/MenuPanel.tsx +++ b/packages/webgal/src/UI/Menu/MenuPanel/MenuPanel.tsx @@ -8,6 +8,7 @@ import { setMenuPanelTag, setVisibility } from '@/store/GUIReducer'; import { backToTitle } from '@/Core/controller/gamePlay/backToTitle'; import useTrans from '@/hooks/useTrans'; import useSoundEffect from '@/hooks/useSoundEffect'; +import { showGlogalDialog } from '@/UI/GlobalDialog/GlobalDialog'; /** * Menu页的底栏 @@ -17,7 +18,7 @@ export const MenuPanel = () => { // 国际化 const t = useTrans('menu.'); - const { playSeClick, playSeEnter } = useSoundEffect(); + const { playSeClick, playSeDialogOpen, playSePageChange } = useSoundEffect(); const GUIState = useSelector((state: RootState) => state.GUI); const dispatch = useDispatch(); // 设置Menu按钮的高亮 @@ -26,18 +27,17 @@ export const MenuPanel = () => { const OptionTagOn = GUIState.currentMenuTag === MenuPanelTag.Option ? ` ${styles.MenuPanel_button_hl}` : ``; // 设置Menu按钮的颜色 - const SaveTagColor = GUIState.currentMenuTag === MenuPanelTag.Save ? `rgba(74, 34, 93, 0.9)` : `rgba(8, 8, 8, 0.3)`; - const LoadTagColor = GUIState.currentMenuTag === MenuPanelTag.Load ? `rgba(11, 52, 110, 0.9)` : `rgba(8, 8, 8, 0.3)`; + const SaveTagColor = GUIState.currentMenuTag === MenuPanelTag.Save ? `rgba(74, 34, 93, 0.9)` : `rgba(123,144,169,1)`; + const LoadTagColor = GUIState.currentMenuTag === MenuPanelTag.Load ? `rgba(11, 52, 110, 0.9)` : `rgba(123,144,169,1)`; const OptionTagColor = - GUIState.currentMenuTag === MenuPanelTag.Option ? `rgba(81, 110, 65, 0.9)` : `rgba(8, 8, 8, 0.3)`; + GUIState.currentMenuTag === MenuPanelTag.Option ? `rgba(81, 110, 65, 0.9)` : `rgba(123,144,169,1)`; // 设置Menu图标的颜色 - const SaveIconColor = - GUIState.currentMenuTag === MenuPanelTag.Save ? `rgba(74, 34, 93, 0.9)` : `rgba(185, 185, 185, 1)`; + const SaveIconColor = GUIState.currentMenuTag === MenuPanelTag.Save ? `rgba(74, 34, 93, 0.9)` : `rgba(123,144,169,1)`; const LoadIconColor = - GUIState.currentMenuTag === MenuPanelTag.Load ? `rgba(11, 52, 110, 0.9)` : `rgba(185, 185, 185, 1)`; + GUIState.currentMenuTag === MenuPanelTag.Load ? `rgba(11, 52, 110, 0.9)` : `rgba(123,144,169,1)`; const OptionIconColor = - GUIState.currentMenuTag === MenuPanelTag.Option ? `rgba(81, 110, 65, 0.9)` : `rgba(185, 185, 185, 1)`; + GUIState.currentMenuTag === MenuPanelTag.Option ? `rgba(81, 110, 65, 0.9)` : `rgba(123,144,169,1)`; return ( <div className={styles.MenuPanel_main}> @@ -47,7 +47,7 @@ export const MenuPanel = () => { iconColor={SaveIconColor} tagColor={SaveTagColor} clickFunc={() => { - playSeClick(); + playSePageChange(); if (GUIState.showTitle) return; dispatch(setMenuPanelTag(MenuPanelTag.Save)); }} @@ -60,36 +60,50 @@ export const MenuPanel = () => { iconColor={LoadIconColor} tagColor={LoadTagColor} clickFunc={() => { - playSeClick(); + playSePageChange(); dispatch(setMenuPanelTag(MenuPanelTag.Load)); }} tagName={t('loadSaving.title')} key="loadButton" /> + <MenuPanelButton + iconName="title" + iconColor="rgba(123,144,169,1)" + tagColor="rgba(123,144,169,1)" + clickFunc={() => { + playSeDialogOpen(); + showGlogalDialog({ + title: t('$gaming.buttons.titleTips'), + leftText: t('$common.yes'), + rightText: t('$common.no'), + leftFunc: () => { + backToTitle(); + dispatch(setVisibility({ component: 'showMenuPanel', visibility: false })); + }, + rightFunc: () => {}, + }); + }} + tagName={t('title.title')} + key="titleIcon" + /> <MenuPanelButton iconName="option" + style={{ marginLeft: 'auto' }} buttonOnClassName={OptionTagOn} iconColor={OptionIconColor} tagColor={OptionTagColor} clickFunc={() => { - playSeClick(); + playSePageChange(); dispatch(setMenuPanelTag(MenuPanelTag.Option)); }} tagName={t('options.title')} key="optionButton" /> - <MenuPanelButton - iconName="title" - clickFunc={() => { - playSeClick(); - backToTitle(); - dispatch(setVisibility({ component: 'showMenuPanel', visibility: false })); - }} - tagName={t('title.title')} - key="titleIcon" - /> + <MenuPanelButton iconName="exit" + iconColor="rgba(123,144,169,1)" + tagColor="rgba(123,144,169,1)" clickFunc={() => { playSeClick(); dispatch(setVisibility({ component: 'showMenuPanel', visibility: false })); diff --git a/packages/webgal/src/UI/Menu/MenuPanel/MenuPanelButton.tsx b/packages/webgal/src/UI/Menu/MenuPanel/MenuPanelButton.tsx index f590fef4b..a8d47c11c 100644 --- a/packages/webgal/src/UI/Menu/MenuPanel/MenuPanelButton.tsx +++ b/packages/webgal/src/UI/Menu/MenuPanel/MenuPanelButton.tsx @@ -19,10 +19,10 @@ export const MenuPanelButton = (props: IMenuPanel) => { className={buttonClassName} onClick={() => { props.clickFunc(); - playSePageChange(); + // playSePageChange(); }} onMouseEnter={playSeEnter} - style={{ color: props.tagColor }} + style={{ ...props.style, color: props.tagColor }} > <div className={styles.MenuPanel_button_icon}> <MenuIconMap iconName={props.iconName} iconColor={props.iconColor} /> diff --git a/packages/webgal/src/UI/Menu/MenuPanel/menuPanel.module.scss b/packages/webgal/src/UI/Menu/MenuPanel/menuPanel.module.scss index e828c4cdb..fc879cbc0 100644 --- a/packages/webgal/src/UI/Menu/MenuPanel/menuPanel.module.scss +++ b/packages/webgal/src/UI/Menu/MenuPanel/menuPanel.module.scss @@ -4,28 +4,36 @@ display: flex; justify-content: center; align-items: center; - background-color: rgba(255, 255, 255, 1); + //background-color: rgba(255, 255, 255, 1); //box-shadow: 0 0 45px 15px rgba(0, 0, 0, 0.05); + padding: 0 55px; } .MenuPanel_button { - padding: 0.35em 0 0 0; + padding: 0.25em 15px 0 15px; + margin-right: 15px; display: flex; justify-content: center; font-size: 200%; text-align: center; - font-weight: bold; - width: 20%; + //font-weight: bold; + border-radius: 6px; + //width: 20%; + min-width: 12.5%; cursor: pointer; - color: rgba(8, 8, 8, 0.3); + color: rgba(123,144,169, 1); background: rgba(0, 0, 0, 0); overflow: hidden; - border-right: 1.5px solid rgba(0, 0, 0, 0.15); - transition: text-shadow 0.7s, backgroud 0.7s, all 0.33s; + //border-right: 1.5px solid rgba(0, 0, 0, 0.15); + transition: text-shadow 0.7s, background-color 0.7s; +} + +.MenuPanel_button:last-child{ + margin-right: 0; } .MenuPanel_button:hover { - background: rgba(0, 0, 0, 0.05); + background-color: rgba(245, 246, 247, 0.15); } .MenuPanel_button:last-child { @@ -37,3 +45,7 @@ padding: 0 0.15em 0 0; margin: 0 0.15em 0 0; } + +.MenuPanel_button_hl{ + background-color: rgba(245, 246, 247, 0.35) !important; +} diff --git a/packages/webgal/src/UI/Menu/MenuPanel/menuPanelInterface.ts b/packages/webgal/src/UI/Menu/MenuPanel/menuPanelInterface.ts index abc2104df..9a38371cb 100644 --- a/packages/webgal/src/UI/Menu/MenuPanel/menuPanelInterface.ts +++ b/packages/webgal/src/UI/Menu/MenuPanel/menuPanelInterface.ts @@ -1,3 +1,5 @@ +import { CSSProperties } from 'react'; + /** * @interface IMenuPanel Menu页面的按钮的参数接口 */ @@ -8,4 +10,5 @@ export interface IMenuPanel { iconColor?: string; // 图标颜色 tagName?: string; // 标签显示名称 iconName: string; // 图标名称 + style?: CSSProperties; } diff --git a/packages/webgal/src/UI/Menu/Options/Display/Display.tsx b/packages/webgal/src/UI/Menu/Options/Display/Display.tsx index 7f9bb0224..b7bc13045 100644 --- a/packages/webgal/src/UI/Menu/Options/Display/Display.tsx +++ b/packages/webgal/src/UI/Menu/Options/Display/Display.tsx @@ -4,7 +4,7 @@ import { RootState } from '@/store/store'; import { NormalOption } from '@/UI/Menu/Options/NormalOption'; import { NormalButton } from '@/UI/Menu/Options/NormalButton'; import { setOptionData } from '@/store/userDataReducer'; -import { playSpeed, textFont, textSize } from '@/store/userDataInterface'; +import { fullScreenOption, playSpeed, textFont, textSize } from '@/store/userDataInterface'; import { setStorage } from '@/Core/controller/storage/storageController'; import { TextPreview } from '@/UI/Menu/Options/TextPreview/TextPreview'; import useTrans from '@/hooks/useTrans'; @@ -17,6 +17,22 @@ export function Display() { return ( <div className={styles.Options_main_content_half}> + <NormalOption key="fullScreen" title={t('fullScreen.title')}> + <NormalButton + textList={t('fullScreen.options.on', 'fullScreen.options.off')} + functionList={[ + () => { + dispatch(setOptionData({ key: 'fullScreen', value: fullScreenOption.on })); + setStorage(); + }, + () => { + dispatch(setOptionData({ key: 'fullScreen', value: fullScreenOption.off })); + setStorage(); + }, + ]} + currentChecked={userDataState.optionData.fullScreen} + /> + </NormalOption> <NormalOption key="textSpeed" title={t('textSpeed.title')}> <NormalButton textList={t('textSpeed.options.slow', 'textSpeed.options.medium', 'textSpeed.options.fast')} diff --git a/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx b/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx index 8b18048cf..d6d9e0459 100644 --- a/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx +++ b/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx @@ -5,14 +5,11 @@ import { useFontFamily } from '@/hooks/useFontFamily'; import { useTextAnimationDuration, useTextDelay } from '@/hooks/useTextOptions'; import useTrans from '@/hooks/useTrans'; import { getTextSize } from '@/UI/getTextSize'; -import StandardTextbox from '@/Stage/TextBox/themes/standard/StandardTextbox'; -import IMSSTextbox from '@/Stage/TextBox/themes/imss/IMSSTextbox'; -import { ReactNode } from 'react'; +import IMSSTextbox from '@/Stage/TextBox/IMSSTextbox'; import { compileSentence } from '@/Stage/TextBox/TextBox'; export const TextPreview = (props: any) => { const t = useTrans('menu.options.pages.display.options.'); - const theme = useSelector((state: RootState) => state.GUI.theme); const userDataState = useSelector((state: RootState) => state.userData); const stageState = useSelector((state: RootState) => state.stage); const previewBackground = stageState.bgName; @@ -27,12 +24,7 @@ export const TextPreview = (props: any) => { const previewText = t('textPreview.text'); const previewTextArray = compileSentence(previewText, 3); - const textboxs = new Map([ - ['standard', StandardTextbox], - ['imss', IMSSTextbox], - ]); - - const Textbox = textboxs.get(theme.textbox) || StandardTextbox; + const Textbox = IMSSTextbox; const textboxProps = { textArray: previewTextArray, diff --git a/packages/webgal/src/UI/Menu/Options/options.module.scss b/packages/webgal/src/UI/Menu/Options/options.module.scss index 3cfa5811a..bc3a7622e 100644 --- a/packages/webgal/src/UI/Menu/Options/options.module.scss +++ b/packages/webgal/src/UI/Menu/Options/options.module.scss @@ -3,7 +3,7 @@ cursor: default; height: 90%; width: 100%; - background: rgba(255, 255, 255, 0.65); + //background: rgba(255, 255, 255, 0.65); } .Options_top { @@ -133,4 +133,4 @@ .Options_page_button:hover { opacity: 1; -} \ No newline at end of file +} diff --git a/packages/webgal/src/UI/Menu/SaveAndLoad/Load/Load.tsx b/packages/webgal/src/UI/Menu/SaveAndLoad/Load/Load.tsx index 564afd3a4..22342417d 100644 --- a/packages/webgal/src/UI/Menu/SaveAndLoad/Load/Load.tsx +++ b/packages/webgal/src/UI/Menu/SaveAndLoad/Load/Load.tsx @@ -37,16 +37,6 @@ export const Load: FC = () => { page.push(element); } - const { i18n } = useTranslation(); - const lang = i18n.language; - const isFr = lang === 'fr'; - const frStyl: CSSProperties = { - fontSize: '150%', - padding: '0 0.2em 0 0.2em', - margin: '0 0 0 0.8em', - letterSpacing: '0.05em', - }; - const showSaves = []; // 现在尝试设置10个存档每页 const start = (userDataState.optionData.slPage - 1) * 10 + 1; @@ -103,7 +93,7 @@ export const Load: FC = () => { return ( <div className={styles.Save_Load_main}> <div className={styles.Save_Load_top}> - <div className={styles.Save_Load_title} style={isFr ? frStyl : undefined}> + <div className={styles.Save_Load_title}> <div className={styles.Load_title_text}>{t('loadSaving.title')}</div> </div> <div className={styles.Save_Load_top_buttonList}>{page}</div> diff --git a/packages/webgal/src/UI/Menu/SaveAndLoad/Save/Save.tsx b/packages/webgal/src/UI/Menu/SaveAndLoad/Save/Save.tsx index 796c1fd55..3d11dfa21 100644 --- a/packages/webgal/src/UI/Menu/SaveAndLoad/Save/Save.tsx +++ b/packages/webgal/src/UI/Menu/SaveAndLoad/Save/Save.tsx @@ -37,16 +37,6 @@ export const Save: FC = () => { page.push(element); } - const { i18n } = useTranslation(); - const lang = i18n.language; - const isFr = lang === 'fr'; - const frStyl: CSSProperties = { - fontSize: '150%', - padding: '0 0.2em 0 0.2em', - margin: '0 0 0 0.8em', - letterSpacing: '0.05em', - }; - const tCommon = useTrans('common.'); const showSaves = []; @@ -115,7 +105,7 @@ export const Save: FC = () => { return ( <div className={styles.Save_Load_main}> <div className={styles.Save_Load_top}> - <div className={styles.Save_Load_title} style={isFr ? frStyl : undefined}> + <div className={styles.Save_Load_title}> <div className={styles.Save_title_text}>{t('saving.title')}</div> </div> <div className={styles.Save_Load_top_buttonList}>{page}</div> diff --git a/packages/webgal/src/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss b/packages/webgal/src/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss index 7fb999ff4..a0f623ed2 100644 --- a/packages/webgal/src/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss +++ b/packages/webgal/src/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss @@ -9,26 +9,32 @@ height: 10%; width: 100%; display: flex; - background-color: rgba(255, 255, 255, 1); + //background-color: rgba(255, 255, 255, 1); //box-shadow: 0 0 1.5em 0.1em rgba(0, 0, 0, 0.05); animation: Elements_in ease-out 1s forwards; //border-bottom: 1px solid rgba(0, 0, 0, 0.1); + justify-content: center; } .Save_Load_title { font-family: "思源宋体", serif; letter-spacing: 0.1em; width: auto; - height: 100%; - font-size: 275%; + font-size: 500%; min-width: 350px; - margin: 0 0 0 0.8em; - padding: 0 0.8em 0 0.8em; + //margin: 0 0 0 0.8em; + //padding: 0 0.8em 0 0.8em; box-sizing: border-box; //border-bottom: 4px solid #77428D; display: flex; justify-content: center; align-items: center; + position: absolute; + left: 20px; + top:0; + z-index: -1; + opacity: 0.2; + transform: translateY(-10px); } .Save_title_text { @@ -51,7 +57,7 @@ .Save_Load_top_buttonList { height: 100%; display: flex; - padding: 0 0 0 2em; + //padding: 0 0 0 2em; } .Save_Load_top_button { diff --git a/packages/webgal/src/UI/Title/Title.tsx b/packages/webgal/src/UI/Title/Title.tsx index c836760b7..1026e4379 100644 --- a/packages/webgal/src/UI/Title/Title.tsx +++ b/packages/webgal/src/UI/Title/Title.tsx @@ -14,53 +14,62 @@ import useTrans from '@/hooks/useTrans'; import { hasFastSaveRecord, loadFastSaveGame } from '@/Core/controller/storage/fastSaveLoad'; import useSoundEffect from '@/hooks/useSoundEffect'; import { WebGAL } from '@/Core/WebGAL'; +import useApplyStyle from '@/hooks/useApplyStyle'; +import { fullScreenOption } from '@/store/userDataInterface'; +import { keyboard } from '@/hooks/useHotkey'; /** * 标题页 * @constructor */ const Title: FC = () => { + const userDataState = useSelector((state: RootState) => state.userData); const GUIState = useSelector((state: RootState) => state.GUI); const dispatch = useDispatch(); + const fullScreen = userDataState.optionData.fullScreen; const background = GUIState.titleBg; const showBackground = background === '' ? 'rgba(0,0,0,1)' : `url("${background}")`; const t = useTrans('title.'); const { playSeEnter, playSeClick } = useSoundEffect(); + const applyStyle = useApplyStyle('UI/Title/title.scss'); + return ( <> - {GUIState.showTitle && <div className={styles.Title_backup_background} />} + {GUIState.showTitle && <div className={applyStyle('Title_backup_background', styles.Title_backup_background)} />} <div id="enter_game_target" onClick={() => { playBgm(GUIState.titleBgm); dispatch(setVisibility({ component: 'isEnterGame', visibility: true })); - // setTimeout(resize, 2000); + if (fullScreen === fullScreenOption.on) { + document.documentElement.requestFullscreen(); + if (keyboard) keyboard.lock(['Escape', 'F11']); + } }} onMouseEnter={playSeEnter} /> {GUIState.showTitle && ( <div - className={styles.Title_main} + className={applyStyle('Title_main', styles.Title_main)} style={{ backgroundImage: showBackground, backgroundSize: 'cover', }} > - <div className={styles.Title_buttonList}> + <div className={applyStyle('Title_buttonList', styles.Title_buttonList)}> <div - className={styles.Title_button} + className={applyStyle('Title_button', styles.Title_button)} onClick={() => { startGame(); playSeClick(); }} onMouseEnter={playSeEnter} > - <div className={styles.Title_button_text + ' ' + styles.Title_button_text_up}>{t('start.title')}</div> - {/* <div className={styles.Title_button_text}>{t('start.subtitle')}</div> */} + <div className={applyStyle('Title_button_text', styles.Title_button_text)}>{t('start.title')}</div> </div> <div - className={styles.Title_button} + className={applyStyle('Title_button', styles.Title_button)} onClick={async () => { playSeClick(); dispatch(setVisibility({ component: 'showTitle', visibility: false })); @@ -68,11 +77,10 @@ const Title: FC = () => { }} onMouseEnter={playSeEnter} > - <div className={styles.Title_button_text + ' ' + styles.Title_button_text_up}>{t('continue.title')}</div> - {/* <div className={styles.Title_button_text}>{t('continue.subtitle')}</div> */} + <div className={applyStyle('Title_button_text', styles.Title_button_text)}>{t('continue.title')}</div> </div> <div - className={styles.Title_button} + className={applyStyle('Title_button', styles.Title_button)} onClick={() => { playSeClick(); dispatch(setVisibility({ component: 'showMenuPanel', visibility: true })); @@ -80,11 +88,10 @@ const Title: FC = () => { }} onMouseEnter={playSeEnter} > - <div className={styles.Title_button_text + ' ' + styles.Title_button_text_up}>{t('options.title')}</div> - {/* <div className={styles.Title_button_text}>{t('options.subtitle')}</div> */} + <div className={applyStyle('Title_button_text', styles.Title_button_text)}>{t('options.title')}</div> </div> <div - className={styles.Title_button} + className={applyStyle('Title_button', styles.Title_button)} onClick={() => { playSeClick(); dispatch(setVisibility({ component: 'showMenuPanel', visibility: true })); @@ -92,30 +99,17 @@ const Title: FC = () => { }} onMouseEnter={playSeEnter} > - <div className={styles.Title_button_text + ' ' + styles.Title_button_text_up}>{t('load.title')}</div> - {/* <div className={styles.Title_button_text}>{t('load.subtitle')}</div> */} + <div className={applyStyle('Title_button_text', styles.Title_button_text)}>{t('load.title')}</div> </div> - {/* <div */} - {/* className={styles.Title_button} */} - {/* onClick={() => { */} - {/* window.opener = null; */} - {/* window.open('', '_self'); */} - {/* window.close(); */} - {/* }} */} - {/* > */} - {/* <div className={styles.Title_button_text + ' ' + styles.Title_button_text_up}>退出游戏</div> */} - {/* <div className={styles.Title_button_text}>EXIT</div> */} - {/* </div> */} <div - className={styles.Title_button} + className={applyStyle('Title_button', styles.Title_button)} onClick={() => { playSeClick(); dispatch(setVisibility({ component: 'showExtra', visibility: true })); }} onMouseEnter={playSeEnter} > - <div className={styles.Title_button_text + ' ' + styles.Title_button_text_up}>{t('extra.title')}</div> - {/* <div className={styles.Title_button_text}>{t('extra.subtitle')}</div> */} + <div className={applyStyle('Title_button_text', styles.Title_button_text)}>{t('extra.title')}</div> </div> </div> </div> diff --git a/packages/webgal/src/UI/Title/title.module.scss b/packages/webgal/src/UI/Title/title.module.scss index aa8ba0014..c10fe2a60 100644 --- a/packages/webgal/src/UI/Title/title.module.scss +++ b/packages/webgal/src/UI/Title/title.module.scss @@ -6,11 +6,9 @@ } .Title_buttonList { - font-family: "思源宋体", serif; display: flex; position: absolute; left: 0; - //background: linear-gradient(to right, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0)); min-width: 25%; height: 100%; justify-content: center; @@ -20,20 +18,14 @@ padding-left: 120px; } -.Title_buttonList:hover { - //background: rgba(0, 0, 0, 0.6); -} - .Title_button { font-weight: bold; text-align: center; flex: 0 1 auto; cursor: pointer; - //margin: 0 0.5em 0 0.5em; padding: 1em 2em 1em 2em; margin: 20px 0; transition: all 0.33s; - //text-shadow: 0 0 10px rgba(0, 0, 0, 0.75); background: rgba(255, 255, 255, 0.15); backdrop-filter: blur(5px); border-radius: 4px; @@ -42,30 +34,17 @@ } .Title_button:hover { - //background-color: rgba(255, 255, 255, 0.1); text-shadow: 0 0 10px rgba(255, 255, 255, 1); - //transform: scale(1.025, 1.025) translate(0, -0.05em); padding: 1em 6em 1em 3em; - //box-shadow: 10px 10px 45px rgba(255, 255, 255, 0.25); } - .Title_button_text { font-size: 165%; - color: transparent; - background: linear-gradient(135deg, #fdfbfb 0%, #dcddde 100%); - -webkit-background-clip: text; + color: #fbfbfb; padding: 0 0.5em 0 0.5em; letter-spacing: 0.2em; } -.Title_button_text_up { - font-size: 200%; - font-family: WebgalUI, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; - letter-spacing: 0.15em; - //border-bottom: 2px solid rgba(255, 255, 255, 0.75); -} - .Title_backup_background { width: 100%; height: 100%; diff --git a/packages/webgal/src/config/info.ts b/packages/webgal/src/config/info.ts index 6ea1c90c6..7f199da5e 100644 --- a/packages/webgal/src/config/info.ts +++ b/packages/webgal/src/config/info.ts @@ -1,5 +1,5 @@ export const __INFO = { - version: 'WebGAL 4.4.11', + version: 'WebGAL 4.4.12', contributors: [ { username: 'Mahiru', link: 'https://github.com/MakinoharaShoko' }, { username: 'Hoshinokinya', link: 'https://github.com/hshqwq' }, diff --git a/packages/webgal/src/hooks/useApplyStyle.ts b/packages/webgal/src/hooks/useApplyStyle.ts new file mode 100644 index 000000000..9af1f7f8d --- /dev/null +++ b/packages/webgal/src/hooks/useApplyStyle.ts @@ -0,0 +1,51 @@ +import { useEffect } from 'react'; +import { WebGAL } from '@/Core/WebGAL'; +import axios from 'axios'; +import { IWebGALStyleObj, scss2cssinjsParser } from '@/Core/controller/customUI/scss2cssinjsParser'; +import { useValue } from '@/hooks/useValue'; +import { css, injectGlobal } from '@emotion/css'; +import { useSelector } from 'react-redux'; +import { RootState } from '@/store/store'; + +export default function useApplyStyle(url: string) { + const styleObject = useValue<IWebGALStyleObj>({ classNameStyles: {}, others: '' }); + const replaced = useSelector((state: RootState) => state.stage.replacedUIlable); + + const applyStyle = (classNameLable: string, fallbackClassName: string) => { + // 先看看是否被用户用 applyStyle 指令替换了类名 + const className = replaced?.[classNameLable] ?? classNameLable; + if (Object.keys(styleObject.value.classNameStyles).includes(className)) { + const cijClassName = css(styleObject.value.classNameStyles?.[className] ?? ''); + return `${fallbackClassName} ${cijClassName}`; + } + return fallbackClassName; + }; + + const updateStyleFile = async () => { + const resp = await axios.get(`game/template/${url}`); + const scssStr = resp.data; + styleObject.set(scss2cssinjsParser(scssStr)); + }; + + useEffect(() => { + updateStyleFile(); + }, []); + + useEffect(() => { + injectGlobal(styleObject.value.others); + }, [styleObject.value.others]); + + useRigisterStyleUpdate(updateStyleFile); + + return applyStyle; +} + +function useRigisterStyleUpdate(callback: Function) { + const handler = () => { + callback(); + }; + useEffect(() => { + WebGAL.events.styleUpdate.on(handler); + return () => WebGAL.events.styleUpdate.off(handler); + }, []); +} diff --git a/packages/webgal/src/hooks/useFullScreen.ts b/packages/webgal/src/hooks/useFullScreen.ts new file mode 100644 index 000000000..3dcad037e --- /dev/null +++ b/packages/webgal/src/hooks/useFullScreen.ts @@ -0,0 +1,35 @@ +import { setStorage } from '@/Core/controller/storage/storageController'; +import { RootState } from '@/store/store'; +import { fullScreenOption } from '@/store/userDataInterface'; +import { setOptionData } from '@/store/userDataReducer'; +import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { keyboard } from './useHotkey'; + +export function useFullScreen() { + const userDataState = useSelector((state: RootState) => state.userData); + const GUIState = useSelector((state: RootState) => state.GUI); + const dispatch = useDispatch(); + const fullScreen = userDataState.optionData.fullScreen; + const isEnterGame = GUIState.isEnterGame; + + useEffect(() => { + switch (fullScreen) { + case fullScreenOption.on: { + if (isEnterGame) { + document.documentElement.requestFullscreen(); + if (keyboard) keyboard.lock(['Escape', 'F11']); + }; + break; + } + case fullScreenOption.off: { + if (document.fullscreenElement) { + document.exitFullscreen(); + if (keyboard) keyboard.unlock(); + }; + break; + } + } + }, [fullScreen]); + +}; diff --git a/packages/webgal/src/hooks/useHotkey.tsx b/packages/webgal/src/hooks/useHotkey.tsx index a41c2998a..a08bbdfc5 100644 --- a/packages/webgal/src/hooks/useHotkey.tsx +++ b/packages/webgal/src/hooks/useHotkey.tsx @@ -1,16 +1,19 @@ import { useGenSyncRef } from '@/hooks/useGenSyncRef'; import { RootState } from '@/store/store'; import { useMounted, useUnMounted, useUpdated } from '@/hooks/useLifeCycle'; -import { useCallback, useRef } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { componentsVisibility, MenuPanelTag } from '@/store/guiInterface'; import { setVisibility } from '@/store/GUIReducer'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { startFast, stopAll, stopFast } from '@/Core/controller/gamePlay/fastSkip'; import { nextSentence } from '@/Core/controller/gamePlay/nextSentence'; import styles from '@/UI/Backlog/backlog.module.scss'; import throttle from 'lodash/throttle'; import { fastSaveGame } from '@/Core/controller/storage/fastSaveLoad'; import { WebGAL } from '@/Core/WebGAL'; +import { setOptionData } from '@/store/userDataReducer'; +import { setStorage } from '@/Core/controller/storage/storageController'; +import { fullScreenOption } from '@/store/userDataInterface'; // options备用 export interface HotKeyType { @@ -18,14 +21,21 @@ export interface HotKeyType { MouseWheel: {} | boolean; Ctrl: boolean; Esc: - | { - href: string; - nav: 'replace' | 'push'; - } - | boolean; + | { + href: string; + nav: 'replace' | 'push'; + } + | boolean; AutoSave: {} | boolean; } +export interface Keyboard { + lock: (keys: string[]) => Promise<void>; + unlock: () => Promise<void>; +} + +export const keyboard: Keyboard | undefined = 'keyboard' in navigator && (navigator.keyboard as any); // FireFox and Safari not support + // export const fastSaveGameKey = `FastSaveKey`; // export const isFastSaveKey = `FastSaveActive`; @@ -33,9 +43,10 @@ export function useHotkey(opt?: HotKeyType) { useMouseRightClickHotKey(); useMouseWheel(); useSkip(); - useEscape(); + usePanic(); useFastSaveBeforeUnloadPage(); useSpaceAndEnter(); + useToggleFullScreen(); } /** @@ -79,6 +90,10 @@ export function useMouseRightClickHotKey() { }); } +let wheelTimeout = setTimeout(() => { + // 初始化,什么也不干 +}, 0); + /** * 滚轮向上打开历史记录 * 滚轮向下关闭历史记录 @@ -123,6 +138,12 @@ export function useMouseWheel() { } // setComponentVisibility('showBacklog', false); } else if (isGameActive() && direction === 'down' && !ctrlKey) { + clearTimeout(wheelTimeout); + WebGAL.gameplay.isFast = true; + // 滚轮视作快进 + setTimeout(() => { + WebGAL.gameplay.isFast = false; + }, 150); next(); } }, []); @@ -135,19 +156,18 @@ export function useMouseWheel() { } /** - * ESC panic button + * Panic Button, use Esc and Backquote */ -export function useEscape() { - const isEscKey = useCallback( - (ev: KeyboardEvent) => !ev.isComposing && !ev.defaultPrevented && ev.code === 'Escape', - [], - ); +export function usePanic() { + const panicButtonList = ['Escape', 'Backquote']; + const isPanicButton = (ev: KeyboardEvent) => + !ev.isComposing && !ev.defaultPrevented && panicButtonList.includes(ev.code); const GUIStore = useGenSyncRef((state: RootState) => state.GUI); const isTitleShown = useCallback(() => GUIStore.current.showTitle, [GUIStore]); const isPanicOverlayOpen = useIsPanicOverlayOpen(GUIStore); const setComponentVisibility = useSetComponentVisibility(); - const handlePressEsc = useCallback((ev: KeyboardEvent) => { - if (!isEscKey(ev) || isTitleShown()) return; + const handlePressPanicButton = useCallback((ev: KeyboardEvent) => { + if (!isPanicButton(ev) || isTitleShown()) return; if (isPanicOverlayOpen()) { setComponentVisibility('showPanicOverlay', false); // todo: resume @@ -158,10 +178,10 @@ export function useEscape() { } }, []); useMounted(() => { - document.addEventListener('keyup', handlePressEsc); + document.addEventListener('keyup', handlePressPanicButton); }); useUnMounted(() => { - document.removeEventListener('keyup', handlePressEsc); + document.removeEventListener('keyup', handlePressPanicButton); }); } @@ -355,3 +375,30 @@ function hasScrollToBottom(dom: Element) { const { scrollTop, clientHeight, scrollHeight } = dom; return scrollTop === 0; } + +/** + * F11 进入全屏 + */ +function useToggleFullScreen() { + const userDataState = useSelector((state: RootState) => state.userData); + const dispatch = useDispatch(); + const fullScreen = userDataState.optionData.fullScreen; + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.code === 'F11') { + e.preventDefault(); + if (fullScreen !== fullScreenOption.on) { + dispatch(setOptionData({ key: 'fullScreen', value: fullScreenOption.on })); + setStorage(); + } else { + dispatch(setOptionData({ key: 'fullScreen', value: fullScreenOption.off })); + setStorage(); + } + } + }; + document.addEventListener('keydown', handleKeyDown); + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [fullScreen]); +} diff --git a/packages/webgal/src/main.tsx b/packages/webgal/src/main.tsx index ab5a27c67..640472b7a 100644 --- a/packages/webgal/src/main.tsx +++ b/packages/webgal/src/main.tsx @@ -11,6 +11,8 @@ import 'modern-css-reset/dist/reset.min.css'; import i18n from 'i18next'; import { initReactI18next, Trans } from 'react-i18next'; import { defaultLanguage, i18nTranslationResources, language } from './config/language'; +import { webgalStore } from './store/store'; +import { Provider } from 'react-redux'; i18n .use(initReactI18next) // passes i18n down to react-i18next @@ -32,7 +34,9 @@ i18n ReactDOM.render( <React.StrictMode> <Trans> - <App /> + <Provider store={webgalStore}> + <App /> + </Provider> </Trans> </React.StrictMode>, document.getElementById('root'), diff --git a/packages/webgal/src/store/GUIReducer.ts b/packages/webgal/src/store/GUIReducer.ts index 0a6160835..8eb91875c 100644 --- a/packages/webgal/src/store/GUIReducer.ts +++ b/packages/webgal/src/store/GUIReducer.ts @@ -3,7 +3,7 @@ * @author Mahiru */ import { getStorage } from '@/Core/controller/storage/storageController'; -import { GuiAsset, IGuiState, ITheme, MenuPanelTag, setAssetPayload, setVisibilityPayload } from '@/store/guiInterface'; +import { GuiAsset, IGuiState, MenuPanelTag, setAssetPayload, setVisibilityPayload } from '@/store/guiInterface'; import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { key } from 'localforage'; @@ -27,9 +27,6 @@ const initState: IGuiState = { showPanicOverlay: false, isEnterGame: false, isShowLogo: true, - theme: { - textbox: 'standard', - }, }; /** @@ -70,13 +67,10 @@ const GUISlice = createSlice({ setLogoImage: (state, action: PayloadAction<string[]>) => { state.logoImage = [...action.payload]; }, - setThemeConfigItem: (state, action: PayloadAction<{ key: keyof ITheme; value: string }>) => { - state.theme[action.payload.key] = action.payload.value as any; - }, }, }); -export const { setThemeConfigItem, setVisibility, setMenuPanelTag, setGuiAsset, setLogoImage } = GUISlice.actions; +export const { setVisibility, setMenuPanelTag, setGuiAsset, setLogoImage } = GUISlice.actions; export default GUISlice.reducer; // export function GuiStateStore(): GuiStore { diff --git a/packages/webgal/src/store/guiInterface.ts b/packages/webgal/src/store/guiInterface.ts index 0c902feca..845c7e652 100644 --- a/packages/webgal/src/store/guiInterface.ts +++ b/packages/webgal/src/store/guiInterface.ts @@ -9,10 +9,6 @@ export enum MenuPanelTag { Option, // “设置”选项卡 } -export interface ITheme { - textbox: IWebGalTextBoxTheme; -} - /** * @interface IGuiState GUI状态接口 */ @@ -33,7 +29,6 @@ export interface IGuiState { showPanicOverlay: boolean; isEnterGame: boolean; isShowLogo: boolean; - theme: ITheme; } export type componentsVisibility = Pick< diff --git a/packages/webgal/src/store/stageInterface.ts b/packages/webgal/src/store/stageInterface.ts index e8c599daa..04e9ee1ff 100644 --- a/packages/webgal/src/store/stageInterface.ts +++ b/packages/webgal/src/store/stageInterface.ts @@ -157,6 +157,7 @@ export interface IStageState { // 测试:电影叙事 enableFilm: string; isDisableTextbox: boolean; + replacedUIlable: Record<string, string>; } /** diff --git a/packages/webgal/src/store/stageReducer.ts b/packages/webgal/src/store/stageReducer.ts index f19547963..0eb86aa94 100644 --- a/packages/webgal/src/store/stageReducer.ts +++ b/packages/webgal/src/store/stageReducer.ts @@ -55,6 +55,7 @@ export const initState: IStageState = { currentConcatDialogPrev: '', enableFilm: '', isDisableTextbox: false, + replacedUIlable: {}, }; /** @@ -169,6 +170,9 @@ const stageSlice = createSlice({ state.live2dExpression[index].expression = expression; } }, + replaceUIlable: (state, action: PayloadAction<[string, string]>) => { + state.replacedUIlable[action.payload[0]] = action.payload[1]; + }, }, }); diff --git a/packages/webgal/src/store/userDataInterface.ts b/packages/webgal/src/store/userDataInterface.ts index faa9b3d4d..57f9a5f43 100644 --- a/packages/webgal/src/store/userDataInterface.ts +++ b/packages/webgal/src/store/userDataInterface.ts @@ -29,6 +29,11 @@ export enum voiceOption { no, } +export enum fullScreenOption { + on, + off, +} + /** * @interface IOptionData 用户设置数据接口 */ @@ -46,6 +51,7 @@ export interface IOptionData { textboxOpacity: number; language: language; voiceInterruption: voiceOption; // 是否中断语音 + fullScreen: fullScreenOption; } /** diff --git a/packages/webgal/src/store/userDataReducer.ts b/packages/webgal/src/store/userDataReducer.ts index 0d9741f4b..4dff863ca 100644 --- a/packages/webgal/src/store/userDataReducer.ts +++ b/packages/webgal/src/store/userDataReducer.ts @@ -11,6 +11,7 @@ import { ISetOptionDataPayload, ISetUserDataPayload, IUserData, + fullScreenOption, playSpeed, textFont, textSize, @@ -34,6 +35,7 @@ const initialOptionSet: IOptionData = { textboxOpacity: 75, language: language.zhCn, voiceInterruption: voiceOption.yes, + fullScreen: fullScreenOption.off, }; // 初始化用户数据 @@ -127,6 +129,9 @@ const userDataSlice = createSlice({ setFastSave: (state, action: PayloadAction<ISaveData | null>) => { state.quickSaveData = action.payload; }, + resetFastSave: (state) => { + state.quickSaveData = null; + }, resetOptionSet(state) { Object.assign(state.optionData, initialOptionSet); }, @@ -151,6 +156,7 @@ export const { resetOptionSet, resetSaveData, resetAllData, + resetFastSave, } = userDataSlice.actions; export default userDataSlice.reducer; diff --git a/packages/webgal/src/translations/en.ts b/packages/webgal/src/translations/en.ts index 6d6892fd0..466a8e6df 100644 --- a/packages/webgal/src/translations/en.ts +++ b/packages/webgal/src/translations/en.ts @@ -63,8 +63,15 @@ const en = { display: { title: 'Display', options: { + fullScreen: { + title: 'Full Screen', + options: { + on: 'ON', + off: 'OFF', + }, + }, textSpeed: { - title: 'Speed of Text Showing', + title: 'Text Speed', options: { slow: 'Slow', medium: 'Medium', diff --git a/packages/webgal/src/translations/zh-cn.ts b/packages/webgal/src/translations/zh-cn.ts index 38fdcd1ba..471b2a547 100644 --- a/packages/webgal/src/translations/zh-cn.ts +++ b/packages/webgal/src/translations/zh-cn.ts @@ -63,6 +63,13 @@ const zhCn = { display: { title: '显示', options: { + fullScreen: { + title: '全屏模式', + options: { + on: '开启', + off: '关闭', + }, + }, textSpeed: { title: '文字显示速度', options: { diff --git a/packages/webgal/src/types/debugProtocol.ts b/packages/webgal/src/types/debugProtocol.ts index 1e8a69ac0..e8cf566d6 100644 --- a/packages/webgal/src/types/debugProtocol.ts +++ b/packages/webgal/src/types/debugProtocol.ts @@ -9,6 +9,8 @@ export enum DebugCommand { SYNCFE, // 执行指令 EXE_COMMAND, + // 重新拉取模板样式文件 + REFETCH_TEMPLATE_FILES, } export interface IDebugMessage { diff --git a/packages/webgal/vite.config.ts b/packages/webgal/vite.config.ts index f35fe9af4..400a37ada 100644 --- a/packages/webgal/vite.config.ts +++ b/packages/webgal/vite.config.ts @@ -25,7 +25,7 @@ console.log(env); const filePath = relativePath + '/' + v.slice(0, v.lastIndexOf('.')); return `import '${filePath}';`; }) - .join('\n'), + .join('\n') + '\n', { encoding: 'utf-8' }, ); } diff --git a/releasenote.md b/releasenote.md index 73a34777a..386133e8b 100644 --- a/releasenote.md +++ b/releasenote.md @@ -8,15 +8,23 @@ #### 新功能 -从编辑器接受指令 +调整菜单 UI + +移除文本框其他预设,并接入 UI 自定义模块,为下版本编辑器支持 UI 自定义做准备 + +添加全屏游戏功能 #### 修复 -小幅改善 iOS 设备的崩溃状况 +使用鼠标滚轮快进时立绘延迟退出的问题 + +效果音循环时阻塞自动播放的问题 + +iOS 或 Safari 平台下无法正确处理换行和空格的问题 -优化 Backlog 性能 +调用 `end` 指令后在某些情况下无法继续游戏的问题 -修复动画模块异常 +某些情况下音频无法自动播放的问题 <!-- English Translation --> ## Release Notes @@ -27,17 +35,25 @@ ### In this version -#### New Features +#### New features + +Adjust menu UI + +Remove other presets of text box, and connect to UI customization module, preparing for UI customization supported by next version of editor + +Add full screen game function + +#### Bug fixes -Accept commands from editor +Fix the problem that standing pictures exit delay when fast forwarding with mouse wheel -#### Fixes +Fix the problem that looping sound effects block automatic playback -Slightly improve crash situation on iOS devices +Fix the problem that line breaks and spaces cannot be handled correctly on iOS or Safari platforms -Optimize Backlog performance +Fix the problem that the game cannot continue in some cases after calling the `end` instruction -Fix abnormal animation module +Fix the problem that audio cannot be played automatically in some cases <!-- Japanese Translation --> @@ -51,15 +67,23 @@ Fix abnormal animation module #### 新機能 -エディタからの指示を受け付ける +メニューUIの調整 + +テキストボックスのその他のプリセットを削除し、UIカスタマイズモジュールに接続して、次バージョンのエディタがUIカスタマイズをサポートできるようにします + +全画面ゲーム機能を追加 #### 修正 -iOS デバイスでのクラッシュを若干改善 +マウスホイールを使用して早送りすると立ち絵が遅れて終了する問題 + +効果音が循環すると自動再生がブロックされる問題 + +iOSまたはSafariプラットフォームで改行とスペースが正しく処理されない問題 -Backlog のパフォーマンスを最適化 +特定の状況で`end`命令を呼び出した後、ゲームを続行できない問題 -アニメーションモジュールの異常を修正 +特定の状況でオーディオが自動再生されない問題 <!-- French Translation --> @@ -73,13 +97,21 @@ Backlog のパフォーマンスを最適化 #### Nouvelles fonctionnalités -Recevoir des instructions de l'éditeur +Ajustement de l'interface utilisateur du menu + +Suppression des autres préréglages de zone de texte et connexion au module de personnalisation de l'interface utilisateur, en vue de la prise en charge de la personnalisation de l'interface utilisateur dans la prochaine version de l'éditeur + +Ajout de la fonctionnalité de jeu en plein écran #### Corrections -Amélioration mineure des plantages sur les appareils iOS +Problème de retard de sortie de l'image lors d'une avance rapide à l'aide de la molette de la souris + +Problème de blocage de la lecture automatique lors de la mise en boucle des effets sonores + +Problème de traitement incorrect des sauts de ligne et des espaces sur les plateformes iOS ou Safari -Optimisation des performances de Backlog +Problème d'impossibilité de continuer le jeu dans certains cas après avoir appelé l'instruction `end` -Correction d'un bug dans le module d'animation +Problème d'impossibilité de lecture automatique de l'audio dans certains cas diff --git a/yarn.lock b/yarn.lock index b0947b6d8..01dea8352 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,7 +15,7 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== @@ -99,7 +99,7 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.22.15": +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== @@ -200,6 +200,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" + integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -282,6 +289,86 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@emotion/babel-plugin@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" + integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/serialize" "^1.1.2" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + +"@emotion/cache@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" + integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + stylis "4.2.0" + +"@emotion/css@^11.11.2": + version "11.11.2" + resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.11.2.tgz#e5fa081d0c6e335352e1bc2b05953b61832dca5a" + integrity sha512-VJxe1ucoMYMS7DkiMdC2T7PWNbrEI0a39YRiyDvK2qq4lXwjRbVP/z4lpG+odCsRzadlR+1ywwrTzhdm5HNdew== + dependencies: + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.2" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + +"@emotion/hash@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" + integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== + +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + +"@emotion/serialize@^1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.3.tgz#84b77bfcfe3b7bb47d326602f640ccfcacd5ffb0" + integrity sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA== + dependencies: + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/unitless" "^0.8.1" + "@emotion/utils" "^1.2.1" + csstype "^3.0.2" + +"@emotion/sheet@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" + integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== + +"@emotion/unitless@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + +"@emotion/utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" + integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== + +"@emotion/weak-memoize@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" + integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== + "@esbuild/android-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" @@ -1064,6 +1151,11 @@ resolved "https://registry.yarnpkg.com/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz#90267db13f64d6e9ccb5ae3eac92786a7c77a516" integrity sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A== +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + "@types/prop-types@*": version "15.7.11" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" @@ -1510,6 +1602,15 @@ axios@^0.26.1: dependencies: follow-redirects "^1.14.8" +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -1832,6 +1933,11 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" @@ -1852,6 +1958,17 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -2037,6 +2154,13 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + es-abstract@^1.22.1: version "1.22.3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" @@ -2482,6 +2606,11 @@ find-cache-dir@^3.3.1, find-cache-dir@^3.3.2: make-dir "^3.0.2" pkg-dir "^4.1.0" +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + find-up@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -2984,6 +3113,11 @@ is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: get-intrinsic "^1.2.0" is-typed-array "^1.1.10" +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + is-async-function@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" @@ -3278,6 +3412,11 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -3351,6 +3490,11 @@ lilconfig@2.0.5: resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + lint-staged@^12.3.7: version "12.5.0" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.5.0.tgz#d6925747480ae0e380d13988522f9dd8ef9126e3" @@ -3829,6 +3973,16 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -4356,7 +4510,7 @@ resolve-pkg-maps@^1.0.0: resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== -resolve@^1.22.0, resolve@^1.22.1: +resolve@^1.19.0, resolve@^1.22.0, resolve@^1.22.1: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -4678,6 +4832,11 @@ source-map-support@^0.5.21, source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" +source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -4840,6 +4999,11 @@ style-value-types@5.1.2: hey-listen "^1.0.8" tslib "2.4.0" +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -5207,10 +5371,10 @@ vite-plugin-package-version@^1.0.2: resolved "https://registry.yarnpkg.com/vite-plugin-package-version/-/vite-plugin-package-version-1.1.0.tgz#7d8088955aa21e4ec93353c98992b3f58c4bf13c" integrity sha512-TPoFZXNanzcaKCIrC3e2L/TVRkkRLB6l4RPN/S7KbG7rWfyLcCEGsnXvxn6qR7fyZwXalnnSN/I9d6pSFjHpEA== -"vite@^3.0.0 || ^4.0.0", vite@^4.4.9: - version "4.5.1" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.1.tgz#3370986e1ed5dbabbf35a6c2e1fb1e18555b968a" - integrity sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA== +"vite@^3.0.0 || ^4.0.0", vite@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.2.tgz#d6ea8610e099851dad8c7371599969e0f8b97e82" + integrity sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w== dependencies: esbuild "^0.18.10" postcss "^8.4.27" @@ -5379,7 +5543,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.2: +yaml@^1.10.0, yaml@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==