Upstream franciscoBSalgueiro/en-croissant state of the touched files before 512cbd9d53
This commit is contained in:
1 parent
01c5621cdb
commit
654f884a5c
3 files changed
+1728
No files matched your search
@@ -0,0 +1,218 @@
|
||||
import { ActionIcon, Box, Menu, Portal, Text, Tooltip } from "@mantine/core";
|
||||
import { useClickOutside } from "@mantine/hooks";
|
||||
import {
|
||||
IconArrowsJoin,
|
||||
IconChevronsUp,
|
||||
IconChevronUp,
|
||||
IconCopy,
|
||||
IconFlag,
|
||||
IconX,
|
||||
} from "@tabler/icons-react";
|
||||
import equal from "fast-deep-equal";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { memo, useContext, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useStore } from "zustand";
|
||||
import { useStoreWithEqualityFn } from "zustand/traditional";
|
||||
import Comment from "@/components/common/Comment";
|
||||
import { currentTabAtom } from "@/state/atoms";
|
||||
import type { Annotation } from "@/utils/annotation";
|
||||
import { hasMorePriority, stripClock } from "@/utils/chess";
|
||||
import { getTabFile } from "@/utils/tabs";
|
||||
import { type TreeNode, treeIterator } from "@/utils/treeReducer";
|
||||
import MoveCell from "./MoveCell";
|
||||
import { TreeStateContext } from "./TreeStateContext";
|
||||
|
||||
const transpositionCache = new WeakMap<TreeNode, Map<string, number[][]>>();
|
||||
|
||||
function getTranspositionMap(root: TreeNode) {
|
||||
if (transpositionCache.has(root)) {
|
||||
return transpositionCache.get(root)!;
|
||||
}
|
||||
|
||||
const map = new Map<string, number[][]>();
|
||||
const iterator = treeIterator(root);
|
||||
|
||||
for (const item of iterator) {
|
||||
const strippedFen = stripClock(item.node.fen);
|
||||
if (!map.has(strippedFen)) {
|
||||
map.set(strippedFen, []);
|
||||
}
|
||||
map.get(strippedFen)!.push(item.position);
|
||||
}
|
||||
|
||||
transpositionCache.set(root, map);
|
||||
return map;
|
||||
}
|
||||
|
||||
function getTranspositions(fen: string, position: number[], root: TreeNode) {
|
||||
if (position.length === 0 || position.every((v) => v === 0)) return [];
|
||||
|
||||
const map = getTranspositionMap(root);
|
||||
const strippedFen = stripClock(fen);
|
||||
|
||||
const matchingPositions = map.get(strippedFen) || [];
|
||||
|
||||
return matchingPositions.filter((targetPosition) => !hasMorePriority(position, targetPosition));
|
||||
}
|
||||
|
||||
function CompleteMoveCell({
|
||||
movePath,
|
||||
halfMoves,
|
||||
move,
|
||||
fen,
|
||||
comment,
|
||||
annotations,
|
||||
showComments,
|
||||
first,
|
||||
targetRef,
|
||||
tableLayout,
|
||||
scoreText,
|
||||
}: {
|
||||
halfMoves: number;
|
||||
comment: string;
|
||||
annotations: Annotation[];
|
||||
showComments: boolean;
|
||||
move?: string | null;
|
||||
fen?: string;
|
||||
first?: boolean;
|
||||
movePath: number[];
|
||||
targetRef: React.RefObject<HTMLSpanElement | null>;
|
||||
tableLayout?: boolean;
|
||||
scoreText?: string;
|
||||
}) {
|
||||
const store = useContext(TreeStateContext)!;
|
||||
const isStart = useStore(store, (s) => equal(movePath, s.headers.start));
|
||||
|
||||
const isCurrentVariation = useStore(store, (s) => equal(s.position, movePath));
|
||||
const transpositions = useStoreWithEqualityFn(
|
||||
store,
|
||||
(s) => (fen ? getTranspositions(fen, movePath, s.root) : []),
|
||||
(a, b) => equal(a, b),
|
||||
);
|
||||
const goToMove = useStore(store, (s) => s.goToMove);
|
||||
const deleteMove = useStore(store, (s) => s.deleteMove);
|
||||
const promoteVariation = useStore(store, (s) => s.promoteVariation);
|
||||
const promoteToMainline = useStore(store, (s) => s.promoteToMainline);
|
||||
const copyVariationPgn = useStore(store, (s) => s.copyVariationPgn);
|
||||
const setStart = useStore(store, (s) => s.setStart);
|
||||
|
||||
const moveNumber = Math.ceil(halfMoves / 2);
|
||||
const isWhite = halfMoves % 2 === 1;
|
||||
const hasNumber = !tableLayout && halfMoves > 0 && (first || isWhite);
|
||||
const ref = useClickOutside(() => {
|
||||
setOpen(false);
|
||||
});
|
||||
const [open, setOpen] = useState(false);
|
||||
const currentTab = useAtomValue(currentTabAtom);
|
||||
const tabFile = getTabFile(currentTab);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
ref={isCurrentVariation ? targetRef : undefined}
|
||||
component="span"
|
||||
style={{
|
||||
display: tableLayout ? "block" : "inline-block",
|
||||
marginLeft: hasNumber ? 6 : 0,
|
||||
fontSize: "80%",
|
||||
width: tableLayout ? "100%" : undefined,
|
||||
}}
|
||||
>
|
||||
{hasNumber && `${moveNumber.toString()}${isWhite ? "." : "..."}`}
|
||||
{move && (
|
||||
<Menu opened={open} width={200}>
|
||||
<Menu.Target>
|
||||
<MoveCell
|
||||
ref={ref}
|
||||
move={move}
|
||||
annotations={annotations}
|
||||
isStart={isStart}
|
||||
isCurrentVariation={isCurrentVariation}
|
||||
onClick={() => goToMove(movePath)}
|
||||
onContextMenu={(e: React.MouseEvent) => {
|
||||
setOpen((v) => !v);
|
||||
e.preventDefault();
|
||||
}}
|
||||
fullWidth={tableLayout}
|
||||
rightAccessory={
|
||||
tableLayout && scoreText ? (
|
||||
<Text component="span" size="xs" c="dimmed">
|
||||
{scoreText}
|
||||
</Text>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</Menu.Target>
|
||||
|
||||
<Portal>
|
||||
<Menu.Dropdown>
|
||||
{tabFile?.metadata.type === "repertoire" && (
|
||||
<Menu.Item
|
||||
leftSection={<IconFlag size="0.875rem" />}
|
||||
onClick={() => setStart(movePath)}
|
||||
>
|
||||
{t("Menu.MarkAsStart")}
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item
|
||||
leftSection={<IconChevronsUp size="0.875rem" />}
|
||||
onClick={() => promoteToMainline(movePath)}
|
||||
>
|
||||
{t("Menu.PromoteToMainLine")}
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Item
|
||||
leftSection={<IconChevronUp size="0.875rem" />}
|
||||
onClick={() => promoteVariation(movePath)}
|
||||
>
|
||||
{t("Menu.PromoteVariation")}
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Item
|
||||
leftSection={<IconCopy size="0.875rem" />}
|
||||
onClick={() => copyVariationPgn(movePath)}
|
||||
>
|
||||
{t("Menu.CopyVariationPGN")}
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Item
|
||||
color="red"
|
||||
leftSection={<IconX size="0.875rem" />}
|
||||
onClick={() => deleteMove(movePath)}
|
||||
>
|
||||
{t("Menu.DeleteMove")}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Portal>
|
||||
</Menu>
|
||||
)}
|
||||
{transpositions.length > 0 && (
|
||||
<Tooltip label="Transposition">
|
||||
<ActionIcon size="xs" onClick={() => goToMove(transpositions[0])}>
|
||||
<IconArrowsJoin size="0.875rem" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
{showComments && !tableLayout && comment && <Comment comment={comment} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(CompleteMoveCell, (prev, next) => {
|
||||
return (
|
||||
prev.move === next.move &&
|
||||
prev.fen === next.fen &&
|
||||
prev.comment === next.comment &&
|
||||
equal(prev.annotations, next.annotations) &&
|
||||
prev.showComments === next.showComments &&
|
||||
prev.first === next.first &&
|
||||
equal(prev.movePath, next.movePath) &&
|
||||
prev.halfMoves === next.halfMoves &&
|
||||
prev.tableLayout === next.tableLayout &&
|
||||
prev.scoreText === next.scoreText
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user