Commit c4365854 authored by timel's avatar timel

feat: 抽离Dropdown,提升性能

parent 2805ab54
Pipeline #25709 failed with stages
in 14 minutes and 20 seconds
......@@ -14,8 +14,8 @@ const getWhom = (controller: number): "me" | "op" =>
isMe(controller) ? "me" : "op";
/**
* 根据自己的先后手判断是否是自己
* 原本名字叫judgeSelf
* 根据 controller 判断是否是自己,
* 入参可以是 currentPlayer 或者 card.location.controller
*/
export const isMe = (controller: number): boolean => {
switch (matStore.selfType) {
......
......@@ -18,7 +18,7 @@ import {
SortCardModal,
YesNoModal,
} from "./Message";
import { LifeBar, Mat, Menu, Underlying } from "./PlayMat";
import { LifeBar, Mat, Menu, Underlying, CardDropdown } from "./PlayMat";
import { ChatBox } from "./PlayMat/ChatBox";
export const Component: React.FC = () => {
......@@ -59,6 +59,7 @@ export const Component: React.FC = () => {
<SimpleSelectCardsModal />
<EndModal />
<ChatBox />
<CardDropdown />
</>
);
};
......
import { animated, to, useSpring } from "@react-spring/web";
import { Dropdown, type MenuProps } from "antd";
import classnames from "classnames";
import React, { type CSSProperties, useEffect, useRef, useState } from "react";
import { useSnapshot } from "valtio";
......@@ -12,9 +11,20 @@ import {
ygopro,
} from "@/api";
import { eventbus, Task } from "@/infra";
import { cardStore, CardType, Interactivity, InteractType } from "@/stores";
import {
cardStore,
CardType,
Interactivity,
InteractType,
isMe,
} from "@/stores";
import { showCardModal as displayCardModal } from "@/ui/Duel/Message/CardModal";
import { YgoCard } from "@/ui/Shared";
import {
showCardDropdown,
hideCardDropdown,
type DropdownItem,
} from "@/ui/Duel/PlayMat/Dropdown";
import { YgoCard, matConfig } from "@/ui/Shared";
import {
displayCardListModal,
......@@ -34,6 +44,8 @@ import type { SpringApiProps } from "./springs/types";
const { HAND, GRAVE, REMOVED, EXTRA, MZONE, SZONE, TZONE } = ygopro.CardZone;
const { CARD_RATIO } = matConfig;
export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
const card = cardStore.inner[idx];
const snap = useSnapshot(card);
......@@ -47,6 +59,7 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
rx: 0,
ry: 0,
rz: 0,
rd: 0, // 围绕对角线的旋转
zIndex: 0,
height: 0,
focusScale: 1,
......@@ -113,13 +126,6 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
);
}, [idleInteractivities]);
const [dropdownMenu, setDropdownMenu] = useState({
items: [] as DropdownItem[],
});
// 是否禁用下拉菜单
const [dropdownMenuDisabled, setDropdownMenuDisabled] = useState(false);
// 发动效果
// 1. 下拉菜单里面选择[召唤 / 特殊召唤 /.../效果发动]
// 2. 如果是非效果发动,那么直接选择哪张卡(单张卡直接选择那张)
......@@ -136,10 +142,8 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
});
if (!map.size) {
setDropdownMenuDisabled(true);
hideCardDropdown();
return;
} else {
setDropdownMenuDisabled(false);
}
const actions = [...map.entries()];
const nonEffectActions = actions.filter(
......@@ -228,12 +232,16 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
);
},
};
setDropdownMenu({
const current = api.current[0].get();
showCardDropdown({
items: [...nonEffectItem, ...(hasEffect ? [effectItem] : [])],
x: current.x,
y: current.y - current.height / 2 - 10, // 10px 的 gap
});
};
const onClick = () => {
const onClick = (e: any) => {
e.stopPropagation();
const onCardClick = (card: CardType) => {
// 中央弹窗展示选中卡牌信息
// TODO: 同一张卡片,是否重复点击会关闭CardModal?
......@@ -271,6 +279,15 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
onFieldClick(card);
}
};
/** 鼠标hover在手卡上的效果 不确定 先放着 */
const onMouse = (isEnter: boolean) => () => {
// if (isMe(card.location.controller) && card.location.zone === HAND) {
// api.start({
// rd: isEnter ? 15 : 0,
// });
// }
};
// <<< 效果 <<<
return (
......@@ -279,9 +296,9 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
style={
{
transform: to(
[spring.x, spring.y, spring.z, spring.rx, spring.ry, spring.rz],
(x, y, z, rx, ry, rz) =>
`translate(${x}px, ${y}px) rotateX(${rx}deg) rotateZ(${rz}deg)`,
[spring.x, spring.y, spring.rx, spring.rz, spring.rd],
(x, y, rx, rz, rd) =>
`translate(${x}px, ${y}px) rotateX(${rx}deg) rotateZ(${rz}deg) rotate3d(${CARD_RATIO}, 1, 0, ${rd}deg)`,
),
"--z": spring.z,
"--sub-z": spring.subZ.to([0, 50, 100], [0, 200, 0]), // 中间高,两边低
......@@ -295,31 +312,23 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
} as any as CSSProperties
}
onClick={onClick}
onMouseEnter={onMouse(true)}
onMouseLeave={onMouse(false)}
>
<div className={styles.focus} />
<div className={styles.shadow} />
<Dropdown
menu={dropdownMenu}
placement="top"
overlayClassName={classnames(styles.dropdown, {
[styles["dropdown-disabled"]]: dropdownMenuDisabled,
<div
className={classnames(styles["img-wrap"], {
[styles.focusing]: classFocus,
})}
arrow
trigger={["click"]}
>
<div
className={classnames(styles["img-wrap"], {
[styles.focusing]: classFocus,
})}
>
<YgoCard
className={styles.cover}
// cardName={snap.meta.text.name}
code={snap.code === 0 ? snap.meta.id : snap.code}
/>
<YgoCard className={styles.back} isBack />
</div>
</Dropdown>
<YgoCard
className={styles.cover}
// cardName={snap.meta.text.name}
code={snap.code === 0 ? snap.meta.id : snap.code}
/>
<YgoCard className={styles.back} isBack />
</div>
{snap.selected ? <div className={styles.streamer} /> : <></>}
</animated.div>
);
......@@ -332,10 +341,6 @@ interface Interactivy {
effectCode: number | undefined;
}
type DropdownItem = NonNullable<MenuProps["items"]>[number] & {
onClick: () => void;
};
const handleEffectActivation = (
effectInteractivies: Interactivy[],
meta?: CardMeta,
......
......@@ -10,6 +10,7 @@ export interface SpringApiProps {
rx: number;
ry: number;
rz: number;
rd: number, // 围绕对角线的旋转
zIndex: number;
height: number;
opacity: number;
......
import { Dropdown, type MenuProps } from "antd";
import { proxy, useSnapshot, ref } from "valtio";
export type DropdownItem = NonNullable<MenuProps["items"]>[number] & {
onClick: () => void;
};
const defaultProps = {
isOpen: false,
menu: {
items: ref([]) as DropdownItem[],
},
x: 0,
y: 0,
};
const localStore = proxy(defaultProps);
/** 想让下拉菜单全局唯一 */
export const CardDropdown: React.FC = () => {
const { isOpen, menu, x, y } = useSnapshot(localStore);
return (
<Dropdown
open
menu={menu as any}
overlayStyle={{
position: "fixed",
left: "50%",
top: "50%",
transform: `translate(-50%, -100%) translate3d(${x}px, ${y}px, 0)`,
opacity: isOpen ? 1 : 0, // 不能直接用在open属性上,否则会造成渲染不及时的问题
}}
placement="top"
autoAdjustOverflow={false}
arrow
>
<div style={{ width: 0, height: 0 }} />
</Dropdown>
);
};
export function showCardDropdown(props: {
items: DropdownItem[];
x: number;
y: number;
}) {
localStore.x = props.x;
localStore.y = props.y;
localStore.menu = {
items: props.items.map((x) => {
const tmpOnClick = x.onClick;
x.onClick = () => {
tmpOnClick();
localStore.isOpen = false;
};
return x;
}),
};
localStore.isOpen = true;
}
export function hideCardDropdown() {
localStore.isOpen = false;
}
// 全局点击收起
document.addEventListener("click", () => {
localStore.isOpen = false;
});
......@@ -2,3 +2,4 @@ export * from "./LifeBar";
export * from "./Mat";
export * from "./Menu";
export * from "./Underlying";
export * from "./Dropdown";
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