Commit 94acde7d authored by Chunchi Che's avatar Chunchi Che

update small

parent 420831c0
Pipeline #27606 passed with stages
in 7 minutes and 31 seconds
import {
DeleteOutlined,
FilterOutlined,
SearchOutlined,
SortAscendingOutlined,
SwapOutlined,
} from "@ant-design/icons";
import { App, Button, Dropdown, Input, message, Pagination, Space } from "antd";
import { MenuProps } from "antd/lib";
import { isEqual } from "lodash-es";
import { OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
import React, { memo, useCallback, useEffect, useRef, useState } from "react";
import { useDrop } from "react-dnd";
import { CardMeta, searchCards } from "@/api";
import { isExtraDeckCard, isToken } from "@/common";
import { emptySearchConditions, FtsConditions } from "@/middleware/sqlite/fts";
import {
DeckCard,
DeckCardMouseUpEvent,
IconFont,
ScrollableArea,
Type,
} from "@/ui/Shared";
import { selectedCard } from "..";
import { Filter } from "../Filter";
import styles from "../index.module.scss";
import { editDeckStore } from "../store";
/** 卡片库,选择卡片加入正在编辑的卡组 */
export const DeckDatabase: React.FC = () => {
const { modal } = App.useApp();
const [searchWord, setSearchWord] = useState("");
const [searchConditions, setSearchConditions] = useState<FtsConditions>(
emptySearchConditions,
);
const [searchCardResult, setSearchCardResult] = useState<CardMeta[]>([]);
const defaultSort = (a: CardMeta, b: CardMeta) => a.id - b.id;
const sortRef = useRef<(a: CardMeta, b: CardMeta) => number>(defaultSort);
const [sortEdited, setSortEdited] = useState(false);
const [showMdproDecks, setShowMdproDecks] = useState(false);
const setSortRef = (sort: (a: CardMeta, b: CardMeta) => number) => {
sortRef.current = sort;
setSearchCardResult([...searchCardResult.sort(sortRef.current)]);
setSortEdited(true);
};
const genSort = (key: keyof CardMeta["data"], scale: 1 | -1 = 1) => {
return () =>
setSortRef(
(a: CardMeta, b: CardMeta) =>
((a.data?.[key] ?? 0) - (b.data?.[key] ?? 0)) * scale,
);
};
const dropdownOptions: MenuProps["items"] = (
[
["从新到旧", () => setSortRef((a, b) => b.id - a.id)],
["从旧到新", () => setSortRef((a, b) => a.id - b.id)],
["攻击力从高到低", genSort("atk", -1)],
["攻击力从低到高", genSort("atk")],
["守备力从高到低", genSort("def", -1)],
["守备力从低到高", genSort("def")],
["星/阶/刻/Link从高到低", genSort("level", -1)],
["星/阶/刻/Link从低到高", genSort("level")],
["灵摆刻度从高到低", genSort("lscale", -1)],
["灵摆刻度从低到高", genSort("lscale")],
] as const
).map(([label, onClick], key) => ({ key, label, onClick }));
const handleSearch = (conditions: FtsConditions = searchConditions) => {
const result = searchCards({ query: searchWord, conditions })
.filter((card) => !isToken(card.data.type ?? 0))
.sort(sortRef.current); // 衍生物不显示
setSearchCardResult(() => result);
};
useEffect(() => {
handleSearch();
}, []);
const [_, dropRef] = useDrop({
accept: ["Card"], // 指明该区域允许接收的拖放物。可以是单个,也可以是数组
// 里面的值就是useDrag所定义的type
// 当拖拽物在这个拖放区域放下时触发,这个item就是拖拽物的item(拖拽物携带的数据)
drop: ({ value, source }: { value: CardMeta; source: Type | "search" }) => {
if (source !== "search") {
editDeckStore.remove(source, value);
}
},
});
const showFilterModal = () => {
const { destroy } = modal.info({
width: 500,
centered: true,
title: null,
icon: null,
content: (
<Filter
conditions={searchConditions}
onConfirm={(newConditions) => {
setSearchConditions(newConditions);
destroy();
setTimeout(() => handleSearch(newConditions), 200); // 先收起再搜索
}}
onCancel={() => destroy()}
/>
),
footer: null,
});
};
/** 滚动条的ref,用来在翻页之后快速回顶 */
const ref = useRef<OverlayScrollbarsComponentRef<"div">>(null);
const scrollToTop = useCallback(() => {
const viewport = ref.current?.osInstance()?.elements().viewport;
if (viewport) viewport.scrollTop = 0;
}, []);
return (
<div className={styles.container} ref={dropRef}>
<Space className={styles.title} direction="horizontal">
<Input
placeholder="关键词(空格分隔)"
bordered={false}
suffix={
<Button
type="text"
icon={<SearchOutlined />}
onClick={() => handleSearch()}
/>
}
value={searchWord}
onChange={(e) => setSearchWord(e.target.value)}
onKeyUp={(e) => e.key === "Enter" && handleSearch()}
allowClear
style={{ width: "250%" }}
/>
<Button
style={{ marginRight: "1rem" }}
icon={<SwapOutlined />}
onClick={() => setShowMdproDecks(!showMdproDecks)}
>
{showMdproDecks ? "卡片数据库" : "Mdpro在线卡组"}
</Button>
</Space>
<div className={styles["select-btns"]}>
<Button
block
type={
isEqual(emptySearchConditions, searchConditions)
? "text"
: "primary"
}
disabled = {showMdproDecks}
icon={<FilterOutlined />}
onClick={showFilterModal}
>
筛选
</Button>
<Dropdown
menu={{ items: dropdownOptions }}
disabled = {showMdproDecks}
trigger={["click"]}
placement="bottom"
arrow
>
<Button
block
type={sortEdited ? "primary" : "text"}
icon={<SortAscendingOutlined />}
>
<span>
排列
<span className={styles["search-count"]}>
({searchCardResult.length})
</span>
</span>
</Button>
</Dropdown>
<Button
block
type="text"
disabled={showMdproDecks}
icon={<DeleteOutlined />}
onClick={() => {
setSearchConditions(emptySearchConditions);
setSortRef(defaultSort);
setSortEdited(false);
handleSearch(emptySearchConditions);
}}
>
重置
</Button>
</div>
<ScrollableArea className={styles["search-cards-container"]} ref={ref}>
{searchCardResult.length ? (
<CardResults results={searchCardResult} scrollToTop={scrollToTop} />
) : (
<div className={styles.empty}>
<IconFont type="icon-empty" size={40} />
<div>无搜索结果</div>
</div>
)}
</ScrollableArea>
</div>
);
};
/** 搜索区的搜索结果,使用memo避免重复渲染 */
const CardResults: React.FC<{
results: CardMeta[];
scrollToTop: () => void;
}> = memo(({ results, scrollToTop }) => {
const itemsPerPage = 196; // 每页显示的数据数量
const [currentPage, setCurrentPage] = useState(1);
useEffect(() => {
setCurrentPage(1);
}, [results]);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const currentData = results.slice(startIndex, endIndex);
const showSelectedCard = (card: CardMeta) => {
selectedCard.id = card.id;
selectedCard.open = true;
};
const handleAddCardToMain = (card: CardMeta) => {
const cardType = card.data.type ?? 0;
const isExtraCard = isExtraDeckCard(cardType);
const type = isExtraCard ? "extra" : "main";
const { result, reason } = editDeckStore.canAdd(card, type, "search");
if (result) {
editDeckStore.add(type, card);
} else {
message.error(reason);
}
};
const handleAddCardToSide = (card: CardMeta) => {
const { result, reason } = editDeckStore.canAdd(card, "side", "search");
if (result) {
editDeckStore.add("side", card);
} else {
message.error(reason);
}
};
/** safari 不支持 onAuxClick,所以使用 mousedown 模拟 */
const handleMouseUp = (payload: DeckCardMouseUpEvent) => {
const { event, card } = payload;
switch (event.button) {
// 左键
case 0:
showSelectedCard(card);
break;
// 中键
case 1:
handleAddCardToSide(card);
break;
// 右键
case 2:
handleAddCardToMain(card);
break;
default:
break;
}
};
return (
<>
<div className={styles["search-cards"]}>
{currentData.map((card) => (
<DeckCard
value={card}
key={card.id}
source="search"
onMouseUp={handleMouseUp}
onMouseEnter={() => showSelectedCard(card)}
/>
))}
</div>
{results.length > itemsPerPage && (
<div style={{ textAlign: "center", padding: "10px 0 20px" }}>
<Pagination
current={currentPage}
onChange={(page) => {
setCurrentPage(page);
scrollToTop();
}}
total={results.length}
pageSize={itemsPerPage}
showSizeChanger={false}
showLessItems
hideOnSinglePage
/>
</div>
)}
</>
);
});
......@@ -157,6 +157,7 @@ export const DeckDatabase: React.FC = () => {
? "text"
: "primary"
}
disabled={showMdproDecks}
icon={<FilterOutlined />}
onClick={showFilterModal}
>
......@@ -164,6 +165,7 @@ export const DeckDatabase: React.FC = () => {
</Button>
<Dropdown
menu={{ items: dropdownOptions }}
disabled={showMdproDecks}
trigger={["click"]}
placement="bottom"
arrow
......@@ -184,6 +186,7 @@ export const DeckDatabase: React.FC = () => {
<Button
block
type="text"
disabled={showMdproDecks}
icon={<DeleteOutlined />}
onClick={() => {
setSearchConditions(emptySearchConditions);
......
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