Commit b2ac11db authored by timel's avatar timel Committed by Chunchi Che

feat: card list modal

parent e1115001
......@@ -47,16 +47,14 @@ export default async (move: MsgMove) => {
}
}
// log出来看看,后期删掉即可
await (async () => {
console.color("green")(
`${meta.text.name} ${ygopro.CardZone[from.zone]}:${from.sequence}:${
from.is_overlay ? from.overlay_sequence : ""
}${ygopro.CardZone[to.zone]}:${to.sequence}:${
to.is_overlay ? to.overlay_sequence : ""
}`
);
})();
// log出来看看
console.color("green")(
`${meta.text.name} ${ygopro.CardZone[from.zone]}:${from.sequence}${
from.is_overlay ? ":" + from.overlay_sequence : ""
}${ygopro.CardZone[to.zone]}:${to.sequence}${
to.is_overlay ? ":" + to.overlay_sequence : ""
}`
);
let target: CardType;
......
......@@ -35,9 +35,9 @@ const helper = async (
const effectDesc = effectDescCode
? getCardStr(meta, effectDescCode & 0xf)
: undefined;
const newOption = {
const newOption: Option = {
meta,
location: location.toObject(),
location,
level1,
level2,
effectDesc,
......
......@@ -49,8 +49,8 @@ class CardStore {
card.location.zone === zone &&
card.location.controller === controller &&
card.location.sequence === sequence &&
card.location.is_overlay == true &&
card.location.overlay_sequence == overlay_sequence
card.location.is_overlay === true &&
card.location.overlay_sequence === overlay_sequence
)
.at(0);
} else {
......@@ -60,7 +60,7 @@ class CardStore {
card.location.zone === zone &&
card.location.controller === controller &&
card.location.sequence === sequence &&
card.location.is_overlay == false
card.location.is_overlay === false
)
.at(0);
}
......@@ -69,7 +69,7 @@ class CardStore {
(card) =>
card.location.zone === zone &&
card.location.controller === controller &&
card.location.is_overlay == false
card.location.is_overlay === false
);
}
}
......@@ -84,9 +84,9 @@ class CardStore {
): CardType[] {
return this.inner.filter(
(card) =>
card.location.zone == zone &&
card.location.controller == controller &&
card.location.sequence == sequence &&
card.location.zone === zone &&
card.location.controller === controller &&
card.location.sequence === sequence &&
card.location.is_overlay
);
}
......
import { Drawer, List } from "antd";
import { Drawer, Space } from "antd";
import React from "react";
import { useSnapshot } from "valtio";
import { proxy, useSnapshot } from "valtio";
import { useConfig } from "@/config";
import { messageStore } from "@/stores";
import { EffectButton } from "./EffectButton";
import { CardType, messageStore, cardStore } from "@/stores";
import { showCardModal } from "./CardModal";
import { ygopro } from "@/api";
import { YgoCard } from "@/ui/Shared";
const NeosConfig = useConfig();
......@@ -15,50 +15,69 @@ const CARD_WIDTH = 100;
const { cardListModal } = messageStore;
// TODO: 显示的位置还需要细细斟酌
const defaultStore = {
zone: ygopro.CardZone.HAND,
controller: 0,
monster: {} as CardType,
isOpen: false,
isZone: true,
};
const store = proxy(defaultStore);
export const CardListModal = () => {
const snap = useSnapshot(cardListModal);
const isOpen = snap.isOpen;
const list = snap.list as typeof cardListModal.list;
const { zone, monster, isOpen, isZone, controller } = useSnapshot(store);
let cardList: CardType[] = [];
if (isZone) {
cardList = cardStore.at(zone, controller);
console.log({ cardList });
} else {
// 看超量素材
cardList = cardStore.findOverlay(
monster.location.zone,
monster.location.controller,
monster.location.sequence
);
}
const handleOkOrCancel = () => {
cardListModal.isOpen = false;
store.isOpen = false;
};
return (
<Drawer open={isOpen} onClose={handleOkOrCancel}>
<List
itemLayout="horizontal"
dataSource={list}
renderItem={(item) => (
<List.Item
actions={[
<EffectButton
effectInteractivies={item.interactivies}
meta={item.meta}
/>,
]}
extra={
<img
alt={item.meta?.text.name}
src={
item.meta?.id
? `${NeosConfig.cardImgUrl}/${item.meta.id}.jpg`
: `${NeosConfig.assetsPath}/card_back.jpg`
}
style={{ width: CARD_WIDTH }}
/>
}
onClick={() => {
showCardModal(item);
}}
>
<List.Item.Meta
title={item.meta?.text.name}
description={item.meta?.text.desc}
/>
</List.Item>
)}
></List>
<Drawer
open={isOpen}
onClose={handleOkOrCancel}
headerStyle={{ display: "none" }}
width={CARD_WIDTH + 66}
style={{ maxHeight: "100%" }}
>
<Space direction="vertical">
{cardList.map((card) => (
<YgoCard
code={card.code}
key={card.uuid}
width={CARD_WIDTH}
onClick={() => showCardModal(card)}
/>
))}
</Space>
</Drawer>
);
};
export const displayCardListModal = ({
isZone,
monster,
zone,
controller,
}: Partial<Omit<typeof defaultStore, "isOpen">>) => {
store.isOpen = true;
isZone && (store.isZone = isZone);
monster && (store.monster = monster);
zone && (store.zone = zone);
controller && (store.controller = controller);
};
......@@ -14,7 +14,6 @@ import {
Race2StringCodeMap,
Type2StringCodeMap,
} from "../../../../common";
import { EffectButton } from "../EffectButton";
import { Drawer, Space, Tag, Divider, Timeline, Typography } from "antd";
import { LeftOutlined } from "@ant-design/icons";
......@@ -61,13 +60,6 @@ export const CardModal = () => {
const atk = meta?.data.atk;
const def = meta?.data.def;
const nonEffectInteractivies = snap.interactivies.filter(
(item) => item.desc != "发动效果"
);
const effectInteractivies = snap.interactivies.filter(
(item) => item.desc == "发动效果"
);
return (
// TODO: 宽度要好好设置 根据屏幕宽度
<Drawer
......
import "@/styles/card-modal.scss";
import React from "react";
import { CardMeta, getCardStr, sendSelectIdleCmdResponse } from "@/api";
import { cardStore, messageStore } from "@/stores";
const { cardModal } = messageStore;
export const EffectButton = (props: {
meta?: CardMeta;
effectInteractivies: {
desc: string;
response: number;
effectCode?: number;
}[];
}) => (
<>
{props.effectInteractivies.length > 0 ? (
props.effectInteractivies.length == 1 ? (
// 如果只有一个效果,点击直接触发
<button
className="card-modal-btn"
onClick={() => {
sendSelectIdleCmdResponse(props.effectInteractivies[0].response);
cardModal.isOpen = false;
// 清空互动性
for (const card of cardStore.inner) {
card.idleInteractivities = [];
}
}}
>
{props.effectInteractivies[0].desc}
</button>
) : (
// 如果有多个效果,点击后进入`OptionModal`选择
<button
className="card-modal-btn"
onClick={() => {
for (const effect of props.effectInteractivies) {
const effectMsg =
props.meta && effect.effectCode
? getCardStr(props.meta, effect.effectCode & 0xf) ?? "[:?]"
: "[:?]";
messageStore.optionModal.options.push({
msg: effectMsg,
response: effect.response,
});
}
cardModal.isOpen = false;
// 清空互动性
for (const card of cardStore.inner) {
card.idleInteractivities = [];
}
messageStore.optionModal.isOpen = true;
}}
>
发动效果
</button>
)
) : (
<></>
)}
</>
);
......@@ -3,7 +3,13 @@ import { Button } from "antd";
import React, { useState } from "react";
import { useSnapshot, proxy } from "valtio";
import { sendSelectOptionResponse } from "@/api";
import {
type CardMeta,
sendSelectOptionResponse,
ygopro,
sendSelectIdleCmdResponse,
getCardStr,
} from "@/api";
import { NeosModal } from "./NeosModal";
type Options = { msg: string; response: number }[];
......@@ -54,3 +60,33 @@ export const displayOptionModal = async (options: Options) => {
await new Promise((resolve) => (rs = resolve));
store.isOpen = false;
};
export const handleEffectActivation = async (
meta: CardMeta,
effectInteractivies: {
desc: string;
response: number;
effectCode: number | undefined;
}[]
) => {
if (!effectInteractivies.length) {
return;
}
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,
};
});
await displayOptionModal(options); // 主动发动效果,所以不需要await,但是以后可能要留心
}
};
......@@ -31,7 +31,7 @@ import {
UpOutlined,
} from "@ant-design/icons";
import { fetchStrings, sendSelectIdleCmdResponse, type CardMeta } from "@/api";
import { displayOptionModal } from "../../Message";
import { displayCardListModal, displayOptionModal } from "../../Message";
const NeosConfig = useConfig();
const { HAND, GRAVE, REMOVED, DECK, EXTRA, MZONE, SZONE, TZONE } =
......@@ -233,6 +233,7 @@ export const Card: FC<{ idx: number }> = React.memo(({ idx }) => {
overlayClassName="card-dropdown"
arrow
trigger={["click"]}
// TODO: 没有交互效果、或者不能点击的卡,不应该显示下拉菜单
>
<div className={classnames("card-img-wrap", { focus: classFocus })}>
<YgoCard
......@@ -274,20 +275,10 @@ const onCardClick = (card: CardType) => {
};
const onFieldClick = (card: CardType) => {
const displayStates = cardStore.at(
card.location.zone,
card.location.controller
);
messageStore.cardListModal.list = displayStates.map((item) => ({
meta: {
id: item.code,
text: item.meta.text,
data: item.meta.data,
},
interactivies: item.idleInteractivities.map((interactivy) => ({
desc: interactTypeToString(interactivy.interactType),
response: interactivy.response,
})),
}));
messageStore.cardListModal.isOpen = true;
displayCardListModal({
isZone: true,
zone: card.location.zone,
controller: card.location.controller,
});
};
......@@ -17,7 +17,6 @@ export const Mat: FC = () => {
id="mat"
style={{
width: "100%",
...toCssProperties(matConfig),
}}
>
<Plane>
......
......@@ -5,13 +5,17 @@ export type CSSConfig = Record<string, { value: number; unit: UNIT }>;
/** 转为CSS变量: BOARD_ROTATE_Z -> --board-rotate-z */
export const toCssProperties = (config: CSSConfig) =>
Object.entries(config)
.map(([k, v]) => ({
[`--${k
.split("_")
.map((s) => s.toLowerCase())
.join("-")}`]: `${v.value}${v.unit}`,
}))
.reduce((acc, cur) => ({ ...acc, ...cur }), {});
.map(
([k, v]) =>
[
`--${k
.split("_")
.map((s) => s.toLowerCase())
.join("-")}`,
`${v.value}${v.unit}`,
] as [string, string]
)
.reduce((acc, cur) => [...acc, cur], [] as [string, string][]);
enum UNIT {
PX = "px",
......@@ -81,3 +85,7 @@ export const matConfig = {
unit: UNIT.PX,
},
};
toCssProperties(matConfig).forEach(([k, v]) => {
document.body.style.setProperty(k, v);
});
.skeleton-cover {
background-color: gray;
}
.ygo-card {
aspect-ratio: var(--card-ratio);
}
......@@ -11,34 +11,38 @@ interface Props {
code?: number;
style?: CSSProperties;
width?: number;
onClick?: () => void;
}
export const YgoCard: FC<Props> = (props) => {
const {
className,
code: cardCode = 0,
code = 0,
isBack = false,
width = 80,
style,
onClick = () => {},
} = props;
return useMemo(
() => (
<>
{cardCode === 0 && !isBack ? (
{code === 0 && !isBack ? (
<div
className={classNames("ygo-card", "skeleton-cover")}
className={classNames("ygo-card", "skeleton-cover", className)}
style={{ width, ...style }}
onClick={onClick}
/>
) : (
<img
className={classNames("ygo-card", className)}
src={getCardImgUrl(cardCode, isBack)}
src={getCardImgUrl(code, isBack)}
style={{ width, ...style }}
onClick={onClick}
/>
)}
</>
),
[cardCode]
[code]
);
};
......
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