Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
N
Neos
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
love_飞影
Neos
Commits
7f197346
Commit
7f197346
authored
Aug 31, 2023
by
Chunchi Che
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
代码结构优化:解耦DeckZone和DeckCard,复用在组卡页和Side页
parent
e23ceeb3
Changes
27
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
621 additions
and
432 deletions
+621
-432
src/service/duel/announce.ts
src/service/duel/announce.ts
+17
-21
src/service/duel/rockPaperScissors.ts
src/service/duel/rockPaperScissors.ts
+9
-5
src/service/duel/selectOption.ts
src/service/duel/selectOption.ts
+3
-2
src/stores/matStore/methods/fetchHint.ts
src/stores/matStore/methods/fetchHint.ts
+8
-10
src/ui/BuildDeck/index.module.scss
src/ui/BuildDeck/index.module.scss
+1
-66
src/ui/BuildDeck/index.tsx
src/ui/BuildDeck/index.tsx
+31
-116
src/ui/BuildDeck/store.ts
src/ui/BuildDeck/store.ts
+2
-1
src/ui/BuildDeck/utils.ts
src/ui/BuildDeck/utils.ts
+0
-2
src/ui/Duel/Main.tsx
src/ui/Duel/Main.tsx
+11
-11
src/ui/Duel/Message/AnnounceModal.tsx
src/ui/Duel/Message/AnnounceModal.tsx
+0
-80
src/ui/Duel/Message/OptionModal.tsx
src/ui/Duel/Message/OptionModal.tsx
+84
-19
src/ui/Duel/Message/SelectCardsModal/index.tsx
src/ui/Duel/Message/SelectCardsModal/index.tsx
+29
-15
src/ui/Duel/Message/index.ts
src/ui/Duel/Message/index.ts
+0
-1
src/ui/Duel/PlayMat/Card/index.tsx
src/ui/Duel/PlayMat/Card/index.tsx
+2
-2
src/ui/Layout/index.tsx
src/ui/Layout/index.tsx
+1
-1
src/ui/Match/index.tsx
src/ui/Match/index.tsx
+8
-2
src/ui/NeosRouter.tsx
src/ui/NeosRouter.tsx
+4
-0
src/ui/Shared/DeckCard/index.module.scss
src/ui/Shared/DeckCard/index.module.scss
+38
-0
src/ui/Shared/DeckCard/index.tsx
src/ui/Shared/DeckCard/index.tsx
+56
-0
src/ui/Shared/DeckZone/index.module.scss
src/ui/Shared/DeckZone/index.module.scss
+39
-0
src/ui/Shared/DeckZone/index.tsx
src/ui/Shared/DeckZone/index.tsx
+89
-0
src/ui/Shared/index.ts
src/ui/Shared/index.ts
+2
-0
src/ui/Side/ChangeSideModal/index.tsx
src/ui/Side/ChangeSideModal/index.tsx
+0
-75
src/ui/Side/TpModal.module.scss
src/ui/Side/TpModal.module.scss
+0
-0
src/ui/Side/TpModal.tsx
src/ui/Side/TpModal.tsx
+1
-1
src/ui/Side/index.module.scss
src/ui/Side/index.module.scss
+55
-0
src/ui/Side/index.tsx
src/ui/Side/index.tsx
+131
-2
No files found.
src/service/duel/announce.ts
View file @
7f197346
import
{
fetchCard
,
fetchStrings
,
Region
,
ygopro
}
from
"
@/api
"
;
import
{
fetchCard
,
fetchStrings
,
Region
,
ygopro
}
from
"
@/api
"
;
import
{
display
Announce
Modal
}
from
"
@/ui/Duel/Message
"
;
import
{
display
Option
Modal
}
from
"
@/ui/Duel/Message
"
;
import
MsgAnnounce
=
ygopro
.
StocGameMessage
.
MsgAnnounce
;
import
MsgAnnounce
=
ygopro
.
StocGameMessage
.
MsgAnnounce
;
export
default
async
(
announce
:
MsgAnnounce
)
=>
{
export
default
async
(
announce
:
MsgAnnounce
)
=>
{
...
@@ -14,26 +14,26 @@ export default async (announce: MsgAnnounce) => {
...
@@ -14,26 +14,26 @@ export default async (announce: MsgAnnounce) => {
switch
(
type_
)
{
switch
(
type_
)
{
case
MsgAnnounce
.
AnnounceType
.
RACE
:
{
case
MsgAnnounce
.
AnnounceType
.
RACE
:
{
await
displayAnnounceModal
({
await
displayOptionModal
(
min
,
fetchStrings
(
Region
.
System
,
563
),
title
:
fetchStrings
(
Region
.
System
,
563
),
announce
.
options
.
map
((
option
)
=>
({
options
:
announce
.
options
.
map
((
option
)
=>
({
info
:
fetchStrings
(
Region
.
System
,
1200
+
option
.
code
),
info
:
fetchStrings
(
Region
.
System
,
1200
+
option
.
code
),
response
:
option
.
response
,
response
:
option
.
response
,
})),
})),
});
min
,
);
break
;
break
;
}
}
case
MsgAnnounce
.
AnnounceType
.
Attribute
:
{
case
MsgAnnounce
.
AnnounceType
.
Attribute
:
{
await
displayAnnounceModal
({
await
displayOptionModal
(
min
,
fetchStrings
(
Region
.
System
,
562
),
title
:
fetchStrings
(
Region
.
System
,
562
),
announce
.
options
.
map
((
option
)
=>
({
options
:
announce
.
options
.
map
((
option
)
=>
({
info
:
fetchStrings
(
Region
.
System
,
1010
+
option
.
code
),
info
:
fetchStrings
(
Region
.
System
,
1010
+
option
.
code
),
response
:
option
.
response
,
response
:
option
.
response
,
})),
})),
});
min
,
);
break
;
break
;
}
}
...
@@ -48,23 +48,19 @@ export default async (announce: MsgAnnounce) => {
...
@@ -48,23 +48,19 @@ export default async (announce: MsgAnnounce) => {
});
});
}
}
}
}
await
displayAnnounceModal
({
await
displayOptionModal
(
fetchStrings
(
Region
.
System
,
564
),
options
,
min
);
min
,
title
:
fetchStrings
(
Region
.
System
,
564
),
options
,
});
break
;
break
;
}
}
case
MsgAnnounce
.
AnnounceType
.
Number
:
{
case
MsgAnnounce
.
AnnounceType
.
Number
:
{
await
displayAnnounceModal
({
await
displayOptionModal
(
min
,
fetchStrings
(
Region
.
System
,
565
),
title
:
fetchStrings
(
Region
.
System
,
565
),
announce
.
options
.
map
((
option
)
=>
({
options
:
announce
.
options
.
map
((
option
)
=>
({
info
:
option
.
code
.
toString
(),
info
:
option
.
code
.
toString
(),
response
:
option
.
response
,
response
:
option
.
response
,
})),
})),
});
min
,
);
break
;
break
;
}
}
...
...
src/service/duel/rockPaperScissors.ts
View file @
7f197346
...
@@ -5,9 +5,13 @@ export default async (mora: ygopro.StocGameMessage.MsgRockPaperScissors) => {
...
@@ -5,9 +5,13 @@ export default async (mora: ygopro.StocGameMessage.MsgRockPaperScissors) => {
const
_player
=
mora
.
player
;
const
_player
=
mora
.
player
;
// TODO: I18n
// TODO: I18n
await
displayOptionModal
(
"
请选择猜拳
"
,
[
await
displayOptionModal
(
{
msg
:
"
剪刀
"
,
response
:
1
},
"
请选择猜拳
"
,
{
msg
:
"
石头
"
,
response
:
2
},
[
{
msg
:
"
布
"
,
response
:
3
},
{
info
:
"
剪刀
"
,
response
:
1
},
]);
{
info
:
"
石头
"
,
response
:
2
},
{
info
:
"
布
"
,
response
:
3
},
],
1
,
);
};
};
src/service/duel/selectOption.ts
View file @
7f197346
...
@@ -14,9 +14,10 @@ export default async (selectOption: ygopro.StocGameMessage.MsgSelectOption) => {
...
@@ -14,9 +14,10 @@ export default async (selectOption: ygopro.StocGameMessage.MsgSelectOption) => {
await
Promise
.
all
(
await
Promise
.
all
(
options
.
map
(
async
({
code
,
response
})
=>
{
options
.
map
(
async
({
code
,
response
})
=>
{
const
meta
=
fetchCard
(
code
>>
4
);
const
meta
=
fetchCard
(
code
>>
4
);
const
msg
=
getCardStr
(
meta
,
code
&
0xf
)
||
"
[?]
"
;
const
info
=
getCardStr
(
meta
,
code
&
0xf
)
??
"
[?]
"
;
return
{
msg
,
response
};
return
{
info
,
response
};
}),
}),
),
),
1
,
);
);
};
};
src/stores/matStore/methods/fetchHint.ts
View file @
7f197346
...
@@ -5,11 +5,9 @@ import { cardStore } from "@/stores/cardStore";
...
@@ -5,11 +5,9 @@ import { cardStore } from "@/stores/cardStore";
import
{
matStore
}
from
"
../store
"
;
import
{
matStore
}
from
"
../store
"
;
const
{
hint
}
=
matStore
;
export
const
fetchCommonHintMeta
=
(
code
:
number
)
=>
{
export
const
fetchCommonHintMeta
=
(
code
:
number
)
=>
{
hint
.
code
=
code
;
matStore
.
hint
.
code
=
code
;
hint
.
msg
=
fetchStrings
(
Region
.
System
,
code
);
matStore
.
hint
.
msg
=
fetchStrings
(
Region
.
System
,
code
);
};
};
export
const
fetchSelectHintMeta
=
async
({
export
const
fetchSelectHintMeta
=
async
({
...
@@ -31,13 +29,13 @@ export const fetchSelectHintMeta = async ({
...
@@ -31,13 +29,13 @@ export const fetchSelectHintMeta = async ({
selectHintMeta
=
await
getStrings
(
selectHintData
);
selectHintMeta
=
await
getStrings
(
selectHintData
);
}
}
hint
.
code
=
selectHintData
;
matStore
.
hint
.
code
=
selectHintData
;
if
(
hint
.
code
>
DESCRIPTION_LIMIT
)
{
if
(
matStore
.
hint
.
code
>
DESCRIPTION_LIMIT
)
{
// 针对`MSG_SELECT_PLACE`的特化逻辑
// 针对`MSG_SELECT_PLACE`的特化逻辑
hint
.
msg
=
selectHintMeta
;
matStore
.
hint
.
msg
=
selectHintMeta
;
}
else
{
}
else
{
hint
.
esSelectHint
=
selectHintMeta
;
matStore
.
hint
.
esSelectHint
=
selectHintMeta
;
hint
.
esHint
=
esHint
;
matStore
.
hint
.
esHint
=
esHint
;
}
}
};
};
...
@@ -74,5 +72,5 @@ export const fetchEsHintMeta = async ({
...
@@ -74,5 +72,5 @@ export const fetchEsHintMeta = async ({
}
}
}
}
hint
.
esHint
=
esHint
;
matStore
.
hint
.
esHint
=
esHint
;
};
};
src/ui/BuildDeck/index.module.scss
View file @
7f197346
...
@@ -57,33 +57,7 @@
...
@@ -57,33 +57,7 @@
display
:
flex
;
display
:
flex
;
flex-direction
:
column
;
flex-direction
:
column
;
height
:
100%
;
height
:
100%
;
}
--card-grid
:
10
;
.main
,
.extra
,
.side
{
transition
:
0
.2s
;
position
:
relative
;
border-bottom
:
1px
solid
rgba
(
255
,
255
,
255
,
0
.1
);
padding
:
0
.75rem
;
&
.over
{
background-color
:
hsla
(
0
,
0%
,
100%
,
0
.05
);
}
&
.not-allow-to-drop
{
background-color
:
rgba
(
255
,
0
,
0
,
0
.15
);
cursor
:
not
-
allowed
;
}
}
.main
{
flex
:
3
;
}
.extra
,
.side
{
flex
:
1
;
}
.card-continer
{
display
:
grid
;
grid-template-columns
:
repeat
(
10
,
1fr
);
gap
:
5px
;
}
}
background-color
:
hsla
(
0
,
0%
,
100%
,
0
.05
);
background-color
:
hsla
(
0
,
0%
,
100%
,
0
.05
);
backdrop-filter
:
blur
(
5px
);
backdrop-filter
:
blur
(
5px
);
...
@@ -98,45 +72,6 @@
...
@@ -98,45 +72,6 @@
border-radius
:
0
var
(
--
border-radius
)
var
(
--
border-radius
)
0
;
border-radius
:
0
var
(
--
border-radius
)
var
(
--
border-radius
)
0
;
}
}
.card
{
cursor
:
move
;
width
:
100%
;
background-color
:
rgba
(
255
,
255
,
255
,
0
.1
);
aspect-ratio
:
var
(
--
card-ratio
);
position
:
relative
;
background-size
:
contain
;
content-visibility
:
auto
;
transition
:
0
.1s
;
&
:hover
{
filter
:
brightness
(
0
.9
);
}
.cardname
{
font-size
:
12px
;
position
:
absolute
;
padding
:
5px
;
top
:
0
;
bottom
:
0
;
max-height
:
100%
;
margin
:
auto
;
left
:
0
;
height
:
fit-content
;
width
:
100%
;
text-align
:
center
;
line-height
:
1
.75em
;
overflow
:
hidden
;
//超出的文本隐藏
text-overflow
:
ellipsis
;
//溢出用省略号显示
}
.cardcover
{
position
:
relative
;
}
.cardlimit
{
position
:
absolute
;
top
:
2px
;
left
:
2px
;
width
:
20px
;
}
}
.search-cards-container
{
.search-cards-container
{
height
:
100%
;
height
:
100%
;
.search-cards
{
.search-cards
{
...
...
src/ui/BuildDeck/index.tsx
View file @
7f197346
...
@@ -16,28 +16,28 @@ import {
...
@@ -16,28 +16,28 @@ import {
Pagination
,
Pagination
,
Space
,
Space
,
}
from
"
antd
"
;
}
from
"
antd
"
;
import
classNames
from
"
classnames
"
;
import
{
isEqual
}
from
"
lodash-es
"
;
import
{
isEqual
}
from
"
lodash-es
"
;
import
{
type
OverlayScrollbarsComponentRef
}
from
"
overlayscrollbars-react
"
;
import
{
type
OverlayScrollbarsComponentRef
}
from
"
overlayscrollbars-react
"
;
import
{
memo
,
useCallback
,
useEffect
,
useRef
,
useState
}
from
"
react
"
;
import
{
memo
,
useCallback
,
useEffect
,
useRef
,
useState
}
from
"
react
"
;
import
{
DndProvider
,
useDr
ag
,
useDr
op
}
from
"
react-dnd
"
;
import
{
DndProvider
,
useDrop
}
from
"
react-dnd
"
;
import
{
HTML5Backend
}
from
"
react-dnd-html5-backend
"
;
import
{
HTML5Backend
}
from
"
react-dnd-html5-backend
"
;
import
{
LoaderFunction
}
from
"
react-router-dom
"
;
import
{
LoaderFunction
}
from
"
react-router-dom
"
;
import
{
proxy
,
useSnapshot
}
from
"
valtio
"
;
import
{
proxy
,
useSnapshot
}
from
"
valtio
"
;
import
{
subscribeKey
}
from
"
valtio/utils
"
;
import
{
subscribeKey
}
from
"
valtio/utils
"
;
import
{
type
CardMeta
,
forbidden
,
searchCards
}
from
"
@/api
"
;
import
{
type
CardMeta
,
searchCards
}
from
"
@/api
"
;
import
{
isToken
}
from
"
@/common
"
;
import
{
isToken
}
from
"
@/common
"
;
import
{
useConfig
}
from
"
@/config
"
;
import
{
FtsConditions
}
from
"
@/middleware/sqlite/fts
"
;
import
{
FtsConditions
}
from
"
@/middleware/sqlite/fts
"
;
import
{
deckStore
,
type
IDeck
,
initStore
}
from
"
@/stores
"
;
import
{
deckStore
,
type
IDeck
,
initStore
}
from
"
@/stores
"
;
import
{
import
{
Background
,
Background
,
DeckCard
,
DeckZone
,
IconFont
,
IconFont
,
Loading
,
Loading
,
ScrollableArea
,
ScrollableArea
,
YgoCard
,
}
from
"
@/ui/Shared
"
;
}
from
"
@/ui/Shared
"
;
import
{
Type
}
from
"
@/ui/Shared/DeckZone
"
;
import
{
CardDetail
}
from
"
./CardDetail
"
;
import
{
CardDetail
}
from
"
./CardDetail
"
;
import
{
DeckSelect
}
from
"
./DeckSelect
"
;
import
{
DeckSelect
}
from
"
./DeckSelect
"
;
...
@@ -49,11 +49,8 @@ import {
...
@@ -49,11 +49,8 @@ import {
downloadDeckAsYDK
,
downloadDeckAsYDK
,
editingDeckToIDeck
,
editingDeckToIDeck
,
iDeckToEditingDeck
,
iDeckToEditingDeck
,
type
Type
,
}
from
"
./utils
"
;
}
from
"
./utils
"
;
const
{
assetsPath
}
=
useConfig
();
export
const
loader
:
LoaderFunction
=
async
()
=>
{
export
const
loader
:
LoaderFunction
=
async
()
=>
{
// 必须先加载卡组,不然页面会崩溃
// 必须先加载卡组,不然页面会崩溃
if
(
!
initStore
.
decks
)
{
if
(
!
initStore
.
decks
)
{
...
@@ -207,7 +204,23 @@ export const DeckEditor: React.FC<{
...
@@ -207,7 +204,23 @@ export const DeckEditor: React.FC<{
</
Space
>
</
Space
>
<
ScrollableArea
className=
{
styles
[
"
deck-zone
"
]
}
>
<
ScrollableArea
className=
{
styles
[
"
deck-zone
"
]
}
>
{
([
"
main
"
,
"
extra
"
,
"
side
"
]
as
const
).
map
((
type
)
=>
(
{
([
"
main
"
,
"
extra
"
,
"
side
"
]
as
const
).
map
((
type
)
=>
(
<
DeckZone
key=
{
type
}
type=
{
type
}
/>
<
DeckZone
key=
{
type
}
type=
{
type
}
cards=
{
[...
snapEditDeck
[
type
]]
}
canAdd=
{
editDeckStore
.
canAdd
}
onChange=
{
(
card
,
source
,
destination
)
=>
{
editDeckStore
.
add
(
destination
,
card
);
if
(
source
!==
"
search
"
)
{
editDeckStore
.
remove
(
source
,
card
);
}
}
}
onElementClick=
{
(
card
)
=>
{
selectedCard
.
id
=
card
.
id
;
selectedCard
.
open
=
true
;
}
}
onElementRightClick=
{
(
card
)
=>
editDeckStore
.
remove
(
type
,
card
)
}
/>
))
}
))
}
</
ScrollableArea
>
</
ScrollableArea
>
</
div
>
</
div
>
...
@@ -394,65 +407,6 @@ const Search: React.FC = () => {
...
@@ -394,65 +407,6 @@ const Search: React.FC = () => {
);
);
};
};
/** 正在组卡的zone,包括main/extra/side */
const
DeckZone
:
React
.
FC
<
{
type
:
Type
;
}
>
=
({
type
})
=>
{
const
{
message
}
=
App
.
useApp
();
const
cards
=
useSnapshot
(
editDeckStore
)[
type
];
const
[
allowToDrop
,
setAllowToDrop
]
=
useState
(
false
);
const
[{
isOver
},
dropRef
]
=
useDrop
({
accept
:
[
"
Card
"
],
// 指明该区域允许接收的拖放物。可以是单个,也可以是数组
// 里面的值就是useDrag所定义的type
// 当拖拽物在这个拖放区域放下时触发,这个item就是拖拽物的item(拖拽物携带的数据)
drop
:
({
value
,
source
}:
{
value
:
CardMeta
;
source
:
Type
|
"
search
"
})
=>
{
if
(
type
===
source
)
return
;
const
{
result
,
reason
}
=
editDeckStore
.
canAdd
(
value
,
type
,
source
);
if
(
result
)
{
editDeckStore
.
add
(
type
,
value
);
if
(
source
!==
"
search
"
)
{
editDeckStore
.
remove
(
source
,
value
);
}
}
else
{
message
.
error
(
reason
);
}
},
hover
:
({
value
,
source
})
=>
{
setAllowToDrop
(
type
!==
source
?
editDeckStore
.
canAdd
(
value
,
type
,
source
).
result
:
true
,
);
},
collect
:
(
monitor
)
=>
({
isOver
:
monitor
.
isOver
(),
}),
});
return
(
<
div
className=
{
classNames
(
styles
[
type
],
{
[
styles
.
over
]:
isOver
,
[
styles
[
"
not-allow-to-drop
"
]]:
isOver
&&
!
allowToDrop
,
})
}
ref=
{
dropRef
}
>
<
div
className=
{
styles
[
"
card-continer
"
]
}
>
{
cards
.
map
((
card
,
i
)
=>
(
<
Card
value=
{
card
}
key=
{
card
.
id
+
i
+
type
}
source=
{
type
}
onRightClick=
{
()
=>
editDeckStore
.
remove
(
type
,
card
)
}
/>
))
}
<
div
className=
{
styles
[
"
editing-zone-name
"
]
}
>
{
`${type.toUpperCase()}: ${cards.length}`
}
</
div
>
</
div
>
</
div
>
);
};
/** 搜索区的搜索结果,使用memo避免重复渲染 */
/** 搜索区的搜索结果,使用memo避免重复渲染 */
const
SearchResults
:
React
.
FC
<
{
const
SearchResults
:
React
.
FC
<
{
results
:
CardMeta
[];
results
:
CardMeta
[];
...
@@ -473,7 +427,15 @@ const SearchResults: React.FC<{
...
@@ -473,7 +427,15 @@ const SearchResults: React.FC<{
<>
<>
<
div
className=
{
styles
[
"
search-cards
"
]
}
>
<
div
className=
{
styles
[
"
search-cards
"
]
}
>
{
currentData
.
map
((
card
)
=>
(
{
currentData
.
map
((
card
)
=>
(
<
Card
value=
{
card
}
key=
{
card
.
id
}
source=
"search"
/>
<
DeckCard
value=
{
card
}
key=
{
card
.
id
}
source=
"search"
onClick=
{
()
=>
{
selectedCard
.
id
=
card
.
id
;
selectedCard
.
open
=
true
;
}
}
/>
))
}
))
}
</
div
>
</
div
>
{
results
.
length
>
itemsPerPage
&&
(
{
results
.
length
>
itemsPerPage
&&
(
...
@@ -496,53 +458,6 @@ const SearchResults: React.FC<{
...
@@ -496,53 +458,6 @@ const SearchResults: React.FC<{
);
);
});
});
/** 本组件内使用的单张卡片,增加了文字在图片下方 */
const
Card
:
React
.
FC
<
{
value
:
CardMeta
;
source
:
Type
|
"
search
"
;
onRightClick
?:
()
=>
void
;
}
>
=
memo
(({
value
,
source
,
onRightClick
})
=>
{
const
ref
=
useRef
<
HTMLDivElement
>
(
null
);
const
[{
isDragging
},
drag
]
=
useDrag
({
type
:
"
Card
"
,
item
:
{
value
,
source
},
collect
:
(
monitor
)
=>
({
isDragging
:
monitor
.
isDragging
(),
}),
});
drag
(
ref
);
const
[
showText
,
setShowText
]
=
useState
(
true
);
const
limitCnt
=
forbidden
.
get
(
value
.
id
);
return
(
<
div
className=
{
styles
.
card
}
ref=
{
ref
}
style=
{
{
opacity
:
isDragging
&&
source
!==
"
search
"
?
0
:
1
}
}
onClick=
{
()
=>
{
selectedCard
.
id
=
value
.
id
;
selectedCard
.
open
=
true
;
}
}
onContextMenu=
{
(
e
)
=>
{
e
.
preventDefault
();
onRightClick
?.();
}
}
>
{
showText
&&
<
div
className=
{
styles
.
cardname
}
>
{
value
.
text
.
name
}
</
div
>
}
<
YgoCard
className=
{
styles
.
cardcover
}
code=
{
value
.
id
}
onLoad=
{
()
=>
setShowText
(
false
)
}
/>
{
limitCnt
!==
undefined
&&
(
<
img
className=
{
styles
.
cardlimit
}
src=
{
`${assetsPath}/Limit0${limitCnt}.png`
}
/>
)
}
</
div
>
);
});
const
HigherCardDetail
:
React
.
FC
=
()
=>
{
const
HigherCardDetail
:
React
.
FC
=
()
=>
{
const
{
id
,
open
}
=
useSnapshot
(
selectedCard
);
const
{
id
,
open
}
=
useSnapshot
(
selectedCard
);
return
(
return
(
...
...
src/ui/BuildDeck/store.ts
View file @
7f197346
...
@@ -2,8 +2,9 @@ import { proxy } from "valtio";
...
@@ -2,8 +2,9 @@ import { proxy } from "valtio";
import
{
type
CardMeta
}
from
"
@/api
"
;
import
{
type
CardMeta
}
from
"
@/api
"
;
import
{
isExtraDeckCard
,
isToken
}
from
"
@/common
"
;
import
{
isExtraDeckCard
,
isToken
}
from
"
@/common
"
;
import
{
Type
}
from
"
@/ui/Shared/DeckZone
"
;
import
{
compareCards
,
type
EditingDeck
,
type
Type
}
from
"
./utils
"
;
import
{
compareCards
,
type
EditingDeck
}
from
"
./utils
"
;
export
const
editDeckStore
=
proxy
({
export
const
editDeckStore
=
proxy
({
deckName
:
""
,
deckName
:
""
,
...
...
src/ui/BuildDeck/utils.ts
View file @
7f197346
...
@@ -2,8 +2,6 @@ import { type CardMeta, fetchCard } from "@/api";
...
@@ -2,8 +2,6 @@ import { type CardMeta, fetchCard } from "@/api";
import
{
tellCardBasicType
,
tellCardSecondaryType
}
from
"
@/common
"
;
import
{
tellCardBasicType
,
tellCardSecondaryType
}
from
"
@/common
"
;
import
{
type
IDeck
}
from
"
@/stores
"
;
import
{
type
IDeck
}
from
"
@/stores
"
;
export
type
Type
=
"
main
"
|
"
extra
"
|
"
side
"
;
/** 用在卡组编辑 */
/** 用在卡组编辑 */
export
interface
EditingDeck
{
export
interface
EditingDeck
{
deckName
:
string
;
deckName
:
string
;
...
...
src/ui/Duel/Main.tsx
View file @
7f197346
import
React
,
{
useEffect
}
from
"
react
"
;
import
React
,
{
useEffect
}
from
"
react
"
;
import
{
useNavigate
}
from
"
react-router-dom
"
;
import
{
useSnapshot
}
from
"
valtio
"
;
import
{
resetUnivers
e
}
from
"
@/stores
"
;
import
{
SideStage
,
sideStor
e
}
from
"
@/stores
"
;
import
{
ChangeSideModal
,
TpModal
}
from
"
../Side
"
;
import
{
import
{
Alert
,
Alert
,
AnnounceModal
,
CardListModal
,
CardListModal
,
CardModal
,
CardModal
,
CheckCounterModal
,
CheckCounterModal
,
...
@@ -21,12 +21,15 @@ import {
...
@@ -21,12 +21,15 @@ import {
import
{
LifeBar
,
Mat
,
Menu
,
Underlying
}
from
"
./PlayMat
"
;
import
{
LifeBar
,
Mat
,
Menu
,
Underlying
}
from
"
./PlayMat
"
;
export
const
Component
:
React
.
FC
=
()
=>
{
export
const
Component
:
React
.
FC
=
()
=>
{
const
{
stage
}
=
useSnapshot
(
sideStore
);
const
navigate
=
useNavigate
();
useEffect
(()
=>
{
useEffect
(()
=>
{
return
()
=>
{
if
(
stage
===
SideStage
.
SIDE_CHANGING
)
{
//
Duel组件卸载的时候初始化一些stor
e
//
跳转更换Sid
e
resetUniverse
(
);
navigate
(
"
/side
"
);
}
;
}
},
[]);
},
[
stage
]);
return
(
return
(
<>
<>
...
@@ -44,11 +47,8 @@ export const Component: React.FC = () => {
...
@@ -44,11 +47,8 @@ export const Component: React.FC = () => {
<
OptionModal
/>
<
OptionModal
/>
<
CheckCounterModal
/>
<
CheckCounterModal
/>
<
SortCardModal
/>
<
SortCardModal
/>
<
AnnounceModal
/>
<
SimpleSelectCardsModal
/>
<
SimpleSelectCardsModal
/>
<
EndModal
/>
<
EndModal
/>
<
ChangeSideModal
/>
<
TpModal
/>
</>
</>
);
);
};
};
...
...
src/ui/Duel/Message/AnnounceModal.tsx
deleted
100644 → 0
View file @
e23ceeb3
import
{
CheckCard
}
from
"
@ant-design/pro-components
"
;
import
{
Button
}
from
"
antd
"
;
import
React
,
{
useState
}
from
"
react
"
;
import
{
proxy
,
useSnapshot
}
from
"
valtio
"
;
import
{
sendSelectOptionResponse
}
from
"
@/api
"
;
import
{
NeosModal
}
from
"
./NeosModal
"
;
interface
AnnounceModalProps
{
isOpen
:
boolean
;
title
?:
string
;
min
:
number
;
options
:
{
info
:
string
;
response
:
number
;
}[];
}
const
defaultProps
=
{
isOpen
:
false
,
min
:
1
,
options
:
[],
};
const
localStore
=
proxy
<
AnnounceModalProps
>
(
defaultProps
);
export
const
AnnounceModal
=
()
=>
{
const
{
isOpen
,
title
,
min
,
options
}
=
useSnapshot
(
localStore
);
const
[
selected
,
setSelected
]
=
useState
<
number
[]
>
([]);
return
(
<
NeosModal
title=
{
title
}
open=
{
isOpen
}
footer=
{
<
Button
disabled=
{
selected
.
length
!==
min
}
onClick=
{
()
=>
{
let
response
=
selected
.
reduce
((
res
,
current
)
=>
res
|
current
,
0
);
// 多个选择求或
sendSelectOptionResponse
(
response
);
rs
();
}
}
>
submit
</
Button
>
}
>
<
CheckCard
.
Group
bordered
multiple
size=
"small"
onChange=
{
(
value
:
any
)
=>
{
setSelected
(
value
);
}
}
>
{
options
.
map
((
option
,
idx
)
=>
(
<
CheckCard
key=
{
idx
}
title=
{
option
.
info
}
value=
{
option
.
response
}
/>
))
}
</
CheckCard
.
Group
>
</
NeosModal
>
);
};
let
rs
:
(
arg
?:
any
)
=>
void
=
()
=>
{};
export
const
displayAnnounceModal
=
async
(
args
:
Omit
<
AnnounceModalProps
,
"
isOpen
"
>
,
)
=>
{
Object
.
entries
(
args
).
forEach
(([
key
,
value
])
=>
{
// @ts-ignore
localStore
[
key
]
=
value
;
});
localStore
.
isOpen
=
true
;
await
new
Promise
<
void
>
((
resolve
)
=>
(
rs
=
resolve
));
// 等待在组件内resolve
localStore
.
isOpen
=
false
;
localStore
.
min
=
1
;
localStore
.
options
=
[];
localStore
.
title
=
undefined
;
};
src/ui/Duel/Message/OptionModal.tsx
View file @
7f197346
import
{
CheckCard
}
from
"
@ant-design/pro-components
"
;
import
{
CheckCard
}
from
"
@ant-design/pro-components
"
;
import
{
Button
}
from
"
antd
"
;
import
{
Button
,
Segmented
}
from
"
antd
"
;
import
React
,
{
useState
}
from
"
react
"
;
import
{
chunk
}
from
"
lodash-es
"
;
import
React
,
{
useEffect
,
useState
}
from
"
react
"
;
import
{
proxy
,
useSnapshot
}
from
"
valtio
"
;
import
{
proxy
,
useSnapshot
}
from
"
valtio
"
;
import
{
import
{
...
@@ -14,52 +15,116 @@ import {
...
@@ -14,52 +15,116 @@ import {
import
{
NeosModal
}
from
"
./NeosModal
"
;
import
{
NeosModal
}
from
"
./NeosModal
"
;
type
Options
=
{
msg
:
string
;
response
:
number
}[];
type
Options
=
{
info
:
string
;
response
:
number
}[];
const
defaultStore
=
{
const
defaultStore
=
{
title
:
""
,
title
:
""
,
isOpen
:
false
,
isOpen
:
false
,
min
:
1
,
options
:
[]
satisfies
Options
as
Options
,
options
:
[]
satisfies
Options
as
Options
,
};
};
const
store
=
proxy
(
defaultStore
);
const
store
=
proxy
(
defaultStore
);
// 一页最多4个选项
const
MAX_NUM_PER_PAGE
=
4
;
export
const
OptionModal
=
()
=>
{
export
const
OptionModal
=
()
=>
{
const
snap
=
useSnapshot
(
store
);
const
snap
=
useSnapshot
(
store
);
const
{
title
,
isOpen
,
min
,
options
}
=
snap
;
// options可能太多,因此分页展示
const
[
page
,
setPage
]
=
useState
(
0
);
const
maxPage
=
Math
.
ceil
(
options
.
length
/
MAX_NUM_PER_PAGE
);
const
[
selecteds
,
setSelecteds
]
=
useState
<
number
[][]
>
([]);
const
grouped
=
chunk
(
options
,
MAX_NUM_PER_PAGE
);
const
{
title
,
isOpen
,
options
}
=
snap
;
const
onSummit
=
()
=>
{
const
responses
=
selecteds
.
flat
();
const
[
selected
,
setSelected
]
=
useState
<
number
|
undefined
>
(
undefined
);
if
(
responses
.
length
>
0
)
{
const
response
=
responses
.
reduce
((
res
,
current
)
=>
res
|
current
,
0
);
// 多个选择求或
const
onClick
=
()
=>
{
sendSelectOptionResponse
(
response
);
if
(
selected
!==
undefined
)
{
sendSelectOptionResponse
(
selected
);
rs
();
rs
();
}
}
};
};
useEffect
(()
=>
{
setSelecteds
(
Array
.
from
({
length
:
maxPage
}).
map
((
_
)
=>
[]));
},
[
options
]);
return
(
return
(
<
NeosModal
<
NeosModal
title=
{
title
}
title=
{
title
}
open=
{
isOpen
}
open=
{
isOpen
}
footer=
{
footer=
{
<
Button
disabled=
{
selected
===
undefined
}
onClick=
{
onClick
}
>
<
Button
disabled=
{
selected
s
.
flat
().
length
!==
min
}
onClick=
{
onSummit
}
>
确定
确定
</
Button
>
</
Button
>
}
}
>
>
<
CheckCard
.
Group
bordered
size=
"small"
onChange=
{
setSelected
as
any
}
>
<
Selector
page=
{
page
}
maxPage=
{
maxPage
}
onChange=
{
setPage
as
any
}
/>
{
options
.
map
((
option
,
idx
)
=>
(
{
grouped
.
map
(
<
CheckCard
key=
{
idx
}
title=
{
option
.
msg
}
value=
{
option
.
response
}
/>
(
options
,
i
)
=>
))
}
i
===
page
&&
(
</
CheckCard
.
Group
>
<
CheckCard
.
Group
key=
{
i
}
bordered
multiple
value=
{
selecteds
[
i
]
}
style=
{
{
display
:
"
grid
"
,
gridTemplateColumns
:
"
repeat(2, 1fr)
"
,
gap
:
"
10px
"
,
}
}
onChange=
{
(
values
:
any
)
=>
{
const
v
=
selecteds
.
map
((
x
,
i
)
=>
(
i
===
page
?
values
:
x
));
setSelecteds
(
v
);
}
}
>
{
options
.
map
((
option
,
idx
)
=>
(
<
CheckCard
key=
{
idx
}
style=
{
{
width
:
"
200px
"
,
marginInlineEnd
:
0
,
marginBlockEnd
:
0
,
}
}
title=
{
option
.
info
}
value=
{
option
.
response
}
/>
))
}
</
CheckCard
.
Group
>
),
)
}
</
NeosModal
>
</
NeosModal
>
);
);
};
};
/* 选择区域 */
const
Selector
:
React
.
FC
<
{
page
:
number
;
maxPage
:
number
;
onChange
:
(
value
:
number
)
=>
void
;
}
>
=
({
page
,
maxPage
,
onChange
})
=>
maxPage
>
1
?
(
<
Segmented
block
options=
{
Array
.
from
({
length
:
maxPage
}).
map
((
_
,
idx
)
=>
idx
)
}
style=
{
{
margin
:
"
10px 0
"
}
}
value=
{
page
}
onChange=
{
onChange
as
any
}
></
Segmented
>
)
:
(
<></>
);
let
rs
:
(
v
?:
any
)
=>
void
=
()
=>
{};
let
rs
:
(
v
?:
any
)
=>
void
=
()
=>
{};
export
const
displayOptionModal
=
async
(
title
:
string
,
options
:
Options
)
=>
{
export
const
displayOptionModal
=
async
(
title
:
string
,
options
:
Options
,
min
:
number
,
)
=>
{
store
.
title
=
title
;
store
.
title
=
title
;
store
.
options
=
options
;
store
.
options
=
options
;
store
.
min
=
min
;
store
.
isOpen
=
true
;
store
.
isOpen
=
true
;
await
new
Promise
((
resolve
)
=>
(
rs
=
resolve
));
await
new
Promise
((
resolve
)
=>
(
rs
=
resolve
));
store
.
isOpen
=
false
;
store
.
isOpen
=
false
;
...
@@ -87,10 +152,10 @@ export const handleEffectActivation = async (
...
@@ -87,10 +152,10 @@ export const handleEffectActivation = async (
?
getCardStr
(
meta
,
effect
.
effectCode
&
0xf
)
??
"
[:?]
"
?
getCardStr
(
meta
,
effect
.
effectCode
&
0xf
)
??
"
[:?]
"
:
"
[:?]
"
;
:
"
[:?]
"
;
return
{
return
{
msg
:
effectMsg
,
info
:
effectMsg
,
response
:
effect
.
response
,
response
:
effect
.
response
,
};
};
});
});
await
displayOptionModal
(
fetchStrings
(
Region
.
System
,
556
),
options
);
// 主动发动效果,所以不需要await,但是以后可能要留心
await
displayOptionModal
(
fetchStrings
(
Region
.
System
,
556
),
options
,
1
);
// 主动发动效果,所以不需要await,但是以后可能要留心
}
}
};
};
src/ui/Duel/Message/SelectCardsModal/index.tsx
View file @
7f197346
...
@@ -46,8 +46,8 @@ export const SelectCardsModal: React.FC<SelectCardsModalProps> = ({
...
@@ -46,8 +46,8 @@ export const SelectCardsModal: React.FC<SelectCardsModalProps> = ({
onCancel
,
onCancel
,
onFinish
,
onFinish
,
})
=>
{
})
=>
{
// FIXME: handle `selecteds`
const
grouped
=
groupBy
(
selectables
,
(
option
)
=>
option
.
location
?.
zone
!
);
const
[
result
,
setResult
]
=
useState
<
Option
[]
>
([]);
const
[
result
,
setResult
]
=
useState
<
[
ygopro
.
CardZone
,
Option
[]]
[]
>
([]);
const
[
submitable
,
setSubmitable
]
=
useState
(
false
);
const
[
submitable
,
setSubmitable
]
=
useState
(
false
);
const
hint
=
useSnapshot
(
matStore
.
hint
);
const
hint
=
useSnapshot
(
matStore
.
hint
);
...
@@ -56,14 +56,21 @@ export const SelectCardsModal: React.FC<SelectCardsModalProps> = ({
...
@@ -56,14 +56,21 @@ export const SelectCardsModal: React.FC<SelectCardsModalProps> = ({
const
minMaxText
=
min
===
max
?
min
:
`
${
min
}
-
${
max
}
`
;
const
minMaxText
=
min
===
max
?
min
:
`
${
min
}
-
${
max
}
`
;
// const isMultiple = !single && max > 1;
useEffect
(()
=>
{
// FIXME: 如果想上面这样鞋会panic,还不是很清楚原因,先放着后面再优化
const
initial
:
[
ygopro
.
CardZone
,
Option
[]][]
=
grouped
.
map
(([
zone
,
_
])
=>
[
const
isMultiple
=
true
;
zone
,
[]
as
Option
[],
]);
if
(
initial
.
length
>
0
)
{
setResult
(
initial
);
}
},
[
selectables
]);
// 判断是否可以提交
// 判断是否可以提交
useEffect
(()
=>
{
useEffect
(()
=>
{
const
flatResult
=
result
.
map
(([
_
,
v
])
=>
v
).
flat
();
const
[
sumLevel1
,
sumLevel2
]
=
([
"
level1
"
,
"
level2
"
]
as
const
).
map
((
key
)
=>
const
[
sumLevel1
,
sumLevel2
]
=
([
"
level1
"
,
"
level2
"
]
as
const
).
map
((
key
)
=>
[...
mustSelects
,
...
r
esult
]
[...
mustSelects
,
...
flatR
esult
]
.
map
((
option
)
=>
option
[
key
]
||
0
)
.
map
((
option
)
=>
option
[
key
]
||
0
)
.
reduce
((
sum
,
current
)
=>
sum
+
current
,
0
),
.
reduce
((
sum
,
current
)
=>
sum
+
current
,
0
),
);
);
...
@@ -72,12 +79,10 @@ export const SelectCardsModal: React.FC<SelectCardsModalProps> = ({
...
@@ -72,12 +79,10 @@ export const SelectCardsModal: React.FC<SelectCardsModalProps> = ({
:
sumLevel1
===
totalLevels
||
sumLevel2
===
totalLevels
;
:
sumLevel1
===
totalLevels
||
sumLevel2
===
totalLevels
;
setSubmitable
(
setSubmitable
(
single
single
?
r
esult
.
length
===
1
?
flatR
esult
.
length
===
1
:
result
.
length
>=
min
&&
r
esult
.
length
<=
max
&&
levelMatched
,
:
flatResult
.
length
>=
min
&&
flatR
esult
.
length
<=
max
&&
levelMatched
,
);
);
},
[
result
.
length
]);
},
[
result
]);
const
grouped
=
groupBy
(
selectables
,
(
option
)
=>
option
.
location
?.
zone
!
);
const
zoneOptions
=
grouped
.
map
((
x
)
=>
({
const
zoneOptions
=
grouped
.
map
((
x
)
=>
({
value
:
x
[
0
],
value
:
x
[
0
],
...
@@ -126,7 +131,9 @@ export const SelectCardsModal: React.FC<SelectCardsModalProps> = ({
...
@@ -126,7 +131,9 @@ export const SelectCardsModal: React.FC<SelectCardsModalProps> = ({
<
Button
<
Button
type=
"primary"
type=
"primary"
disabled=
{
!
submitable
}
disabled=
{
!
submitable
}
onClick=
{
()
=>
onSubmit
([...
mustSelects
,
...
result
])
}
onClick=
{
()
=>
onSubmit
([...
mustSelects
,
...
result
.
map
(([
_
,
v
])
=>
v
).
flat
()])
}
>
>
{
submitText
}
{
submitText
}
</
Button
>
</
Button
>
...
@@ -144,11 +151,18 @@ export const SelectCardsModal: React.FC<SelectCardsModalProps> = ({
...
@@ -144,11 +151,18 @@ export const SelectCardsModal: React.FC<SelectCardsModalProps> = ({
options
[
0
]
===
selectedZone
&&
(
options
[
0
]
===
selectedZone
&&
(
<
div
className=
{
styles
[
"
container
"
]
}
key=
{
i
}
>
<
div
className=
{
styles
[
"
container
"
]
}
key=
{
i
}
>
<
CheckCard
.
Group
<
CheckCard
.
Group
onChange=
{
(
res
)
=>
{
onChange=
{
(
res
:
any
)
=>
{
setResult
((
isMultiple
?
res
:
[
res
])
as
any
);
const
newRes
:
[
ygopro
.
CardZone
,
Option
[]][]
=
result
.
map
(
([
k
,
v
])
=>
[
k
,
k
===
selectedZone
?
res
:
v
],
);
setResult
(
newRes
);
}
}
}
}
value=
{
result
.
find
(([
k
,
_
])
=>
k
===
selectedZone
)?.[
1
]
??
([]
as
any
)
}
// TODO 考虑如何设置默认值,比如只有一个的,就直接选中
// TODO 考虑如何设置默认值,比如只有一个的,就直接选中
multiple
=
{
isMultiple
}
multiple
className=
{
styles
[
"
check-group
"
]
}
className=
{
styles
[
"
check-group
"
]
}
>
>
{
options
[
1
].
map
((
card
,
j
)
=>
(
{
options
[
1
].
map
((
card
,
j
)
=>
(
...
...
src/ui/Duel/Message/index.ts
View file @
7f197346
export
*
from
"
./Alert
"
;
export
*
from
"
./Alert
"
;
export
*
from
"
./AnnounceModal
"
;
export
*
from
"
./CardListModal
"
;
export
*
from
"
./CardListModal
"
;
export
*
from
"
./CardModal
"
;
export
*
from
"
./CardModal
"
;
export
*
from
"
./CheckCounterModal
"
;
export
*
from
"
./CheckCounterModal
"
;
...
...
src/ui/Duel/PlayMat/Card/index.tsx
View file @
7f197346
...
@@ -351,11 +351,11 @@ const handleEffectActivation = (
...
@@ -351,11 +351,11 @@ const handleEffectActivation = (
?
getCardStr
(
meta
,
effect
.
effectCode
&
0xf
)
??
"
[:?]
"
?
getCardStr
(
meta
,
effect
.
effectCode
&
0xf
)
??
"
[:?]
"
:
"
[:?]
"
;
:
"
[:?]
"
;
return
{
return
{
msg
:
effectMsg
,
info
:
effectMsg
,
response
:
effect
.
response
,
response
:
effect
.
response
,
};
};
});
});
displayOptionModal
(
fetchStrings
(
Region
.
System
,
556
),
options
);
// 主动发动效果,所以不需要await,但是以后可能要留心
displayOptionModal
(
fetchStrings
(
Region
.
System
,
556
),
options
,
1
);
// 主动发动效果,所以不需要await,但是以后可能要留心
}
}
}
;
}
;
...
...
src/ui/Layout/index.tsx
View file @
7f197346
...
@@ -64,7 +64,7 @@ export const Component = () => {
...
@@ -64,7 +64,7 @@ export const Component = () => {
const
logined
=
Boolean
(
useSnapshot
(
accountStore
).
user
);
const
logined
=
Boolean
(
useSnapshot
(
accountStore
).
user
);
const
{
pathname
}
=
routerLocation
;
const
{
pathname
}
=
routerLocation
;
const
pathnamesHideHeader
=
[
"
/waitroom
"
,
"
/duel
"
];
const
pathnamesHideHeader
=
[
"
/waitroom
"
,
"
/duel
"
,
"
/side
"
];
const
callbackUrl
=
`
${
location
.
origin
}
/match/`
;
const
callbackUrl
=
`
${
location
.
origin
}
/match/`
;
const
onLogin
=
()
=>
location
.
replace
(
getSSOSignInUrl
(
callbackUrl
));
const
onLogin
=
()
=>
location
.
replace
(
getSSOSignInUrl
(
callbackUrl
));
...
...
src/ui/Match/index.tsx
View file @
7f197346
...
@@ -6,12 +6,12 @@ import {
...
@@ -6,12 +6,12 @@ import {
}
from
"
@ant-design/icons
"
;
}
from
"
@ant-design/icons
"
;
import
{
App
,
Button
,
Space
}
from
"
antd
"
;
import
{
App
,
Button
,
Space
}
from
"
antd
"
;
import
{
useEffect
,
useState
}
from
"
react
"
;
import
{
useEffect
,
useState
}
from
"
react
"
;
import
{
useNavigate
}
from
"
react-router-dom
"
;
import
{
LoaderFunction
,
useNavigate
}
from
"
react-router-dom
"
;
import
{
useSnapshot
}
from
"
valtio
"
;
import
{
useSnapshot
}
from
"
valtio
"
;
import
{
match
}
from
"
@/api
"
;
import
{
match
}
from
"
@/api
"
;
import
{
useConfig
}
from
"
@/config
"
;
import
{
useConfig
}
from
"
@/config
"
;
import
{
accountStore
,
deckStore
,
roomStore
}
from
"
@/stores
"
;
import
{
accountStore
,
deckStore
,
r
esetUniverse
,
r
oomStore
}
from
"
@/stores
"
;
import
{
Background
,
IconFont
,
Select
}
from
"
@/ui/Shared
"
;
import
{
Background
,
IconFont
,
Select
}
from
"
@/ui/Shared
"
;
import
styles
from
"
./index.module.scss
"
;
import
styles
from
"
./index.module.scss
"
;
...
@@ -21,6 +21,12 @@ import { connectSrvpro } from "./util";
...
@@ -21,6 +21,12 @@ import { connectSrvpro } from "./util";
const
NeosConfig
=
useConfig
();
const
NeosConfig
=
useConfig
();
export
const
loader
:
LoaderFunction
=
()
=>
{
// 在加载这个页面之前先重置一些store,清掉上局游戏遗留的数据
resetUniverse
();
return
null
;
};
export
const
Component
:
React
.
FC
=
()
=>
{
export
const
Component
:
React
.
FC
=
()
=>
{
const
{
message
}
=
App
.
useApp
();
const
{
message
}
=
App
.
useApp
();
const
serverList
=
NeosConfig
.
servers
;
const
serverList
=
NeosConfig
.
servers
;
...
...
src/ui/NeosRouter.tsx
View file @
7f197346
...
@@ -28,6 +28,10 @@ const router = createBrowserRouter([
...
@@ -28,6 +28,10 @@ const router = createBrowserRouter([
path
:
"
/duel
"
,
path
:
"
/duel
"
,
lazy
:
()
=>
import
(
"
./Duel/Main
"
),
lazy
:
()
=>
import
(
"
./Duel/Main
"
),
},
},
{
path
:
"
/side
"
,
lazy
:
()
=>
import
(
"
./Side
"
),
},
],
],
},
},
]);
]);
...
...
src/ui/Shared/DeckCard/index.module.scss
0 → 100644
View file @
7f197346
.card
{
cursor
:
move
;
width
:
100%
;
background-color
:
rgba
(
255
,
255
,
255
,
0
.1
);
aspect-ratio
:
var
(
--
card-ratio
);
position
:
relative
;
background-size
:
contain
;
content-visibility
:
auto
;
transition
:
0
.1s
;
&
:hover
{
filter
:
brightness
(
0
.9
);
}
.cardname
{
font-size
:
12px
;
position
:
absolute
;
padding
:
5px
;
top
:
0
;
bottom
:
0
;
max-height
:
100%
;
margin
:
auto
;
left
:
0
;
height
:
fit-content
;
width
:
100%
;
text-align
:
center
;
line-height
:
1
.75em
;
overflow
:
hidden
;
//超出的文本隐藏
text-overflow
:
ellipsis
;
//溢出用省略号显示
}
.cardcover
{
position
:
relative
;
}
.cardlimit
{
position
:
absolute
;
top
:
2px
;
left
:
2px
;
width
:
20px
;
}
}
src/ui/Shared/DeckCard/index.tsx
0 → 100644
View file @
7f197346
import
React
,
{
memo
,
useRef
,
useState
}
from
"
react
"
;
import
{
useDrag
}
from
"
react-dnd
"
;
import
{
CardMeta
,
forbidden
}
from
"
@/api
"
;
import
{
useConfig
}
from
"
@/config
"
;
import
{
Type
}
from
"
../DeckZone
"
;
import
{
YgoCard
}
from
"
../YgoCard
"
;
import
styles
from
"
./index.module.scss
"
;
const
{
assetsPath
}
=
useConfig
();
/** 组卡页和Side页使用的单张卡片,增加了文字和禁限数量 */
export
const
DeckCard
:
React
.
FC
<
{
value
:
CardMeta
;
source
:
Type
|
"
search
"
;
onRightClick
?:
()
=>
void
;
onClick
?:
()
=>
void
;
}
>
=
memo
(({
value
,
source
,
onRightClick
,
onClick
})
=>
{
const
ref
=
useRef
<
HTMLDivElement
>
(
null
);
const
[{
isDragging
},
drag
]
=
useDrag
({
type
:
"
Card
"
,
item
:
{
value
,
source
},
collect
:
(
monitor
)
=>
({
isDragging
:
monitor
.
isDragging
(),
}),
});
drag
(
ref
);
const
[
showText
,
setShowText
]
=
useState
(
true
);
const
limitCnt
=
forbidden
.
get
(
value
.
id
);
return
(
<
div
className=
{
styles
.
card
}
ref=
{
ref
}
style=
{
{
opacity
:
isDragging
&&
source
!==
"
search
"
?
0
:
1
}
}
onClick=
{
onClick
}
onContextMenu=
{
(
e
)
=>
{
e
.
preventDefault
();
onRightClick
?.();
}
}
>
{
showText
&&
<
div
className=
{
styles
.
cardname
}
>
{
value
.
text
.
name
}
</
div
>
}
<
YgoCard
className=
{
styles
.
cardcover
}
code=
{
value
.
id
}
onLoad=
{
()
=>
setShowText
(
false
)
}
/>
{
limitCnt
!==
undefined
&&
(
<
img
className=
{
styles
.
cardlimit
}
src=
{
`${assetsPath}/Limit0${limitCnt}.png`
}
/>
)
}
</
div
>
);
});
src/ui/Shared/DeckZone/index.module.scss
0 → 100644
View file @
7f197346
.main
,
.extra
,
.side
{
transition
:
0
.2s
;
position
:
relative
;
border-bottom
:
1px
solid
rgba
(
255
,
255
,
255
,
0
.1
);
padding
:
0
.75rem
;
&
.over
{
background-color
:
hsla
(
0
,
0%
,
100%
,
0
.05
);
}
&
.not-allow-to-drop
{
background-color
:
rgba
(
255
,
0
,
0
,
0
.15
);
cursor
:
not
-
allowed
;
}
}
.main
{
flex
:
3
;
}
.extra
,
.side
{
flex
:
1
;
}
.card-continer
{
display
:
grid
;
grid-template-columns
:
repeat
(
var
(
--
card-grid
)
,
1fr
);
gap
:
5px
;
}
.editing-zone-name
{
position
:
absolute
;
right
:
0
;
bottom
:
0
;
background-color
:
#212332
;
color
:
hsla
(
0
,
0%
,
100%
,
0
.3
);
font-size
:
12px
;
padding
:
2px
6px
;
font-family
:
var
(
--
theme-font
);
user-select
:
none
;
}
src/ui/Shared/DeckZone/index.tsx
0 → 100644
View file @
7f197346
import
{
App
}
from
"
antd
"
;
import
classNames
from
"
classnames
"
;
import
React
,
{
useState
}
from
"
react
"
;
import
{
useDrop
}
from
"
react-dnd
"
;
import
{
CardMeta
}
from
"
@/api
"
;
import
{
DeckCard
}
from
"
../DeckCard
"
;
import
styles
from
"
./index.module.scss
"
;
/** 正在组卡的zone,包括main/extra/side
* 该组件内部没有引用任何store,是解耦的*/
export
type
Type
=
"
main
"
|
"
extra
"
|
"
side
"
;
export
const
DeckZone
:
React
.
FC
<
{
type
:
Type
;
cards
:
CardMeta
[];
canAdd
:
(
card
:
CardMeta
,
type
:
Type
,
source
:
Type
|
"
search
"
,
)
=>
{
result
:
boolean
;
reason
:
string
};
onChange
:
(
card
:
CardMeta
,
source
:
Type
|
"
search
"
,
destination
:
Type
,
)
=>
void
;
onElementClick
:
(
card
:
CardMeta
)
=>
void
;
onElementRightClick
?:
(
card
:
CardMeta
)
=>
void
;
}
>
=
({
type
,
cards
,
canAdd
,
onChange
,
onElementClick
,
onElementRightClick
,
})
=>
{
const
{
message
}
=
App
.
useApp
();
const
[
allowToDrop
,
setAllowToDrop
]
=
useState
(
false
);
const
[{
isOver
},
dropRef
]
=
useDrop
({
accept
:
[
"
Card
"
],
// 指明该区域允许接收的拖放物。可以是单个,也可以是数组
// 里面的值就是useDrag所定义的type
// 当拖拽物在这个拖放区域放下时触发,这个item就是拖拽物的item(拖拽物携带的数据)
drop
:
({
value
,
source
}:
{
value
:
CardMeta
;
source
:
Type
|
"
search
"
})
=>
{
if
(
type
===
source
)
return
;
const
{
result
,
reason
}
=
canAdd
(
value
,
type
,
source
);
if
(
result
)
{
onChange
(
value
,
source
,
type
);
}
else
{
message
.
error
(
reason
);
}
},
hover
:
({
value
,
source
})
=>
{
setAllowToDrop
(
type
!==
source
?
canAdd
(
value
,
type
,
source
).
result
:
true
,
);
},
collect
:
(
monitor
)
=>
({
isOver
:
monitor
.
isOver
(),
}),
});
return
(
<
div
className=
{
classNames
(
styles
[
type
],
{
[
styles
.
over
]:
isOver
,
[
styles
[
"
not-allow-to-drop
"
]]:
isOver
&&
!
allowToDrop
,
})
}
ref=
{
dropRef
}
>
<
div
className=
{
styles
[
"
card-continer
"
]
}
>
{
cards
.
map
((
card
,
i
)
=>
(
<
DeckCard
value=
{
card
}
key=
{
card
.
id
+
i
+
type
}
source=
{
type
}
onClick=
{
()
=>
{
onElementClick
(
card
);
}
}
onRightClick=
{
()
=>
{
onElementRightClick
?.(
card
);
}
}
/>
))
}
<
div
className=
{
styles
[
"
editing-zone-name
"
]
}
>
{
`${type.toUpperCase()}: ${cards.length}`
}
</
div
>
</
div
>
</
div
>
);
};
src/ui/Shared/index.ts
View file @
7f197346
export
*
from
"
./Background
"
;
export
*
from
"
./Background
"
;
export
*
from
"
./CardEffectText
"
;
export
*
from
"
./CardEffectText
"
;
export
*
from
"
./css
"
;
export
*
from
"
./css
"
;
export
*
from
"
./DeckCard
"
;
export
*
from
"
./DeckZone
"
;
export
*
from
"
./IconFont
"
;
export
*
from
"
./IconFont
"
;
export
*
from
"
./Loading
"
;
export
*
from
"
./Loading
"
;
export
*
from
"
./Scrollbar
"
;
export
*
from
"
./Scrollbar
"
;
...
...
src/ui/Side/ChangeSideModal/index.tsx
deleted
100644 → 0
View file @
e23ceeb3
import
{
App
,
Button
,
Modal
}
from
"
antd
"
;
import
React
,
{
useEffect
}
from
"
react
"
;
import
{
DndProvider
}
from
"
react-dnd
"
;
import
{
HTML5Backend
}
from
"
react-dnd-html5-backend
"
;
import
{
useSnapshot
}
from
"
valtio
"
;
import
{
CardMeta
,
sendUpdateDeck
}
from
"
@/api
"
;
import
{
roomStore
,
SideStage
,
sideStore
}
from
"
@/stores
"
;
import
{
DeckEditor
}
from
"
../../BuildDeck
"
;
import
{
editDeckStore
}
from
"
../../BuildDeck/store
"
;
import
{
iDeckToEditingDeck
}
from
"
../../BuildDeck/utils
"
;
import
{
Background
}
from
"
../../Shared
"
;
export
const
ChangeSideModal
:
React
.
FC
=
()
=>
{
const
{
message
}
=
App
.
useApp
();
const
{
deckName
,
main
,
extra
,
side
}
=
useSnapshot
(
editDeckStore
);
const
{
stage
}
=
useSnapshot
(
sideStore
);
const
{
errorMsg
}
=
useSnapshot
(
roomStore
);
const
cardMeta2Id
=
(
meta
:
CardMeta
)
=>
meta
.
id
;
const
handleSummit
=
()
=>
{
const
newDeck
=
{
deckName
:
deckName
,
main
:
main
.
map
(
cardMeta2Id
),
extra
:
extra
.
map
(
cardMeta2Id
),
side
:
side
.
map
(
cardMeta2Id
),
};
sendUpdateDeck
(
newDeck
);
editDeckStore
.
edited
=
false
;
};
useEffect
(()
=>
{
if
(
stage
===
SideStage
.
SIDE_CHANGED
)
{
message
.
info
(
"
副卡组更换成功,请耐心等待其他玩家更换卡组
"
);
}
},
[
stage
]);
useEffect
(()
=>
{
if
(
errorMsg
!==
undefined
&&
errorMsg
!==
""
)
{
message
.
error
(
errorMsg
);
roomStore
.
errorMsg
=
undefined
;
}
},
[
errorMsg
]);
return
(
<
Modal
title=
"请选择更换副卡组"
open=
{
stage
===
SideStage
.
SIDE_CHANGING
||
stage
===
SideStage
.
SIDE_CHANGED
}
width=
{
700
}
closable=
{
false
}
footer=
{
<
Button
disabled=
{
stage
>
SideStage
.
SIDE_CHANGING
}
onClick=
{
handleSummit
}
>
副卡组更换完毕
</
Button
>
}
>
<
DndProvider
backend=
{
HTML5Backend
}
>
<
Background
/>
<
DeckEditor
deck=
{
sideStore
.
deck
}
onClear=
{
()
=>
message
.
error
(
"
对局中清空卡组不怕找不回来吗?!
"
)
}
onSave=
{
()
=>
message
.
error
(
"
点击右下角按钮确认副卡组更换完毕
"
)
}
onReset=
{
async
()
=>
{
editDeckStore
.
set
(
await
iDeckToEditingDeck
(
sideStore
.
deck
));
}
}
/>
</
DndProvider
>
</
Modal
>
);
};
src/ui/Side/TpModal
/index
.module.scss
→
src/ui/Side/TpModal.module.scss
View file @
7f197346
File moved
src/ui/Side/TpModal
/index
.tsx
→
src/ui/Side/TpModal.tsx
View file @
7f197346
...
@@ -5,7 +5,7 @@ import { useSnapshot } from "valtio";
...
@@ -5,7 +5,7 @@ import { useSnapshot } from "valtio";
import
{
sendTpResult
}
from
"
@/api
"
;
import
{
sendTpResult
}
from
"
@/api
"
;
import
{
SideStage
,
sideStore
}
from
"
@/stores
"
;
import
{
SideStage
,
sideStore
}
from
"
@/stores
"
;
import
styles
from
"
./
index
.module.scss
"
;
import
styles
from
"
./
TpModal
.module.scss
"
;
export
const
TpModal
:
React
.
FC
=
()
=>
{
export
const
TpModal
:
React
.
FC
=
()
=>
{
const
{
stage
}
=
useSnapshot
(
sideStore
);
const
{
stage
}
=
useSnapshot
(
sideStore
);
...
...
src/ui/Side/index.module.scss
0 → 100644
View file @
7f197346
.container
{
display
:
flex
;
height
:
100%
;
}
.sider
{
width
:
var
(
--
sider-width
);
flex
:
0
0
var
(
--
sider-width
);
background-color
:
hsla
(
0
,
0%
,
100%
,
0
.05
);
position
:
relative
;
height
:
100%
;
max-height
:
100%
;
min-height
:
100%
;
}
.content
{
flex
:
1
;
padding-bottom
:
0
;
padding
:
1rem
;
width
:
660px
;
}
.deck-container
{
width
:
-
webkit-fill-available
;
height
:
calc
(
100%
-
20px
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0
.05
);
border-radius
:
10px
;
display
:
flex
;
flex-direction
:
column
;
&
>
*
:not
(
:last-of-type
)
{
border-bottom
:
1px
solid
rgba
(
255
,
255
,
255
,
0
.1
);
}
.title
{
height
:
44px
;
flex
:
0
0
44px
;
justify-content
:
space-between
;
padding
:
1em
2em
;
font-size
:
16px
;
}
.deck-zone
{
display
:
flex
;
flex-direction
:
column
;
height
:
100%
;
--card-grid
:
15
;
}
background-color
:
hsla
(
0
,
0%
,
100%
,
0
.05
);
backdrop-filter
:
blur
(
5px
);
}
.detail-container
{
--detail-width
:
300px
;
width
:
var
(
--
detail-width
);
flex
:
0
0
var
(
--
detail-width
);
position
:
relative
;
}
src/ui/Side/index.tsx
View file @
7f197346
export
*
from
"
./ChangeSideModal
"
;
import
{
CheckOutlined
,
UndoOutlined
}
from
"
@ant-design/icons
"
;
export
*
from
"
./TpModal
"
;
import
{
App
,
Button
,
Space
}
from
"
antd
"
;
import
React
,
{
useEffect
,
useState
}
from
"
react
"
;
import
{
DndProvider
}
from
"
react-dnd
"
;
import
{
HTML5Backend
}
from
"
react-dnd-html5-backend
"
;
import
{
useNavigate
}
from
"
react-router-dom
"
;
import
{
useSnapshot
}
from
"
valtio
"
;
import
{
CardMeta
,
fetchCard
,
sendUpdateDeck
}
from
"
@/api
"
;
import
{
isExtraDeckCard
}
from
"
@/common
"
;
import
{
IDeck
,
roomStore
,
SideStage
,
sideStore
}
from
"
@/stores
"
;
import
{
CardDetail
}
from
"
../BuildDeck/CardDetail
"
;
import
{
Background
,
DeckZone
,
ScrollableArea
,
Type
}
from
"
../Shared
"
;
import
{
Chat
}
from
"
../WaitRoom/Chat
"
;
import
styles
from
"
./index.module.scss
"
;
import
{
TpModal
}
from
"
./TpModal
"
;
export
const
Component
:
React
.
FC
=
()
=>
{
const
{
message
}
=
App
.
useApp
();
const
{
deck
:
sideDeck
}
=
sideStore
;
const
{
stage
}
=
useSnapshot
(
sideStore
);
const
{
errorMsg
}
=
useSnapshot
(
roomStore
);
const
initialDeck
=
JSON
.
parse
(
JSON
.
stringify
(
sideDeck
));
const
[
deck
,
setDeck
]
=
useState
<
IDeck
>
(
initialDeck
);
const
[
selectedCard
,
setSelectedCard
]
=
useState
(
0
);
const
navigate
=
useNavigate
();
const
canAdd
=
(
card
:
CardMeta
,
type
:
Type
,
_source
:
Type
|
"
search
"
)
=>
{
const
cardType
=
card
.
data
.
type
??
0
;
if
(
(
type
===
"
extra
"
&&
!
isExtraDeckCard
(
cardType
))
||
(
type
===
"
main
"
&&
isExtraDeckCard
(
cardType
))
)
{
return
{
result
:
false
,
reason
:
"
卡片种类不符合
"
};
}
else
{
return
{
result
:
true
,
reason
:
""
};
}
};
const
onChange
=
(
card
:
CardMeta
,
source
:
Type
|
"
search
"
,
destination
:
Type
,
)
=>
{
setDeck
((
prev
)
=>
{
const
deck
=
{
...
prev
};
if
(
source
!==
"
search
"
)
{
const
removeIndex
=
deck
[
source
].
findIndex
((
id
)
=>
id
===
card
.
id
);
if
(
removeIndex
!==
-
1
)
{
deck
[
source
].
splice
(
removeIndex
,
1
);
}
}
deck
[
destination
].
push
(
card
.
id
);
return
deck
;
});
};
const
onReset
=
()
=>
{
setDeck
(
JSON
.
parse
(
JSON
.
stringify
(
sideDeck
)));
message
.
info
(
"
重置成功
"
);
};
const
onSummit
=
()
=>
sendUpdateDeck
(
deck
);
useEffect
(()
=>
{
if
(
stage
===
SideStage
.
SIDE_CHANGED
)
{
message
.
info
(
"
副卡组更换成功,请耐心等待其他玩家更换卡组
"
);
}
if
(
stage
===
SideStage
.
DUEL_START
)
{
// 决斗开始,跳转
navigate
(
"
/duel
"
);
}
},
[
stage
]);
useEffect
(()
=>
{
if
(
errorMsg
!==
undefined
&&
errorMsg
!==
""
)
{
message
.
error
(
errorMsg
);
roomStore
.
errorMsg
=
undefined
;
}
},
[
errorMsg
]);
return
(
<
DndProvider
backend=
{
HTML5Backend
}
>
<
Background
/>
<
div
className=
{
styles
.
container
}
>
<
div
className=
{
styles
.
sider
}
>
<
Chat
/>
</
div
>
<
div
className=
{
styles
.
content
}
>
<
div
className=
{
styles
[
"
deck-container
"
]
}
>
<
Space
className=
{
styles
.
title
}
>
<
div
>
请拖动更换副卡组
</
div
>
<
Space
style=
{
{
marginRight
:
6
}
}
>
<
Button
type=
"text"
size=
"small"
icon=
{
<
UndoOutlined
/>
}
onClick=
{
onReset
}
>
重置
</
Button
>
<
Button
type=
"primary"
size=
"small"
icon=
{
<
CheckOutlined
/>
}
disabled=
{
stage
>
SideStage
.
SIDE_CHANGING
}
onClick=
{
onSummit
}
>
确定
</
Button
>
</
Space
>
</
Space
>
<
ScrollableArea
className=
{
styles
[
"
deck-zone
"
]
}
>
{
([
"
main
"
,
"
extra
"
,
"
side
"
]
as
const
).
map
((
type
)
=>
(
<
DeckZone
key=
{
type
}
type=
{
type
}
cards=
{
[...
deck
[
type
]].
map
((
id
)
=>
fetchCard
(
id
))
}
canAdd=
{
canAdd
}
onChange=
{
onChange
}
onElementClick=
{
(
card
)
=>
setSelectedCard
(
card
.
id
)
}
/>
))
}
</
ScrollableArea
>
</
div
>
</
div
>
<
div
className=
{
styles
[
"
detail-container
"
]
}
>
<
CardDetail
code=
{
selectedCard
}
open=
{
true
}
onClose=
{
()
=>
{}
}
/>
</
div
>
</
div
>
<
TpModal
/>
</
DndProvider
>
);
};
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment