Commit c4c2246c authored by timel's avatar timel

optimize: card drapdown menu store

parent 2e712f62
Pipeline #22576 passed with stages
in 13 minutes and 28 seconds
......@@ -4,8 +4,9 @@ import { animated, to, useSpring } from "@react-spring/web";
import { Dropdown, type MenuProps } from "antd";
import classnames from "classnames";
import React, { type CSSProperties, useEffect, useState } from "react";
import { proxy, useSnapshot } from "valtio";
import { useSnapshot } from "valtio";
import type { CardMeta } from "@/api";
import {
fetchStrings,
getCardStr,
......@@ -139,131 +140,16 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
useEffect(() => {
setHighlight(!!idleInteractivities.length);
}, [idleInteractivities]);
const menuItems = useSnapshot(dropdownMenu);
// <<< 效果 <<<
return (
<animated.div
className={classnames("mat-card", { highlight })}
style={
{
transform: to(
[styles.x, styles.y, styles.z, styles.rx, styles.ry, styles.rz],
(x, y, z, rx, ry, rz) =>
`translate(${x}px, ${y}px) rotateX(${rx}deg) rotateZ(${rz}deg)`
),
"--z": styles.z,
"--ry": styles.ry,
height: styles.height,
zIndex: styles.zIndex,
"--focus-scale": styles.focusScale,
"--focus-display": styles.focusDisplay,
"--focus-opacity": styles.focusOpacity,
} as any as CSSProperties
}
onClick={() => {
if ([MZONE, SZONE, HAND].includes(state.location.zone)) {
onCardClick(state);
} else if ([EXTRA, GRAVE, REMOVED].includes(state.location.zone)) {
onFieldClick(state);
}
}}
>
<div className="card-focus" />
<div className="card-shadow" />
<Dropdown
menu={{ items: menuItems.value as any }}
placement="bottom"
overlayClassName="card-dropdown"
arrow
trigger={["click"]}
// disabled={!highlight}
>
<div className={classnames("card-img-wrap", { focus: classFocus })}>
<YgoCard
className={classnames("card-cover")}
code={snap.code === 0 ? snap.meta.id : snap.code}
/>
<YgoCard className="card-back" isBack />
</div>
</Dropdown>
{snap.selected ? <div className="card-streamer" /> : <></>}
</animated.div>
);
});
const onCardClick = (card: CardType) => {
// 中央弹窗展示选中卡牌信息
// TODO: 同一张卡片,是否重复点击会关闭CardModal?
displayCardModal(card);
if (card.idleInteractivities) handleDropdownMenu([card], false);
// 侧边栏展示超量素材信息
const overlayMaterials = cardStore.findOverlay(
card.location.zone,
card.location.controller,
card.location.sequence
);
if (overlayMaterials.length > 0) {
displayCardListModal({
isZone: false,
monster: card,
});
}
};
const onFieldClick = (card: CardType) => {
displayCardListModal({
isZone: true,
zone: card.location.zone,
controller: card.location.controller,
const [dropdownMenu, setDropdownMenu] = useState({
items: [] as DropdownItem[],
});
// 收集这个zone的所有交互,并且在下拉菜单之中显示
const cards = cardStore.at(card.location.zone, card.location.controller);
handleDropdownMenu(cards, true);
};
// >>> 下拉菜单:点击动作 >>>
interface Interactivy {
desc: string;
response: number;
effectCode: number | undefined;
}
type DropdownItem = NonNullable<MenuProps["items"]>[number] & {
onClick: () => void;
};
const dropdownMenu = proxy({ value: [] as DropdownItem[] });
const handleEffectActivation = (
effectInteractivies: Interactivy[],
meta?: any // FIXME: meta的类型
) => {
if (!effectInteractivies.length) return;
else if (effectInteractivies.length === 1) {
// 如果只有一个效果,点击直接触发
sendSelectIdleCmdResponse(effectInteractivies[0].response);
} else {
// optionsModal
const options = effectInteractivies.map((effect) => {
const effectMsg =
meta && effect.effectCode
? getCardStr(meta, effect.effectCode & 0xf) ?? "[:?]"
: "[:?]";
return {
msg: effectMsg,
response: effect.response,
};
});
displayOptionModal(fetchStrings("!system", 556), options); // 主动发动效果,所以不需要await,但是以后可能要留心
}
};
// 发动效果
// 1. 下拉菜单里面选择[召唤 / 特殊召唤 /.../效果发动]
// 2. 如果是非效果发动,那么直接选择哪张卡(单张卡直接选择那张)
// 3. 如果是效果发动,那么选择哪张卡,然后选择效果
const handleDropdownMenu = (cards: CardType[], isField: boolean) => {
// 发动效果
// 1. 下拉菜单里面选择[召唤 / 特殊召唤 /.../效果发动]
// 2. 如果是非效果发动,那么直接选择哪张卡(单张卡直接选择那张)
// 3. 如果是效果发动,那么选择哪张卡,然后选择效果
const handleDropdownMenu = (cards: CardType[], isField: boolean) => {
const map = new Map<Interactivity<number>["interactType"], CardType[]>();
cards.forEach((card) => {
card.idleInteractivities.forEach(({ interactType }) => {
......@@ -345,7 +231,9 @@ const handleDropdownMenu = (cards: CardType[], isField: boolean) => {
// 选择发动哪个效果
handleEffectActivation(
card.idleInteractivities
.filter(({ interactType }) => interactType === InteractType.ACTIVATE)
.filter(
({ interactType }) => interactType === InteractType.ACTIVATE
)
.map((x) => ({
desc: interactTypeToString(x.interactType),
response: x.response,
......@@ -355,7 +243,128 @@ const handleDropdownMenu = (cards: CardType[], isField: boolean) => {
);
},
};
dropdownMenu.value = nonEffectItem;
hasEffect && dropdownMenu.value.push(effectItem);
const items = [...nonEffectItem];
hasEffect && items.push(effectItem);
setDropdownMenu({ items });
};
const onClick = () => {
const onCardClick = (card: CardType) => {
// 中央弹窗展示选中卡牌信息
// TODO: 同一张卡片,是否重复点击会关闭CardModal?
displayCardModal(card);
if (card.idleInteractivities.length) handleDropdownMenu([card], false);
// 侧边栏展示超量素材信息
const overlayMaterials = cardStore.findOverlay(
card.location.zone,
card.location.controller,
card.location.sequence
);
if (overlayMaterials.length > 0) {
displayCardListModal({
isZone: false,
monster: card,
});
}
};
const onFieldClick = (card: CardType) => {
displayCardListModal({
isZone: true,
zone: card.location.zone,
controller: card.location.controller,
});
// 收集这个zone的所有交互,并且在下拉菜单之中显示
const cards = cardStore.at(card.location.zone, card.location.controller);
handleDropdownMenu(cards, true);
};
if ([MZONE, SZONE, HAND].includes(state.location.zone)) {
onCardClick(state);
} else if ([EXTRA, GRAVE, REMOVED].includes(state.location.zone)) {
onFieldClick(state);
}
};
// <<< 效果 <<<
return (
<animated.div
className={classnames("mat-card", { highlight })}
style={
{
transform: to(
[styles.x, styles.y, styles.z, styles.rx, styles.ry, styles.rz],
(x, y, z, rx, ry, rz) =>
`translate(${x}px, ${y}px) rotateX(${rx}deg) rotateZ(${rz}deg)`
),
"--z": styles.z,
"--ry": styles.ry,
height: styles.height,
zIndex: styles.zIndex,
"--focus-scale": styles.focusScale,
"--focus-display": styles.focusDisplay,
"--focus-opacity": styles.focusOpacity,
} as any as CSSProperties
}
onClick={onClick}
>
<div className="card-focus" />
<div className="card-shadow" />
<Dropdown
menu={dropdownMenu}
placement="bottom"
overlayClassName="card-dropdown"
arrow
trigger={["click"]}
// disabled={!highlight} // TODO: 这里的disable要考虑到field的情况,比如额外卡组
>
<div className={classnames("card-img-wrap", { focus: classFocus })}>
<YgoCard
className={classnames("card-cover")}
code={snap.code === 0 ? snap.meta.id : snap.code}
/>
<YgoCard className="card-back" isBack />
</div>
</Dropdown>
{snap.selected ? <div className="card-streamer" /> : <></>}
</animated.div>
);
});
// >>> 下拉菜单:点击动作 >>>
interface Interactivy {
desc: string;
response: number;
effectCode: number | undefined;
}
type DropdownItem = NonNullable<MenuProps["items"]>[number] & {
onClick: () => void;
};
const handleEffectActivation = (
effectInteractivies: Interactivy[],
meta?: CardMeta
) => {
if (!effectInteractivies.length) return;
else if (effectInteractivies.length === 1) {
// 如果只有一个效果,点击直接触发
sendSelectIdleCmdResponse(effectInteractivies[0].response);
} else {
// optionsModal
const options = effectInteractivies.map((effect) => {
const effectMsg =
meta && effect.effectCode
? getCardStr(meta, effect.effectCode & 0xf) ?? "[:?]"
: "[:?]";
return {
msg: effectMsg,
response: effect.response,
};
});
displayOptionModal(fetchStrings("!system", 556), options); // 主动发动效果,所以不需要await,但是以后可能要留心
}
};
// <<< 下拉菜单 <<<
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment