mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-11-04 07:02:58 +08:00 
			
		
		
		
	feat: 部分设置菜单
This commit is contained in:
		
							parent
							
								
									0cc76bd475
								
							
						
					
					
						commit
						8cc0c76846
					
				@ -23,7 +23,7 @@ export const UIContainer = defineComponent<UIContainerProps>(props => {
 | 
				
			|||||||
                    controller={data}
 | 
					                    controller={data}
 | 
				
			||||||
                    instance={b}
 | 
					                    instance={b}
 | 
				
			||||||
                    key={b.key}
 | 
					                    key={b.key}
 | 
				
			||||||
                    hidden={b.hidden}
 | 
					                    hidden={b.hidden && !b.alwaysShow}
 | 
				
			||||||
                ></b.ui.component>
 | 
					                ></b.ui.component>
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -34,7 +34,7 @@ export const UIContainer = defineComponent<UIContainerProps>(props => {
 | 
				
			|||||||
                    key={v.key}
 | 
					                    key={v.key}
 | 
				
			||||||
                    controller={data}
 | 
					                    controller={data}
 | 
				
			||||||
                    instance={v}
 | 
					                    instance={v}
 | 
				
			||||||
                    hidden={v.hidden}
 | 
					                    hidden={v.hidden && !v.alwaysShow}
 | 
				
			||||||
                ></v.ui.component>
 | 
					                ></v.ui.component>
 | 
				
			||||||
            ))
 | 
					            ))
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
				
			|||||||
@ -94,6 +94,7 @@
 | 
				
			|||||||
        "60": "Repeated Tip id: '$1'.",
 | 
					        "60": "Repeated Tip id: '$1'.",
 | 
				
			||||||
        "61": "Unexpected recursive call of $1.update in render function. Please ensure you have to do this, if you do, ignore this warn.",
 | 
					        "61": "Unexpected recursive call of $1.update in render function. Please ensure you have to do this, if you do, ignore this warn.",
 | 
				
			||||||
        "62": "Recursive fallback fonts in '$1'.",
 | 
					        "62": "Recursive fallback fonts in '$1'.",
 | 
				
			||||||
 | 
					        "63": "Uncaught promise error in waiting box component. Error reason: $1",
 | 
				
			||||||
        "1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
 | 
					        "1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
 | 
				
			||||||
        "1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
 | 
					        "1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -50,7 +50,7 @@ const confirmBoxProps = {
 | 
				
			|||||||
>;
 | 
					>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 确认框组件,与 2.x 的 drawConfirm 类似,可以键盘操作,
 | 
					 * 确认框组件,与 2.x 的 drawConfirm 类似,可以键盘操作,单次调用参考 {@link getConfirm}。
 | 
				
			||||||
 * 参数参考 {@link ConfirmBoxProps},事件参考 {@link ConfirmBoxEmits},用例如下:
 | 
					 * 参数参考 {@link ConfirmBoxProps},事件参考 {@link ConfirmBoxEmits},用例如下:
 | 
				
			||||||
 * ```tsx
 | 
					 * ```tsx
 | 
				
			||||||
 * const onYes = () => console.log('yes');
 | 
					 * const onYes = () => console.log('yes');
 | 
				
			||||||
