Commit 3eec2126 authored by Chunchi Che's avatar Chunchi Che

Merge branch 'feat/ui/select_card' into 'main'

Feat/ui/select card

See merge request mycard/Neos!61
parents 6811be94 3b13f994
This diff is collapsed.
......@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/pro-components": "^2.3.49",
"@react-spring/shared": "^9.6.1",
"@react-spring/types": "^9.6.1",
"@react-spring/web": "^9.6.1",
......
......@@ -25,6 +25,10 @@ import {
setCardModalInteractiviesImpl,
setCardListModalIsOpenImpl,
setCardListModalInfoImpl,
setCheckCardModalIsOpenImpl,
setCheckCardModalMinMaxImpl,
resetCheckCardModalImpl,
checkCardModalCase,
} from "./modalSlice";
import {
MonsterState,
......@@ -76,6 +80,7 @@ const initialState: DuelState = {
modalState: {
cardModal: { isOpen: false, interactivies: [] },
cardListModal: { isOpen: false, list: [] },
checkCardModal: { isOpen: false, tags: [] },
},
};
......@@ -116,6 +121,9 @@ const duelSlice = createSlice({
setCardModalInteractivies: setCardModalInteractiviesImpl,
setCardListModalIsOpen: setCardListModalIsOpenImpl,
setCardListModalInfo: setCardListModalInfoImpl,
setCheckCardModalIsOpen: setCheckCardModalIsOpenImpl,
setCheckCardModalMinMax: setCheckCardModalMinMaxImpl,
resetCheckCardModal: resetCheckCardModalImpl,
},
extraReducers(builder) {
handsCase(builder);
......@@ -123,6 +131,7 @@ const duelSlice = createSlice({
monsterCase(builder);
magicCase(builder);
cemeteryCase(builder);
checkCardModalCase(builder);
},
});
......@@ -148,6 +157,9 @@ export const {
initCemetery,
setCardListModalIsOpen,
setCardListModalInfo,
setCheckCardModalIsOpen,
setCheckCardModalMinMax,
resetCheckCardModal,
} = duelSlice.actions;
export const selectDuelHsStart = (state: RootState) => {
return state.duel.meInitInfo != null;
......
import { PayloadAction, CaseReducer } from "@reduxjs/toolkit";
import {
PayloadAction,
CaseReducer,
createAsyncThunk,
ActionReducerMapBuilder,
} from "@reduxjs/toolkit";
import { fetchCard } from "../../api/cards";
import { RootState } from "../../store";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface ModalState {
// 卡牌弹窗
......@@ -20,6 +27,21 @@ export interface ModalState {
imgUrl?: string;
}[];
};
// 卡牌选择弹窗
checkCardModal: {
isOpen: boolean;
selectMin?: number;
selectMax?: number;
tags: {
tagName: string;
options: {
code: number;
name?: string;
desc?: string;
response: number;
}[];
}[];
};
}
// 更新卡牌弹窗打开状态
......@@ -66,7 +88,7 @@ export const setCardListModalIsOpenImpl: CaseReducer<
state.modalState.cardListModal.isOpen = action.payload;
};
// 更新卡牌列表文本
// 更新卡牌列表数据
export const setCardListModalInfoImpl: CaseReducer<
DuelState,
PayloadAction<{ name?: string; desc?: string; imgUrl?: string }[]>
......@@ -76,6 +98,100 @@ export const setCardListModalInfoImpl: CaseReducer<
state.modalState.cardListModal.list = list;
};
// 更新卡牌选择弹窗打开状态
export const setCheckCardModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.checkCardModal.isOpen = action.payload;
};
// 更新卡牌选择弹窗选择数目状态
export const setCheckCardModalMinMaxImpl: CaseReducer<
DuelState,
PayloadAction<{ min: number; max: number }>
> = (state, action) => {
state.modalState.checkCardModal.selectMin = action.payload.min;
state.modalState.checkCardModal.selectMax = action.payload.max;
};
// 增加卡牌选择选项
export const fetchCheckCardMeta = createAsyncThunk(
"duel/fetchCheckCardMeta",
async (param: {
controler: number;
tagName: string;
option: { code: number; response: number };
}) => {
const meta = await fetchCard(param.option.code);
const response = {
controler: param.controler,
tagName: param.tagName,
meta: {
code: meta.id,
name: meta.text.name,
desc: meta.text.desc,
},
};
return response;
}
);
export const checkCardModalCase = (
builder: ActionReducerMapBuilder<DuelState>
) => {
builder.addCase(fetchCheckCardMeta.pending, (state, action) => {
const controler = action.meta.arg.controler;
const tagName = action.meta.arg.tagName;
const code = action.meta.arg.option.code;
const response = action.meta.arg.option.response;
const combinedTagName = judgeSelf(controler, state)
? `我方的${tagName}`
: `对方的${tagName}`;
for (const tag of state.modalState.checkCardModal.tags) {
if (tag.tagName === combinedTagName) {
tag.options.push({ code, response });
return;
}
}
state.modalState.checkCardModal.tags.push({
tagName,
options: [{ code, response }],
});
});
builder.addCase(fetchCheckCardMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const tagName = action.payload.tagName;
const meta = action.payload.meta;
const combinedTagName = judgeSelf(controler, state)
? `我方的${tagName}`
: `对方的${tagName}`;
for (const tag of state.modalState.checkCardModal.tags) {
if (tag.tagName === combinedTagName) {
for (const option of tag.options) {
if (option.code == meta.code) {
option.name = meta.name;
option.desc = meta.desc;
}
}
}
}
});
};
export const resetCheckCardModalImpl: CaseReducer<DuelState> = (state) => {
state.modalState.checkCardModal.isOpen = false;
state.modalState.checkCardModal.selectMin = undefined;
state.modalState.checkCardModal.selectMax = undefined;
state.modalState.checkCardModal.tags = [];
};
export const selectCardModalIsOpen = (state: RootState) =>
state.duel.modalState.cardModal.isOpen;
export const selectCardModalName = (state: RootState) =>
......@@ -90,3 +206,13 @@ export const selectCardListModalIsOpen = (state: RootState) =>
state.duel.modalState.cardListModal.isOpen;
export const selectCardListModalInfo = (state: RootState) =>
state.duel.modalState.cardListModal.list;
export const selectCheckCardModalIsOpen = (state: RootState) =>
state.duel.modalState.checkCardModal.isOpen;
export const selectCheckCardModalMinMax = (state: RootState) => {
return {
min: state.duel.modalState.checkCardModal.selectMin || 0,
max: state.duel.modalState.checkCardModal.selectMax || 0,
};
};
export const selectCheckCardModalTags = (state: RootState) =>
state.duel.modalState.checkCardModal.tags;
import { ygopro } from "../../api/ocgcore/idl/ocgcore";
import { AppDispatch } from "../../store";
import {
setCheckCardModalIsOpen,
setCheckCardModalMinMax,
} from "../../reducers/duel/mod";
import { fetchCheckCardMeta } from "../../reducers/duel/modalSlice";
import MsgSelectCard = ygopro.StocGameMessage.MsgSelectCard;
import { CardZoneToChinese } from "./util";
export default (selectCard: MsgSelectCard, dispatch: AppDispatch) => {
console.log(selectCard);
// TODO
const _player = selectCard.player;
const _cancelable = selectCard.cancelable; // TODO: 处理可取消逻辑
const min = selectCard.min;
const max = selectCard.max;
const cards = selectCard.cards;
dispatch(setCheckCardModalMinMax({ min, max }));
for (const card of cards) {
const tagName = CardZoneToChinese(card.location.location);
dispatch(
fetchCheckCardMeta({
controler: card.location.controler,
tagName,
option: { code: card.code, response: card.response },
})
);
}
dispatch(setCheckCardModalIsOpen(true));
};
import { ygopro } from "../../api/ocgcore/idl/ocgcore";
export function CardZoneToChinese(zone: ygopro.CardZone): string {
switch (zone) {
case ygopro.CardZone.DECK: {
return "卡组";
}
case ygopro.CardZone.HAND: {
return "手牌";
}
case ygopro.CardZone.EXTRA: {
return "额外卡组";
}
case ygopro.CardZone.GRAVE: {
return "墓地";
}
case ygopro.CardZone.FZONE: {
return "FZONE";
}
case ygopro.CardZone.MZONE: {
return "怪兽区";
}
case ygopro.CardZone.SZONE: {
return "魔法陷阱区";
}
case ygopro.CardZone.REMOVED: {
return "除外区";
}
case ygopro.CardZone.OVERLAY: {
return "超量区";
}
case ygopro.CardZone.PZONE: {
return "灵摆区";
}
case ygopro.CardZone.ONFIELD: {
return "场地区";
}
default: {
return "未知区域";
}
}
}
import React, { useState } from "react";
import { useAppSelector } from "../../hook";
import { store } from "../../store";
import {
selectCheckCardModalIsOpen,
selectCheckCardModalMinMax,
selectCheckCardModalTags,
} from "../../reducers/duel/modalSlice";
import {
resetCheckCardModal,
setCheckCardModalIsOpen,
} from "../../reducers/duel/mod";
import { Modal, Button, Row, Col } from "antd";
import { CheckCard } from "@ant-design/pro-components";
import { sendSelectCardResponse } from "../../api/ocgcore/ocgHelper";
const CheckCardModal = () => {
const dispatch = store.dispatch;
const isOpen = useAppSelector(selectCheckCardModalIsOpen);
const { min, max } = useAppSelector(selectCheckCardModalMinMax);
const tabs = useAppSelector(selectCheckCardModalTags);
const [response, setResponse] = useState<number[]>([]);
const defaultValue: number[] = [];
return (
<Modal
title={`请选择${min}到${max}张卡片`}
open={isOpen}
closable={false}
footer={
<Button
disabled={response.length < min || response.length > max}
onClick={() => {
sendSelectCardResponse(response);
dispatch(setCheckCardModalIsOpen(false));
dispatch(resetCheckCardModal());
}}
>
summit
</Button>
}
width={800}
>
<CheckCard.Group
multiple
bordered
size="small"
defaultValue={defaultValue}
onChange={(value) => {
// @ts-ignore
setResponse(value);
}}
>
{tabs.map((tab) => {
return (
<Row>
{tab.options.map((option) => {
return (
<Col span={4}>
<CheckCard
title={option.name}
description={option.desc}
style={{ width: 120 }}
cover={
<img
alt={option.code.toString()}
src={`https://cdn02.moecube.com:444/images/ygopro-images-zh-CN/${option.code}.jpg`}
style={{ width: 100 }}
/>
}
value={option.response}
/>
</Col>
);
})}
</Row>
);
})}
</CheckCard.Group>
</Modal>
);
};
export default CheckCardModal;
......@@ -13,6 +13,7 @@ import Deck from "./deck";
import Exclusion from "./exclusion";
import Cemeteries from "./cemetery";
import CardListModal from "./cardListModal";
import CheckCardModal from "./checkCardModal";
// Ref: https://github.com/brianzinn/react-babylonjs/issues/126
const NeosDuel = () => (
......@@ -40,6 +41,7 @@ const NeosDuel = () => (
<CardModal />
<CardListModal />
<HintNotification />
<CheckCardModal />
</>
);
......
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