Skip to content

Commit

Permalink
Popup actions for game link
Browse files Browse the repository at this point in the history
  • Loading branch information
SAPikachu committed May 14, 2024
1 parent e6e2712 commit d1f49be
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 4 deletions.
27 changes: 27 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@mui/material": "^5.15.16",
"@sentry/react": "^6.17.4",
"clsx": "^1.1.1",
"copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.11",
"i18next": "^19.9.2",
"i18next-browser-languagedetector": "^6.1.8",
Expand Down
5 changes: 4 additions & 1 deletion src/components/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { RegisterSnackbarProvider } from "../../utils/notify";
import { FC } from "react";
import StarPlayerProvider from "../playerDetails/star/starPlayerProvider";
import { Routes } from "./routes";
import GameLinkActionsProvider from "../gameRecords/gameLinkActions";

const Helmet = Loadable({
loader: () => import("react-helmet"),
Expand Down Expand Up @@ -70,7 +71,9 @@ function App() {
<Scroller>
{Conf.showTopNotice ? <AppHeader /> : <></>}
<Container>
<Routes />
<GameLinkActionsProvider>
<Routes />
</GameLinkActionsProvider>
</Container>
</Scroller>
</MaintenanceHandler>
Expand Down
82 changes: 82 additions & 0 deletions src/components/gameRecords/gameLinkActions/dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { ContentCopy, PieChartRounded, ReadMore, Replay, SvgIconComponent } from "@mui/icons-material";
import { Avatar, Dialog, List, ListItem, ListItemAvatar, ListItemButton, ListItemText } from "@mui/material";
import copy from "copy-to-clipboard";
import { useSnackbar } from "notistack";
import React, { AnchorHTMLAttributes, useCallback, useEffect, useReducer } from "react";
import { useTranslation } from "react-i18next";
import { GameRecord, PlayerRecord } from "../../../data/types";
import Conf from "../../../utils/conf";
import { generatePlayerPathById } from "../routeUtils";

const Action = ({
Icon,
text,
...props
}: {
Icon: SvgIconComponent;
text: string;
} & Parameters<typeof ListItemButton>[0] &
AnchorHTMLAttributes<HTMLAnchorElement>) => (
<ListItem disableGutters>
<ListItemButton {...props}>
<ListItemAvatar>
<Avatar>
<Icon fontSize="inherit" />
</Avatar>
</ListItemAvatar>
<ListItemText primary={text} primaryTypographyProps={{ variant: "body2", sx: { pr: 1 } }} />
</ListItemButton>
</ListItem>
);
export const ActionsDialog = React.memo(
({ player, game, onClose }: { player?: PlayerRecord; game?: GameRecord; onClose: () => void }) => {
const { t } = useTranslation();
const { enqueueSnackbar } = useSnackbar();
const [savedGame, updateGame] = useReducer(
(prev: GameRecord | undefined, cur: GameRecord | undefined) => cur || prev,
game,
(game) => game
);
useEffect(() => {
updateGame(game);
}, [game]);
const isMasked = !(game || savedGame)?.uuid || (game || savedGame)?._masked;
const gameLink = !game ? "#" : (isMasked ? GameRecord.getMaskedRecordLink : GameRecord.getRecordLink)(game, player);
const copyLink = useCallback(() => {
if (!gameLink) {
return;
}
copy(gameLink);
enqueueSnackbar(t("链接复制成功"), { variant: "success", autoHideDuration: 2000 });
}, [gameLink, enqueueSnackbar, t]);
return (
<Dialog open={!!game} onClose={onClose} onClick={onClose} maxWidth="xs">
<List>
<Action Icon={Replay} text={t("查看牌谱")} href={gameLink} target="_blank" />
{!isMasked && <Action Icon={ContentCopy} onClick={copyLink} text={t("复制链接")} />}
<Action
Icon={ReadMore}
text={t("玩家详细")}
href={player?.accountId ? generatePlayerPathById(player.accountId) : "#"}
/>
{Conf.features.aiReview && !isMasked && (
<Action
Icon={PieChartRounded}
text={t("AI 检讨")}
target="_blank"
href={
game && player
? `${t("https://mjai.ekyu.moe/zh-cn.html")}?url=${encodeURIComponent(
GameRecord.getRecordLink(game, player)
)}`
: "#"
}
/>
)}
</List>
</Dialog>
);
}
);

export default ActionsDialog;
36 changes: 36 additions & 0 deletions src/components/gameRecords/gameLinkActions/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { ReactNode, useCallback, useMemo, useState } from "react";
import { GameRecord, PlayerRecord } from "../../../data/types";
import Loadable from "../../misc/customizedLoadable";

const ActionsDialog = Loadable({
loader: () => import(/* webpackMode: "lazy" */ /* webpackFetchPriority: "low" */ "./dialog"),
loading: () => <></>,
});

const Context = React.createContext<{ open: (player: PlayerRecord, game: GameRecord) => void }>({
open: () => {
/* Placeholder */
},
});

export const useGameLinkActions = () => React.useContext(Context);

const GameLinkActionsProvider = ({ children }: { children: ReactNode }) => {
const [info, setInfo] = useState<{ player: PlayerRecord; game: GameRecord } | null>(null);
const { player, game } = info || {};
const open = useCallback(
(player: PlayerRecord, game: GameRecord) => {
setInfo({ player, game });
},
[setInfo]
);
const close = useCallback(() => setInfo(null), [setInfo]);
const value = useMemo(() => ({ open }), [open]);
return (
<Context.Provider value={value}>
<ActionsDialog player={player} game={game} onClose={close} />
{children}
</Context.Provider>
);
};
export default GameLinkActionsProvider;
10 changes: 7 additions & 3 deletions src/components/gameRecords/player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";

import { GameRecord, PlayerRecord, getLevelTag } from "../../data/types";
import Conf from "../../utils/conf";
import { useGameLinkActions } from "./gameLinkActions";
import { generatePlayerPathById } from "./routeUtils";

export const Player = React.memo(function ({
Expand All @@ -23,6 +24,7 @@ export const Player = React.memo(function ({
} & TypographyProps) {
const { t } = useTranslation();
const theme = useTheme();
const { open } = useGameLinkActions();
const { nickname, level, score, accountId } = player;
const isTop = GameRecord.getRankIndexByPlayer(game, player) === 0;
return (
Expand All @@ -36,9 +38,11 @@ export const Player = React.memo(function ({
{...props}
>
<Link
href={(maskedGameLink || !game.uuid || game._masked
? GameRecord.getMaskedRecordLink
: GameRecord.getRecordLink)(game, player)}
href={maskedGameLink || !game.uuid || game._masked ? "#" : GameRecord.getRecordLink(game, player)}
onClick={(e) => {
e.preventDefault();
open(player, game);
}}
title={t("查看牌谱")}
target="_blank"
rel="noopener noreferrer"
Expand Down
2 changes: 2 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
"筛选": "Filter",

"查看牌谱": "View game",
"复制链接": "Copy link",
"链接复制成功": "Link copied",
"玩家详细": "Player details",
"AI 检讨": "AI review",
"https://mjai.ekyu.moe/zh-cn.html": "https://mjai.ekyu.moe/",
Expand Down
2 changes: 2 additions & 0 deletions src/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
"北": "",

"查看牌谱": "牌譜を見る",
"复制链接": "リンクをコピーする",
"链接复制成功": "リンクはコピーされました",
"玩家详细": "プレイヤーの情報",
"AI 检讨": "AI レビュー",
"https://mjai.ekyu.moe/zh-cn.html": "https://mjai.ekyu.moe/ja.html",
Expand Down
2 changes: 2 additions & 0 deletions src/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
"北": "",

"查看牌谱": "패보 보기",
"复制链接": "링크 복사",
"链接复制成功": "링크가 복사되었습니다",
"玩家详细": "플레이어 정보",
"AI 检讨": "AI 패보 복기하기",
"https://mjai.ekyu.moe/zh-cn.html": "https://mjai.ekyu.moe/ko.html",
Expand Down

0 comments on commit d1f49be

Please sign in to comment.