@ -243,7 +243,7 @@ const choicesProps = {
 | 
				
			|||||||
>;
 | 
					>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 选项框组件,用于在多个选项中选择一个,例如样板的系统设置就由它实现。
 | 
					 * 选项框组件,用于在多个选项中选择一个,例如样板的系统设置就由它实现。单次调用参考 {@link getChoice}。
 | 
				
			||||||
 * 参数参考 {@link ChoicesProps},事件参考 {@link ChoicesEmits}。用例如下:
 | 
					 * 参数参考 {@link ChoicesProps},事件参考 {@link ChoicesEmits}。用例如下:
 | 
				
			||||||
 * ```tsx
 | 
					 * ```tsx
 | 
				
			||||||
 * <Choices
 | 
					 * <Choices
 | 
				
			||||||
@ -530,12 +530,28 @@ export const Choices = defineComponent<
 | 
				
			|||||||
}, choicesProps);
 | 
					}, choicesProps);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 弹出一个确认框,然后将确认结果返回
 | 
					 * 弹出一个确认框,然后将确认结果返回,例如给玩家弹出一个确认框,并获取玩家是否确认:
 | 
				
			||||||
 | 
					 * ```ts
 | 
				
			||||||
 | 
					 * const confirm = await getConfirm(
 | 
				
			||||||
 | 
					 *   // 在哪个 UI 控制器上打开,对于一般 UI 组件来说,直接填写 props.controller 即可
 | 
				
			||||||
 | 
					 *   props.controller,
 | 
				
			||||||
 | 
					 *   // 确认内容
 | 
				
			||||||
 | 
					 *   '确认要 xxx 吗?',
 | 
				
			||||||
 | 
					 *   // 确认框的位置,宽度由下一个参数指定,高度参数由组件内部计算得出,指定无效
 | 
				
			||||||
 | 
					 *   [240, 240, void 0, void 0, 0.5, 0.5],
 | 
				
			||||||
 | 
					 *   // 宽度设为 240
 | 
				
			||||||
 | 
					 *   240,
 | 
				
			||||||
 | 
					 *   // 可以给选择框传入其他的 props,例如指定字体,此项可选
 | 
				
			||||||
 | 
					 *   { font: new Font('Verdana', 20) }
 | 
				
			||||||
 | 
					 * );
 | 
				
			||||||
 | 
					 * // 之后,就可以直接判断 confirm 来执行不同的操作了
 | 
				
			||||||
 | 
					 * if (confirm) { ... }
 | 
				
			||||||
 | 
					 * ```
 | 
				
			||||||
 * @param controller UI 控制器
 | 
					 * @param controller UI 控制器
 | 
				
			||||||
 * @param text 确认文本内容
 | 
					 * @param text 确认文本内容
 | 
				
			||||||
 * @param loc 确认框的位置
 | 
					 * @param loc 确认框的位置
 | 
				
			||||||
 * @param width 确认框的宽度
 | 
					 * @param width 确认框的宽度
 | 
				
			||||||
 * @param props 额外的 props
 | 
					 * @param props 额外的 props,参考 {@link ConfirmBoxProps}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function getConfirm(
 | 
					export function getConfirm(
 | 
				
			||||||
    controller: IUIMountable,
 | 
					    controller: IUIMountable,
 | 
				
			||||||
@ -567,12 +583,28 @@ export function getConfirm(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 弹出一个选择框,然后将选择结果返回
 | 
					 * 弹出一个选择框,然后将选择结果返回,例如给玩家弹出一个选择框,并获取玩家选择了哪个:
 | 
				
			||||||
 | 
					 * ```ts
 | 
				
			||||||
 | 
					 * const choice = await getChoice(
 | 
				
			||||||
 | 
					 *   // 在哪个 UI 控制器上打开,对于一般 UI 组件来说,直接填写 props.controller 即可
 | 
				
			||||||
 | 
					 *   props.controller,
 | 
				
			||||||
 | 
					 *   // 选项内容,参考 Choices 的注释
 | 
				
			||||||
 | 
					 *   [[0, '选项1'], [1, '选项2'], [2, '选项3']],
 | 
				
			||||||
 | 
					 *   // 选择框的位置,宽度由下一个参数指定,高度参数由组件内部计算得出,指定无效
 | 
				
			||||||
 | 
					 *   [240, 240, void 0, void 0, 0.5, 0.5],
 | 
				
			||||||
 | 
					 *   // 宽度设为 240
 | 
				
			||||||
 | 
					 *   240,
 | 
				
			||||||
 | 
					 *   // 可以给选择框传入其他的 props,例如指定标题,此项可选
 | 
				
			||||||
 | 
					 *   { title: '选项标题' }
 | 
				
			||||||
 | 
					 * );
 | 
				
			||||||
 | 
					 * // 之后,就可以直接判断 choice 来执行不同的操作了
 | 
				
			||||||
 | 
					 * if (choice === 0) { ... }
 | 
				
			||||||
 | 
					 * ```
 | 
				
			||||||
 * @param controller UI 控制器
 | 
					 * @param controller UI 控制器
 | 
				
			||||||
 * @param choices 选择框的选项
 | 
					 * @param choices 选择框的选项
 | 
				
			||||||
 * @param loc 选择框的位置
 | 
					 * @param loc 选择框的位置
 | 
				
			||||||
 * @param width 选择框的宽度
 | 
					 * @param width 选择框的宽度
 | 
				
			||||||
 * @param props 额外的 props
 | 
					 * @param props 额外的 props,参考 {@link ChoicesProps}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function getChoice<T extends ChoiceKey = ChoiceKey>(
 | 
					export function getChoice<T extends ChoiceKey = ChoiceKey>(
 | 
				
			||||||
    controller: IUIMountable,
 | 
					    controller: IUIMountable,
 | 
				
			||||||
 | 
				
			|||||||
@ -5,13 +5,15 @@ import {
 | 
				
			|||||||
    PathProps,
 | 
					    PathProps,
 | 
				
			||||||
    Sprite
 | 
					    Sprite
 | 
				
			||||||
} from '@/core/render';
 | 
					} from '@/core/render';
 | 
				
			||||||
import { computed, defineComponent, ref, watch } from 'vue';
 | 
					import { computed, defineComponent, ref, SetupContext, watch } from 'vue';
 | 
				
			||||||
import { SetupComponentOptions } from './types';
 | 
					import { SetupComponentOptions } from './types';
 | 
				
			||||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
 | 
					import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
 | 
				
			||||||
import { TextboxProps, TextContent } from './textbox';
 | 
					import { TextboxProps, TextContent, TextContentProps } from './textbox';
 | 
				
			||||||
import { Scroll, ScrollExpose, ScrollProps } from './scroll';
 | 
					import { Scroll, ScrollExpose, ScrollProps } from './scroll';
 | 
				
			||||||
import { transitioned } from '../use';
 | 
					import { transitioned } from '../use';
 | 
				
			||||||
import { hyper } from 'mutate-animate';
 | 
					import { hyper } from 'mutate-animate';
 | 
				
			||||||
 | 
					import { logger } from '@/core/common/logger';
 | 
				
			||||||
 | 
					import { GameUI, IUIMountable } from '@/core/system';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ProgressProps extends DefaultProps {
 | 
					interface ProgressProps extends DefaultProps {
 | 
				
			||||||
    /** 进度条的位置 */
 | 
					    /** 进度条的位置 */
 | 
				
			||||||
@ -401,3 +403,181 @@ export const Background = defineComponent<BackgroundProps>(props => {
 | 
				
			|||||||
            />
 | 
					            />
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
}, backgroundProps);
 | 
					}, backgroundProps);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface WaitBoxProps<T>
 | 
				
			||||||
 | 
					    extends Partial<BackgroundProps>,
 | 
				
			||||||
 | 
					        Partial<TextContentProps> {
 | 
				
			||||||
 | 
					    loc: ElementLocator;
 | 
				
			||||||
 | 
					    width: number;
 | 
				
			||||||
 | 
					    promise?: Promise<T>;
 | 
				
			||||||
 | 
					    text?: string;
 | 
				
			||||||
 | 
					    pad?: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type WaitBoxEmits<T> = {
 | 
				
			||||||
 | 
					    resolve: (data: T) => void;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface WaitBoxExpose<T> {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 手动将此组件兑现,注意调用时如果传入的 Promise 还没有兑现,
 | 
				
			||||||
 | 
					     * 当 Promise 兑现后将不会再次触发 resolve 事件,即 resolve 事件只会被触发一次
 | 
				
			||||||
 | 
					     * @param data 兑现值
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    resolve(data: T): void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const waitBoxProps = {
 | 
				
			||||||
 | 
					    props: ['promise', 'loc', 'winskin', 'color', 'border'],
 | 
				
			||||||
 | 
					    emits: ['resolve']
 | 
				
			||||||
 | 
					} satisfies SetupComponentOptions<
 | 
				
			||||||
 | 
					    WaitBoxProps<unknown>,
 | 
				
			||||||
 | 
					    WaitBoxEmits<unknown>,
 | 
				
			||||||
 | 
					    keyof WaitBoxEmits<unknown>
 | 
				
			||||||
 | 
					>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 等待框,可以等待某个异步操作 (Promise),操作完毕后触发兑现事件,单次调用参考 {@link waitbox}。
 | 
				
			||||||
 | 
					 * 参数参考 {@link WaitBoxProps},事件参考 {@link WaitBoxEmits},函数接口参考 {@link WaitBoxExpose}。用例如下:
 | 
				
			||||||
 | 
					 * ```tsx
 | 
				
			||||||
 | 
					 * // 创建一个等待 1000ms 的 Promise,兑现值是等待完毕时的当前时间刻
 | 
				
			||||||
 | 
					 * const promise = new Promise(res => window.setTimeout(() => res(Date.now()), 1000));
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * <WaitBox
 | 
				
			||||||
 | 
					 *   // 传入要等待的 Promise
 | 
				
			||||||
 | 
					 *   promise={promise}
 | 
				
			||||||
 | 
					 *   // 等待框的位置,宽度由 width 参数指定,高度由内部计算得来,不需要手动指定,即使手动指定也无效
 | 
				
			||||||
 | 
					 *   loc={[240, 240, void 0, void 0, 0.5, 0.5]}
 | 
				
			||||||
 | 
					 *   // 等待框的宽度
 | 
				
			||||||
 | 
					 *   width={240}
 | 
				
			||||||
 | 
					 *   // 完全继承 Background 的参数,因此可以直接指定背景样式
 | 
				
			||||||
 | 
					 *   winskin="winskin2.png"
 | 
				
			||||||
 | 
					 *   // 完全继承 TextContent 的参数,因此可以直接指定字体
 | 
				
			||||||
 | 
					 *   font={new Font('Verdana', 28)}
 | 
				
			||||||
 | 
					 * />
 | 
				
			||||||
 | 
					 * ```
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const WaitBox = defineComponent<
 | 
				
			||||||
 | 
					    WaitBoxProps<unknown>,
 | 
				
			||||||
 | 
					    WaitBoxEmits<unknown>,
 | 
				
			||||||
 | 
					    keyof WaitBoxEmits<unknown>
 | 
				
			||||||
 | 
					>(
 | 
				
			||||||
 | 
					    <T,>(
 | 
				
			||||||
 | 
					        props: WaitBoxProps<T>,
 | 
				
			||||||
 | 
					        { emit, expose, attrs }: SetupContext<WaitBoxEmits<T>>
 | 
				
			||||||
 | 
					    ) => {
 | 
				
			||||||
 | 
					        const contentHeight = ref(200);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const text = computed(() => props.text ?? '请等待 ...');
 | 
				
			||||||
 | 
					        const pad = computed(() => props.pad ?? 24);
 | 
				
			||||||
 | 
					        const containerLoc = computed<ElementLocator>(() => {
 | 
				
			||||||
 | 
					            const [x = 0, y = 0, , , ax = 0, ay = 0] = props.loc;
 | 
				
			||||||
 | 
					            return [x, y, props.width, contentHeight.value, ax, ay];
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        const backLoc = computed<ElementLocator>(() => {
 | 
				
			||||||
 | 
					            return [1, 1, props.width - 2, contentHeight.value - 2];
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        const contentLoc = computed<ElementLocator>(() => {
 | 
				
			||||||
 | 
					            return [
 | 
				
			||||||
 | 
					                pad.value,
 | 
				
			||||||
 | 
					                pad.value,
 | 
				
			||||||
 | 
					                props.width - pad.value * 2,
 | 
				
			||||||
 | 
					                contentHeight.value - pad.value * 2
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let resolved: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        props.promise?.then(
 | 
				
			||||||
 | 
					            value => {
 | 
				
			||||||
 | 
					                resolve(value);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            reason => {
 | 
				
			||||||
 | 
					                logger.warn(63, reason);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const resolve = (data: T) => {
 | 
				
			||||||
 | 
					            if (resolved) return;
 | 
				
			||||||
 | 
					            resolved = true;
 | 
				
			||||||
 | 
					            emit('resolve', data);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const onContentHeight = (height: number) => {
 | 
				
			||||||
 | 
					            contentHeight.value = height + pad.value * 2;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expose<WaitBoxExpose<T>>({ resolve });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return () => (
 | 
				
			||||||
 | 
					            <container loc={containerLoc.value}>
 | 
				
			||||||
 | 
					                <Background
 | 
				
			||||||
 | 
					                    loc={backLoc.value}
 | 
				
			||||||
 | 
					                    zIndex={0}
 | 
				
			||||||
 | 
					                    winskin={props.winskin}
 | 
				
			||||||
 | 
					                    color={props.color}
 | 
				
			||||||
 | 
					                    border={props.border}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <TextContent
 | 
				
			||||||
 | 
					                    {...attrs}
 | 
				
			||||||
 | 
					                    autoHeight
 | 
				
			||||||
 | 
					                    text={text.value}
 | 
				
			||||||
 | 
					                    loc={contentLoc.value}
 | 
				
			||||||
 | 
					                    width={props.width - pad.value * 2}
 | 
				
			||||||
 | 
					                    zIndex={5}
 | 
				
			||||||
 | 
					                    onUpdateHeight={onContentHeight}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </container>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    waitBoxProps
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 打开一个等待框,等待传入的 Promise 兑现后,关闭等待框,并将兑现值返回。
 | 
				
			||||||
 | 
					 * 示例,等待 1000ms:
 | 
				
			||||||
 | 
					 * ```ts
 | 
				
			||||||
 | 
					 * // 创建一个等待 1000ms 的 Promise,兑现值是等待完毕时的当前时间刻
 | 
				
			||||||
 | 
					 * const promise = new Promise(res => window.setTimeout(() => res(Date.now()), 1000));
 | 
				
			||||||
 | 
					 * const value = await waitbox(
 | 
				
			||||||
 | 
					 *   // 在哪个 UI 控制器上打开,对于一般 UI 组件来说,直接填写 props.controller 即可
 | 
				
			||||||
 | 
					 *   props.controller,
 | 
				
			||||||
 | 
					 *   // 确认框的位置,宽度由下一个参数指定,高度参数由组件内部计算得出,指定无效
 | 
				
			||||||
 | 
					 *   [240, 240, void 0, void 0, 0.5, 0.5],
 | 
				
			||||||
 | 
					 *   // 确认框的宽度
 | 
				
			||||||
 | 
					 *   240,
 | 
				
			||||||
 | 
					 *   // 要等待的 Promise
 | 
				
			||||||
 | 
					 *   promise,
 | 
				
			||||||
 | 
					 *   // 额外的 props,例如填写等待文本,此项可选,参考 WaitBoxProps
 | 
				
			||||||
 | 
					 *   { text: '请等待 1000ms' }
 | 
				
			||||||
 | 
					 * );
 | 
				
			||||||
 | 
					 * console.log(value); // 输出时间刻
 | 
				
			||||||
 | 
					 * ```
 | 
				
			||||||
 | 
					 * @param controller UI 控制器
 | 
				
			||||||
 | 
					 * @param loc 等待框的位置
 | 
				
			||||||
 | 
					 * @param width 等待框的宽度
 | 
				
			||||||
 | 
					 * @param promise 要等待的 Promise
 | 
				
			||||||
 | 
					 * @param props 额外的 props,参考 {@link WaitBoxProps}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function waitbox<T>(
 | 
				
			||||||
 | 
					    controller: IUIMountable,
 | 
				
			||||||
 | 
					    loc: ElementLocator,
 | 
				
			||||||
 | 
					    width: number,
 | 
				
			||||||
 | 
					    promise: Promise<T>,
 | 
				
			||||||
 | 
					    props?: Partial<WaitBoxProps<T>>
 | 
				
			||||||
 | 
					): Promise<T> {
 | 
				
			||||||
 | 
					    return new Promise<T>(res => {
 | 
				
			||||||
 | 
					        const instance = controller.open(WaitBoxUI, {
 | 
				
			||||||
 | 
					            ...(props ?? {}),
 | 
				
			||||||
 | 
					            loc,
 | 
				
			||||||
 | 
					            width,
 | 
				
			||||||
 | 
					            promise,
 | 
				
			||||||
 | 
					            onResolve: data => {
 | 
				
			||||||
 | 
					                controller.close(instance);
 | 
				
			||||||
 | 
					                res(data as T);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const WaitBoxUI = new GameUI('wait-box', WaitBox);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										579
									
								
								src/module/render/ui/settings.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										579
									
								
								src/module/render/ui/settings.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,579 @@
 | 
				
			|||||||
 | 
					import { ElementLocator } from '@/core/render';
 | 
				
			||||||
 | 
					import { GameUI, UIComponentProps } from '@/core/system';
 | 
				
			||||||
 | 
					import { defineComponent } from 'vue';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    ChoiceItem,
 | 
				
			||||||
 | 
					    ChoiceKey,
 | 
				
			||||||
 | 
					    Choices,
 | 
				
			||||||
 | 
					    ChoicesProps,
 | 
				
			||||||
 | 
					    getConfirm,
 | 
				
			||||||
 | 
					    SetupComponentOptions,
 | 
				
			||||||
 | 
					    waitbox
 | 
				
			||||||
 | 
					} from '../components';
 | 
				
			||||||
 | 
					import { mainUi } from '@/core/main/init/ui';
 | 
				
			||||||
 | 
					import { gameKey } from '@/core/main/custom/hotkey';
 | 
				
			||||||
 | 
					import { generateKeyboardEvent } from '@/core/main/custom/keyboard';
 | 
				
			||||||
 | 
					import { getVitualKeyOnce } from '@/plugin/utils';
 | 
				
			||||||
 | 
					import { getAllSavesData, getSaveData } from '@/module/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SettingsProps extends Partial<ChoicesProps>, UIComponentProps {
 | 
				
			||||||
 | 
					    loc: ElementLocator;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const settingsProps = {
 | 
				
			||||||
 | 
					    props: ['loc', 'controller', 'instance']
 | 
				
			||||||
 | 
					} satisfies SetupComponentOptions<SettingsProps>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const enum MainChoice {
 | 
				
			||||||
 | 
					    SystemSetting,
 | 
				
			||||||
 | 
					    VirtualKey,
 | 
				
			||||||
 | 
					    ViewMap,
 | 
				
			||||||
 | 
					    /** @see {@link ReplaySettings} */
 | 
				
			||||||
 | 
					    Replay,
 | 
				
			||||||
 | 
					    /** @see {@link SyncSave} */
 | 
				
			||||||
 | 
					    SyncSave,
 | 
				
			||||||
 | 
					    /** @see {@link GameInfo} */
 | 
				
			||||||
 | 
					    GameInfo,
 | 
				
			||||||
 | 
					    Restart,
 | 
				
			||||||
 | 
					    Back
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const MainSettings = defineComponent<SettingsProps>(props => {
 | 
				
			||||||
 | 
					    const choices: ChoiceItem[] = [
 | 
				
			||||||
 | 
					        [MainChoice.SystemSetting, '系统设置'],
 | 
				
			||||||
 | 
					        [MainChoice.VirtualKey, '虚拟键盘'],
 | 
				
			||||||
 | 
					        [MainChoice.ViewMap, '浏览地图'],
 | 
				
			||||||
 | 
					        [MainChoice.Replay, '录像回放'],
 | 
				
			||||||
 | 
					        [MainChoice.SyncSave, '同步存档'],
 | 
				
			||||||
 | 
					        [MainChoice.GameInfo, '游戏信息'],
 | 
				
			||||||
 | 
					        [MainChoice.Restart, '返回标题'],
 | 
				
			||||||
 | 
					        [MainChoice.Back, '返回游戏']
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const choose = (key: ChoiceKey) => {
 | 
				
			||||||
 | 
					        switch (key) {
 | 
				
			||||||
 | 
					            case MainChoice.SystemSetting: {
 | 
				
			||||||
 | 
					                mainUi.open('settings');
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case MainChoice.VirtualKey: {
 | 
				
			||||||
 | 
					                getVitualKeyOnce().then(value => {
 | 
				
			||||||
 | 
					                    gameKey.emitKey(
 | 
				
			||||||
 | 
					                        value.key,
 | 
				
			||||||
 | 
					                        value.assist,
 | 
				
			||||||
 | 
					                        'up',
 | 
				
			||||||
 | 
					                        generateKeyboardEvent(value.key, value.assist)
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case MainChoice.ViewMap: {
 | 
				
			||||||
 | 
					                // todo
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case MainChoice.Replay: {
 | 
				
			||||||
 | 
					                props.controller.open(ReplaySettingsUI, { loc: props.loc });
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case MainChoice.SyncSave: {
 | 
				
			||||||
 | 
					                props.controller.open(SyncSaveUI, { loc: props.loc });
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case MainChoice.GameInfo: {
 | 
				
			||||||
 | 
					                props.controller.open(GameInfoUI, { loc: props.loc });
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case MainChoice.Restart: {
 | 
				
			||||||
 | 
					                props.controller.closeAll();
 | 
				
			||||||
 | 
					                core.restart();
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case MainChoice.Back: {
 | 
				
			||||||
 | 
					                props.controller.close(props.instance);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => (
 | 
				
			||||||
 | 
					        <Choices
 | 
				
			||||||
 | 
					            loc={props.loc}
 | 
				
			||||||
 | 
					            choices={choices}
 | 
				
			||||||
 | 
					            width={240}
 | 
				
			||||||
 | 
					            onChoose={choose}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}, settingsProps);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const enum ReplayChoice {
 | 
				
			||||||
 | 
					    Start,
 | 
				
			||||||
 | 
					    StartFromSave,
 | 
				
			||||||
 | 
					    ResumeReplay,
 | 
				
			||||||
 | 
					    ReplayRest,
 | 
				
			||||||
 | 
					    ChooseReplay,
 | 
				
			||||||
 | 
					    Download,
 | 
				
			||||||
 | 
					    Back
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ReplaySettings = defineComponent<SettingsProps>(props => {
 | 
				
			||||||
 | 
					    const choice: ChoiceItem[] = [
 | 
				
			||||||
 | 
					        [ReplayChoice.Start, '从头回放录像'],
 | 
				
			||||||
 | 
					        [ReplayChoice.StartFromSave, '从存档开始回放'],
 | 
				
			||||||
 | 
					        [ReplayChoice.ResumeReplay, '接续播放剩余录像'],
 | 
				
			||||||
 | 
					        [ReplayChoice.ReplayRest, '播放存档剩余录像'],
 | 
				
			||||||
 | 
					        [ReplayChoice.ChooseReplay, '选择录像文件'],
 | 
				
			||||||
 | 
					        [ReplayChoice.Download, '下载当前录像'],
 | 
				
			||||||
 | 
					        [ReplayChoice.Back, '返回游戏']
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const choose = (key: ChoiceKey) => {
 | 
				
			||||||
 | 
					        switch (key) {
 | 
				
			||||||
 | 
					            case ReplayChoice.Start: {
 | 
				
			||||||
 | 
					                props.controller.closeAll();
 | 
				
			||||||
 | 
					                core.ui.closePanel();
 | 
				
			||||||
 | 
					                const route = core.status.route.slice();
 | 
				
			||||||
 | 
					                const seed = core.getFlag<number>('__seed__');
 | 
				
			||||||
 | 
					                core.startGame(core.status.hard, seed, route);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case ReplayChoice.StartFromSave: {
 | 
				
			||||||
 | 
					                // todo
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case ReplayChoice.ResumeReplay: {
 | 
				
			||||||
 | 
					                // todo
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case ReplayChoice.ReplayRest: {
 | 
				
			||||||
 | 
					                // todo
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case ReplayChoice.ChooseReplay: {
 | 
				
			||||||
 | 
					                props.controller.closeAll();
 | 
				
			||||||
 | 
					                core.chooseReplayFile();
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case ReplayChoice.Download: {
 | 
				
			||||||
 | 
					                core.download(
 | 
				
			||||||
 | 
					                    core.firstData.name + '_' + core.formatDate2() + '.h5route',
 | 
				
			||||||
 | 
					                    // @ts-expect-error 暂时无法推导
 | 
				
			||||||
 | 
					                    LZString.compressToBase64(
 | 
				
			||||||
 | 
					                        JSON.stringify({
 | 
				
			||||||
 | 
					                            name: core.firstData.name,
 | 
				
			||||||
 | 
					                            hard: core.status.hard,
 | 
				
			||||||
 | 
					                            seed: core.getFlag('__seed__'),
 | 
				
			||||||
 | 
					                            route: core.encodeRoute(core.status.route)
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case ReplayChoice.Back: {
 | 
				
			||||||
 | 
					                props.controller.close(props.instance);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => (
 | 
				
			||||||
 | 
					        <Choices
 | 
				
			||||||
 | 
					            loc={props.loc}
 | 
				
			||||||
 | 
					            choices={choice}
 | 
				
			||||||
 | 
					            width={240}
 | 
				
			||||||
 | 
					            onChoose={choose}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}, settingsProps);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const enum GameInfoChoice {
 | 
				
			||||||
 | 
					    Statistics,
 | 
				
			||||||
 | 
					    Project,
 | 
				
			||||||
 | 
					    Tower,
 | 
				
			||||||
 | 
					    Help,
 | 
				
			||||||
 | 
					    Download,
 | 
				
			||||||
 | 
					    Back
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const GameInfo = defineComponent<SettingsProps>(props => {
 | 
				
			||||||
 | 
					    const choices: ChoiceItem[] = [
 | 
				
			||||||
 | 
					        [GameInfoChoice.Statistics, '数据统计'],
 | 
				
			||||||
 | 
					        [GameInfoChoice.Project, '查看工程'],
 | 
				
			||||||
 | 
					        [GameInfoChoice.Tower, '游戏主页'],
 | 
				
			||||||
 | 
					        [GameInfoChoice.Help, '操作帮助'],
 | 
				
			||||||
 | 
					        [GameInfoChoice.Download, '下载离线版本'],
 | 
				
			||||||
 | 
					        [GameInfoChoice.Back, '返回主菜单']
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const choose = async (key: ChoiceKey) => {
 | 
				
			||||||
 | 
					        switch (key) {
 | 
				
			||||||
 | 
					            case GameInfoChoice.Statistics: {
 | 
				
			||||||
 | 
					                // todo
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case GameInfoChoice.Project: {
 | 
				
			||||||
 | 
					                if (core.platform.isPC) window.open('editor.html', '_blank');
 | 
				
			||||||
 | 
					                else {
 | 
				
			||||||
 | 
					                    const confirm = await getConfirm(
 | 
				
			||||||
 | 
					                        props.controller,
 | 
				
			||||||
 | 
					                        '即将离开本游戏,跳转至工程页面,确认跳转?',
 | 
				
			||||||
 | 
					                        props.loc,
 | 
				
			||||||
 | 
					                        240
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    if (confirm) {
 | 
				
			||||||
 | 
					                        window.location.href = 'editor-mobile.html';
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case GameInfoChoice.Tower: {
 | 
				
			||||||
 | 
					                const name = core.firstData.name;
 | 
				
			||||||
 | 
					                const href = `/tower/?name=${name}`;
 | 
				
			||||||
 | 
					                if (core.platform.isPC) {
 | 
				
			||||||
 | 
					                    window.open(href, '_blank');
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    const confirm = await getConfirm(
 | 
				
			||||||
 | 
					                        props.controller,
 | 
				
			||||||
 | 
					                        '即将离开本游戏,跳转至评论页面,确认跳转?',
 | 
				
			||||||
 | 
					                        props.loc,
 | 
				
			||||||
 | 
					                        240
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    if (confirm) {
 | 
				
			||||||
 | 
					                        window.location.href = href;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case GameInfoChoice.Download: {
 | 
				
			||||||
 | 
					                const name = core.firstData.name;
 | 
				
			||||||
 | 
					                const href = `/games/${name}/${name}.zip`;
 | 
				
			||||||
 | 
					                if (core.platform.isPC) window.open(href);
 | 
				
			||||||
 | 
					                else window.location.href = href;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case GameInfoChoice.Help: {
 | 
				
			||||||
 | 
					                // todo
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case GameInfoChoice.Back: {
 | 
				
			||||||
 | 
					                props.controller.close(props.instance);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => (
 | 
				
			||||||
 | 
					        <Choices
 | 
				
			||||||
 | 
					            loc={props.loc}
 | 
				
			||||||
 | 
					            choices={choices}
 | 
				
			||||||
 | 
					            width={240}
 | 
				
			||||||
 | 
					            onChoose={choose}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const enum SyncSaveChoice {
 | 
				
			||||||
 | 
					    // ----- 主菜单
 | 
				
			||||||
 | 
					    ToServer,
 | 
				
			||||||
 | 
					    FromServer,
 | 
				
			||||||
 | 
					    ToLocal,
 | 
				
			||||||
 | 
					    FromLocal,
 | 
				
			||||||
 | 
					    ClearLocal,
 | 
				
			||||||
 | 
					    Back,
 | 
				
			||||||
 | 
					    // ----- 子菜单
 | 
				
			||||||
 | 
					    AllSaves,
 | 
				
			||||||
 | 
					    NowSave
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SyncSave = defineComponent<SettingsProps>(props => {
 | 
				
			||||||
 | 
					    const choices: ChoiceItem[] = [
 | 
				
			||||||
 | 
					        [SyncSaveChoice.ToServer, '同步存档至服务器'],
 | 
				
			||||||
 | 
					        [SyncSaveChoice.FromServer, '从服务器加载存档'],
 | 
				
			||||||
 | 
					        [SyncSaveChoice.ToLocal, '存档至本地文件'],
 | 
				
			||||||
 | 
					        [SyncSaveChoice.FromLocal, '存本地文件读档'],
 | 
				
			||||||
 | 
					        [SyncSaveChoice.ClearLocal, '清空本地存档'],
 | 
				
			||||||
 | 
					        [SyncSaveChoice.Back, '返回上一级']
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const choose = (key: ChoiceKey) => {
 | 
				
			||||||
 | 
					        switch (key) {
 | 
				
			||||||
 | 
					            case SyncSaveChoice.ToServer: {
 | 
				
			||||||
 | 
					                props.controller.open(SyncSaveSelectUI, { loc: props.loc });
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case SyncSaveChoice.FromServer: {
 | 
				
			||||||
 | 
					                // todo
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case SyncSaveChoice.ToLocal: {
 | 
				
			||||||
 | 
					                props.controller.open(DownloadSaveSelectUI, { loc: props.loc });
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case SyncSaveChoice.FromLocal: {
 | 
				
			||||||
 | 
					                // todo
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case SyncSaveChoice.ClearLocal: {
 | 
				
			||||||
 | 
					                props.controller.open(ClearSaveSelectUI, { loc: props.loc });
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case SyncSaveChoice.Back: {
 | 
				
			||||||
 | 
					                props.controller.close(props.instance);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => (
 | 
				
			||||||
 | 
					        <Choices
 | 
				
			||||||
 | 
					            loc={props.loc}
 | 
				
			||||||
 | 
					            width={240}
 | 
				
			||||||
 | 
					            choices={choices}
 | 
				
			||||||
 | 
					            onChoose={choose}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SyncSaveSelect = defineComponent<SettingsProps>(props => {
 | 
				
			||||||
 | 
					    const choices: ChoiceItem[] = [
 | 
				
			||||||
 | 
					        [SyncSaveChoice.AllSaves, '同步全部存档'],
 | 
				
			||||||
 | 
					        [SyncSaveChoice.NowSave, '同步当前存档'],
 | 
				
			||||||
 | 
					        [SyncSaveChoice.Back, '返回上一级']
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const choose = async (key: ChoiceKey) => {
 | 
				
			||||||
 | 
					        switch (key) {
 | 
				
			||||||
 | 
					            case SyncSaveChoice.AllSaves: {
 | 
				
			||||||
 | 
					                core.playSound('confirm.opus');
 | 
				
			||||||
 | 
					                const confirm = await getConfirm(
 | 
				
			||||||
 | 
					                    props.controller,
 | 
				
			||||||
 | 
					                    '你确定要同步全部存档么?这可能在存档较多的时候比较慢。',
 | 
				
			||||||
 | 
					                    props.loc,
 | 
				
			||||||
 | 
					                    240
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                if (confirm) {
 | 
				
			||||||
 | 
					                    core.syncSave('all');
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case SyncSaveChoice.NowSave: {
 | 
				
			||||||
 | 
					                core.playSound('confirm.opus');
 | 
				
			||||||
 | 
					                const confirm = await getConfirm(
 | 
				
			||||||
 | 
					                    props.controller,
 | 
				
			||||||
 | 
					                    '确定要同步当前存档吗?',
 | 
				
			||||||
 | 
					                    props.loc,
 | 
				
			||||||
 | 
					                    240
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                if (confirm) {
 | 
				
			||||||
 | 
					                    core.syncSave();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case SyncSaveChoice.Back: {
 | 
				
			||||||
 | 
					                props.controller.close(props.instance);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => (
 | 
				
			||||||
 | 
					        <Choices
 | 
				
			||||||
 | 
					            loc={props.loc}
 | 
				
			||||||
 | 
					            width={240}
 | 
				
			||||||
 | 
					            choices={choices}
 | 
				
			||||||
 | 
					            onChoose={choose}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DownloadSaveSelect = defineComponent<SettingsProps>(props => {
 | 
				
			||||||
 | 
					    const choices: ChoiceItem[] = [
 | 
				
			||||||
 | 
					        [SyncSaveChoice.AllSaves, '下载全部存档'],
 | 
				
			||||||
 | 
					        [SyncSaveChoice.NowSave, '下载当前存档'],
 | 
				
			||||||
 | 
					        [SyncSaveChoice.Back, '返回上一级']
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const choose = async (key: ChoiceKey) => {
 | 
				
			||||||
 | 
					        switch (key) {
 | 
				
			||||||
 | 
					            case SyncSaveChoice.AllSaves: {
 | 
				
			||||||
 | 
					                const confirm = await getConfirm(
 | 
				
			||||||
 | 
					                    props.controller,
 | 
				
			||||||
 | 
					                    '确认要下载所有存档吗?',
 | 
				
			||||||
 | 
					                    props.loc,
 | 
				
			||||||
 | 
					                    240
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                if (confirm) {
 | 
				
			||||||
 | 
					                    const data = await waitbox(
 | 
				
			||||||
 | 
					                        props.controller,
 | 
				
			||||||
 | 
					                        props.loc,
 | 
				
			||||||
 | 
					                        240,
 | 
				
			||||||
 | 
					                        getAllSavesData(),
 | 
				
			||||||
 | 
					                        { text: '请等待处理完毕' }
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    core.download(
 | 
				
			||||||
 | 
					                        `${core.firstData.name}_${core.formatDate2(new Date())}.h5save`,
 | 
				
			||||||
 | 
					                        data
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case SyncSaveChoice.NowSave: {
 | 
				
			||||||
 | 
					                const confirm = await getConfirm(
 | 
				
			||||||
 | 
					                    props.controller,
 | 
				
			||||||
 | 
					                    '确认要下载当前存档吗?',
 | 
				
			||||||
 | 
					                    props.loc,
 | 
				
			||||||
 | 
					                    240
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                if (confirm) {
 | 
				
			||||||
 | 
					                    const data = await getSaveData(core.saves.saveIndex);
 | 
				
			||||||
 | 
					                    core.download(
 | 
				
			||||||
 | 
					                        `${core.firstData.name}_${core.formatDate2(new Date())}.h5save`,
 | 
				
			||||||
 | 
					                        data
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case SyncSaveChoice.Back: {
 | 
				
			||||||
 | 
					                props.controller.close(props.instance);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => (
 | 
				
			||||||
 | 
					        <Choices
 | 
				
			||||||
 | 
					            loc={props.loc}
 | 
				
			||||||
 | 
					            width={240}
 | 
				
			||||||
 | 
					            choices={choices}
 | 
				
			||||||
 | 
					            onChoose={choose}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ClearSaveSelect = defineComponent<SettingsProps>(props => {
 | 
				
			||||||
 | 
					    const choices: ChoiceItem[] = [
 | 
				
			||||||
 | 
					        [SyncSaveChoice.AllSaves, '清空全部塔存档'],
 | 
				
			||||||
 | 
					        [SyncSaveChoice.NowSave, '清空当前塔存档'],
 | 
				
			||||||
 | 
					        [SyncSaveChoice.Back, '返回上一级']
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const choose = async (key: ChoiceKey) => {
 | 
				
			||||||
 | 
					        switch (key) {
 | 
				
			||||||
 | 
					            case SyncSaveChoice.AllSaves: {
 | 
				
			||||||
 | 
					                const confirm = await getConfirm(
 | 
				
			||||||
 | 
					                    props.controller,
 | 
				
			||||||
 | 
					                    '你确定要清除【全部游戏】的所有本地存档?此行为不可逆!!!',
 | 
				
			||||||
 | 
					                    props.loc,
 | 
				
			||||||
 | 
					                    240
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                if (confirm) {
 | 
				
			||||||
 | 
					                    await waitbox(
 | 
				
			||||||
 | 
					                        props.controller,
 | 
				
			||||||
 | 
					                        props.loc,
 | 
				
			||||||
 | 
					                        240,
 | 
				
			||||||
 | 
					                        new Promise<void>(res => {
 | 
				
			||||||
 | 
					                            core.clearLocalForage(() => {
 | 
				
			||||||
 | 
					                                core.saves.ids = {};
 | 
				
			||||||
 | 
					                                core.saves.autosave.data = null;
 | 
				
			||||||
 | 
					                                core.saves.autosave.updated = false;
 | 
				
			||||||
 | 
					                                core.saves.autosave.now = 0;
 | 
				
			||||||
 | 
					                                // @ts-expect-error 沙比样板
 | 
				
			||||||
 | 
					                                core.saves.cache = {};
 | 
				
			||||||
 | 
					                                core.saves.saveIndex = 1;
 | 
				
			||||||
 | 
					                                core.saves.favorite = [];
 | 
				
			||||||
 | 
					                                core.saves.favoriteName = {};
 | 
				
			||||||
 | 
					                                // @ts-expect-error 沙比样板
 | 
				
			||||||
 | 
					                                core.control._updateFavoriteSaves();
 | 
				
			||||||
 | 
					                                core.removeLocalStorage('saveIndex');
 | 
				
			||||||
 | 
					                                res();
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                        }),
 | 
				
			||||||
 | 
					                        { text: '正在情况,请稍后...' }
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    await getConfirm(
 | 
				
			||||||
 | 
					                        props.controller,
 | 
				
			||||||
 | 
					                        '所有塔的存档已经全部清空',
 | 
				
			||||||
 | 
					                        props.loc,
 | 
				
			||||||
 | 
					                        240
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case SyncSaveChoice.NowSave: {
 | 
				
			||||||
 | 
					                const confirm = await getConfirm(
 | 
				
			||||||
 | 
					                    props.controller,
 | 
				
			||||||
 | 
					                    '你确定要清除【当前游戏】的所有本地存档?此行为不可逆!!!',
 | 
				
			||||||
 | 
					                    props.loc,
 | 
				
			||||||
 | 
					                    240
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                if (confirm) {
 | 
				
			||||||
 | 
					                    await waitbox(
 | 
				
			||||||
 | 
					                        props.controller,
 | 
				
			||||||
 | 
					                        props.loc,
 | 
				
			||||||
 | 
					                        240,
 | 
				
			||||||
 | 
					                        new Promise<void>(res => {
 | 
				
			||||||
 | 
					                            Object.keys(core.saves.ids).forEach(function (v) {
 | 
				
			||||||
 | 
					                                core.removeLocalForage('save' + v);
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                            core.removeLocalForage('autoSave', () => {
 | 
				
			||||||
 | 
					                                core.saves.ids = {};
 | 
				
			||||||
 | 
					                                core.saves.autosave.data = null;
 | 
				
			||||||
 | 
					                                core.saves.autosave.updated = false;
 | 
				
			||||||
 | 
					                                core.saves.autosave.now = 0;
 | 
				
			||||||
 | 
					                                core.ui.closePanel();
 | 
				
			||||||
 | 
					                                core.saves.saveIndex = 1;
 | 
				
			||||||
 | 
					                                core.saves.favorite = [];
 | 
				
			||||||
 | 
					                                core.saves.favoriteName = {};
 | 
				
			||||||
 | 
					                                // @ts-expect-error 沙比样板
 | 
				
			||||||
 | 
					                                core.control._updateFavoriteSaves();
 | 
				
			||||||
 | 
					                                core.removeLocalStorage('saveIndex');
 | 
				
			||||||
 | 
					                                res();
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                        }),
 | 
				
			||||||
 | 
					                        { text: '正在情况,请稍后...' }
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    await getConfirm(
 | 
				
			||||||
 | 
					                        props.controller,
 | 
				
			||||||
 | 
					                        '当前塔的存档已被清空',
 | 
				
			||||||
 | 
					                        props.loc,
 | 
				
			||||||
 | 
					                        240
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case SyncSaveChoice.Back: {
 | 
				
			||||||
 | 
					                props.controller.close(props.instance);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => (
 | 
				
			||||||
 | 
					        <Choices
 | 
				
			||||||
 | 
					            loc={props.loc}
 | 
				
			||||||
 | 
					            width={240}
 | 
				
			||||||
 | 
					            choices={choices}
 | 
				
			||||||
 | 
					            onChoose={choose}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** @see {@link MainSettings} */
 | 
				
			||||||
 | 
					export const MainSettingsUI = new GameUI('main-settings', MainSettings);
 | 
				
			||||||
 | 
					/** @see {@link ReplaySettings} */
 | 
				
			||||||
 | 
					export const ReplaySettingsUI = new GameUI('replay-settings', ReplaySettings);
 | 
				
			||||||
 | 
					/** @see {@link GameInfo} */
 | 
				
			||||||
 | 
					export const GameInfoUI = new GameUI('game-info', GameInfo);
 | 
				
			||||||
 | 
					/** @see {@link SyncSave} */
 | 
				
			||||||
 | 
					export const SyncSaveUI = new GameUI('sync-save', SyncSave);
 | 
				
			||||||
 | 
					/** @see {@link SyncSaveSelect} */
 | 
				
			||||||
 | 
					export const SyncSaveSelectUI = new GameUI('sync-save-select', SyncSaveSelect);
 | 
				
			||||||
 | 
					/** @see {@link DownloadSaveSelect} */
 | 
				
			||||||
 | 
					export const DownloadSaveSelectUI = new GameUI(
 | 
				
			||||||
 | 
					    'download-save-select',
 | 
				
			||||||
 | 
					    DownloadSaveSelect
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					/** @see {@link ClearSaveSelect} */
 | 
				
			||||||
 | 
					export const ClearSaveSelectUI = new GameUI(
 | 
				
			||||||
 | 
					    'clear-save-select',
 | 
				
			||||||
 | 
					    ClearSaveSelect
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { GameUI } from '@/core/system';
 | 
					import { GameUI, UIComponentProps } from '@/core/system';
 | 
				
			||||||
import { computed, defineComponent, ref, watch } from 'vue';
 | 
					import { computed, defineComponent, ref, watch } from 'vue';
 | 
				
			||||||
import { SetupComponentOptions, TextContent } from '../components';
 | 
					import { SetupComponentOptions, TextContent } from '../components';
 | 
				
			||||||
import { DefaultProps, ElementLocator, Sprite, Font } from '@/core/render';
 | 
					import { DefaultProps, ElementLocator, Sprite, Font } from '@/core/render';
 | 
				
			||||||
@ -34,7 +34,7 @@ export interface ILeftHeroStatus {
 | 
				
			|||||||
    magicDef: number;
 | 
					    magicDef: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface StatusBarProps<T> extends DefaultProps {
 | 
					interface StatusBarProps<T> extends DefaultProps, UIComponentProps {
 | 
				
			||||||
    loc: ElementLocator;
 | 
					    loc: ElementLocator;
 | 
				
			||||||
    status: T;
 | 
					    status: T;
 | 
				
			||||||
    hidden: boolean;
 | 
					    hidden: boolean;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								src/module/utils/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/module/utils/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './saves';
 | 
				
			||||||
							
								
								
									
										35
									
								
								src/module/utils/saves.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/module/utils/saves.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					export function getAllSavesData() {
 | 
				
			||||||
 | 
					    return new Promise<string>(res => {
 | 
				
			||||||
 | 
					        core.getAllSaves(saves => {
 | 
				
			||||||
 | 
					            if (!saves) {
 | 
				
			||||||
 | 
					                res('');
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const content = {
 | 
				
			||||||
 | 
					                name: core.firstData.name,
 | 
				
			||||||
 | 
					                version: core.firstData.version,
 | 
				
			||||||
 | 
					                data: saves
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            // @ts-expect-error 暂时无法推导
 | 
				
			||||||
 | 
					            res(LZString.compressToBase64(JSON.stringify(content)));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getSaveData(index: number) {
 | 
				
			||||||
 | 
					    return new Promise<string>(res => {
 | 
				
			||||||
 | 
					        core.getSave(index, data => {
 | 
				
			||||||
 | 
					            if (!data) {
 | 
				
			||||||
 | 
					                res('');
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const content = {
 | 
				
			||||||
 | 
					                name: core.firstData.name,
 | 
				
			||||||
 | 
					                version: core.firstData.version,
 | 
				
			||||||
 | 
					                data: data
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            // @ts-expect-error 暂时无法推导
 | 
				
			||||||
 | 
					            res(LZString.compressToBase64(JSON.stringify(content)));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/types/core.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								src/types/core.d.ts
									
									
									
									
										vendored
									
									
								
							@ -657,7 +657,7 @@ interface CoreSave {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 自动存档信息
 | 
					     * 自动存档信息
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    autosave: Readonly<Autosave>;
 | 
					    autosave: Autosave;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 收藏的存档
 | 
					     * 收藏的存档
 | 
				
			||||||
@ -679,7 +679,7 @@ interface Autosave {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 当前存档信息
 | 
					     * 当前存档信息
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    data?: Save[];
 | 
					    data?: Save[] | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 自动存档位的最大值
 | 
					     * 自动存档位的最大值
 | 
				
			||||||
@ -982,7 +982,7 @@ interface Core extends Pick<Main, CoreDataFromMain> {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 存档信息
 | 
					     * 存档信息
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    readonly saves: Readonly<CoreSave>;
 | 
					    readonly saves: CoreSave;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 全局数值信息
 | 
					     * 全局数值信息
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								src/types/event.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/types/event.d.ts
									
									
									
									
										vendored
									
									
								
							@ -46,7 +46,7 @@ interface Events extends EventData {
 | 
				
			|||||||
    startGame(
 | 
					    startGame(
 | 
				
			||||||
        hard: string,
 | 
					        hard: string,
 | 
				
			||||||
        seed?: number,
 | 
					        seed?: number,
 | 
				
			||||||
        route?: string,
 | 
					        route?: string[],
 | 
				
			||||||
        callback?: () => void
 | 
					        callback?: () => void
 | 
				
			||||||
    ): void;
 | 
					    ): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user