Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/copy rolls #319

Merged
merged 2 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### New Features

- Added a new beta tests dialog in the user settings menu - this will allow me to test changes before fully releasing them
- Added the ability to copy rolls to the clipboard

### Changes

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { useStore } from "stores/store";
import { Virtuoso } from "react-virtuoso";
import { LinearProgress } from "@mui/material";
import { Box, LinearProgress } from "@mui/material";
import { GameLogEntry } from "./GameLogEntry";

const MAX_ITEMS = 1000000000;
Expand Down Expand Up @@ -30,7 +30,7 @@ export function GameLog() {
}, [logLength]);

return (
<>
<Box sx={{ flexGrow: 1 }}>
{loading && <LinearProgress />}
<Virtuoso
firstItemIndex={firstItemIndex}
Expand All @@ -39,6 +39,6 @@ export function GameLog() {
startReached={getLogs}
itemContent={(index, log) => <GameLogEntry log={log} />}
/>
</>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Box, Typography } from "@mui/material";
import { RollSnackbar } from "components/shared/RollSnackbar";
import { useEffect } from "react";
import { useStore } from "stores/store";
import { Roll } from "types/DieRolls.type";
import { RollDisplay } from "../RollDisplay";
import { NormalRollActions } from "../RollDisplay/NormalRollActions";

export interface GameLogEntryProps {
log: Roll;
Expand Down Expand Up @@ -67,11 +68,11 @@ export function GameLogEntry(props: GameLogEntryProps) {
alignItems={isYourEntry ? "flex-end" : "flex-start"}
>
<Typography>{rollerName}</Typography>
<RollSnackbar roll={log} isExpanded />
{/* <Card variant={"outlined"} sx={(theme) => ({ p: 2 })}>
<Typography>{log.rollLabel}</Typography>
<Typography>{log.result}</Typography>
</Card> */}
<RollDisplay
roll={log}
isExpanded
actions={<NormalRollActions roll={log} />}
/>
<Typography color={"textSecondary"} variant={"caption"}>
{getLogTimeString(log.timestamp)}
</Typography>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,9 @@ export function Notes(props: NotesProps) {

return (
<Box
height={"100%"}
height={condensedView ? "70vh" : "100%"}
display={"flex"}
width={"100%"}
minHeight={condensedView ? "90vh" : undefined}
>
{(!condensedView || !selectedNoteId) && (
<NoteSidebar
Expand All @@ -91,6 +90,8 @@ export function Notes(props: NotesProps) {
flexShrink={0}
width={0}
minHeight={"100%"}
display={"flex"}
flexDirection={"column"}
sx={{ overflowY: "auto" }}
>
{condensedView &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import {
Box,
ButtonBase,
ListItemIcon,
ListItemText,
Menu,
MenuItem,
} from "@mui/material";
import { useId, useRef, useState } from "react";
import { ROLL_TYPE, Roll } from "types/DieRolls.type";
import MoreIcon from "@mui/icons-material/MoreHoriz";
import CopyIcon from "@mui/icons-material/CopyAll";
import { getRollResultLabel } from "./getRollResultLabel";
import { useSnackbar } from "providers/SnackbarProvider";
import { convertRollToClipboard } from "./clipboardFormatter";

export interface NormalRollActionsProps {
roll: Roll;
}

async function pasteRich(rich: string, plain: string) {
if (typeof ClipboardItem !== "undefined") {
// Shiny new Clipboard API, not fully supported in Firefox.
// https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API#browser_compatibility
const html = new Blob([rich], { type: "text/html" });
const text = new Blob([plain], { type: "text/plain" });
const data = new ClipboardItem({ "text/html": html, "text/plain": text });
await navigator.clipboard.write([data]);
} else {
// Fallback using the deprecated `document.execCommand`.
// https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand#browser_compatibility
const cb = (e: ClipboardEvent) => {
e.clipboardData?.setData("text/html", rich);
e.clipboardData?.setData("text/plain", plain);
e.preventDefault();
};
document.addEventListener("copy", cb);
document.execCommand("copy");
document.removeEventListener("copy", cb);
}
}

export function NormalRollActions(props: NormalRollActionsProps) {
const { roll } = props;
const id = useId();
const blockQuoteId = `roll-action-copy-${id}`;

const [isMenuOpen, setIsMenuOpen] = useState(false);
const menuParent = useRef<HTMLButtonElement>(null);

const { error, success } = useSnackbar();

const handleCopyRoll = () => {
const clipboardData = convertRollToClipboard(roll);

if (clipboardData) {
pasteRich(clipboardData.rich, clipboardData.plain)
.then(() => {
success("Copied roll to clipboard.");
})
.catch(() => {
error("Failed to copy roll");
});
} else {
error("Copying this roll type is not supported");
}
};

let rollTitle = roll.rollLabel;
let actionContents: string | undefined = undefined;
let challengeContents: string | undefined = undefined;
let result: string | undefined = undefined;

if (roll.type === ROLL_TYPE.STAT) {
if (roll.moveName) {
rollTitle = `${roll.moveName} (${roll.rollLabel})`;
}

const rollTotal = roll.action + (roll.modifier ?? 0) + (roll.adds ?? 0);
actionContents = roll.action + "";
if (roll.modifier || roll.adds) {
actionContents +=
(roll.modifier ? ` + ${roll.modifier}` : "") +
(roll.adds ? ` + ${roll.adds}` : "") +
` = ${rollTotal > 10 ? "10 (Max)" : rollTotal}`;
}

challengeContents = roll.challenge1 + ", " + roll.challenge2;

result = getRollResultLabel(roll.result).toLocaleUpperCase();
}

return (
<>
<ButtonBase
aria-label={"Roll Menu"}
ref={menuParent}
onClick={(evt) => {
evt.stopPropagation();
setIsMenuOpen(true);
}}
>
<MoreIcon />
</ButtonBase>
{isMenuOpen && (
<Menu
sx={{
zIndex: 10001,
}}
open={isMenuOpen}
onClose={() => setIsMenuOpen(false)}
anchorEl={menuParent.current}
>
<MenuItem
onClick={(evt) => {
evt.stopPropagation();
handleCopyRoll();
setIsMenuOpen(false);
}}
>
<ListItemIcon>
<CopyIcon />
</ListItemIcon>
<ListItemText>Copy Roll Result</ListItemText>
</MenuItem>
</Menu>
)}
{/* Copy to clipboard component */}
<Box component={"blockquote"} display={"none"} id={blockQuoteId}>
<p>{rollTitle}</p>
{actionContents && (
<p>
<em>Action:</em> {actionContents}
</p>
)}
{challengeContents && (
<p>
<em>Challenge:</em> {challengeContents}
</p>
)}
{result && (
<p>
<b>{result}</b>
</p>
)}
</Box>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Box } from "@mui/material";
import { PropsWithChildren } from "react";

export function RollContainer(props: PropsWithChildren) {
const { children } = props;
return (
<Box display={"flex"} alignItems={"center"}>
{children}
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { Box, Card, CardActionArea } from "@mui/material";
import { ROLL_TYPE, Roll } from "types/DieRolls.type";
import { RollTitle } from "./RollTitle";
import { RollValues } from "./RollValues";
import { RollResult } from "./RollResult";
import { getRollResultLabel } from "./getRollResultLabel";
import { RollContainer } from "./RollContainer";
import { ReactNode } from "react";

export interface RollDisplayProps {
roll: Roll;
onClick?: () => void;
isExpanded: boolean;
actions?: ReactNode;
}

export function RollDisplay(props: RollDisplayProps) {
const { roll, onClick, isExpanded, actions } = props;

return (
<Card
sx={(theme) => ({
backgroundColor: theme.palette.darkGrey.dark,
color: theme.palette.darkGrey.contrastText,
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
})}
>
<Box
component={onClick ? CardActionArea : "div"}
onClick={onClick ? onClick : undefined}
px={2}
py={1}
>
{roll.type === ROLL_TYPE.STAT && (
<>
<RollTitle
title={roll.moveName ? roll.moveName : roll.rollLabel}
overline={roll.moveName ? roll.rollLabel : undefined}
isExpanded={isExpanded}
actions={actions}
/>
<RollContainer>
<RollValues
d6Result={{
action: roll.action,
modifier: roll.modifier,
adds: roll.adds,
rollTotal:
roll.action + (roll.modifier ?? 0) + (roll.adds ?? 0),
}}
d10Results={[roll.challenge1, roll.challenge2]}
isExpanded={isExpanded}
/>
<RollResult
result={getRollResultLabel(roll.result)}
extras={[
...(roll.challenge1 === roll.challenge2 ? ["Doubles"] : []),
...(roll.action === 1 ? ["Natural 1"] : []),
]}
/>
</RollContainer>
</>
)}
{roll.type === ROLL_TYPE.TRACK_PROGRESS && (
<>
<RollTitle
title={roll.rollLabel}
isExpanded={isExpanded}
actions={actions}
/>
<RollContainer>
<RollValues
fixedResult={{
title: "Progress",
value: roll.trackProgress,
}}
d10Results={[roll.challenge1, roll.challenge2]}
isExpanded={isExpanded}
/>
<RollResult
result={getRollResultLabel(roll.result)}
extras={[
...(roll.challenge1 === roll.challenge2 ? ["Doubles"] : []),
]}
/>
</RollContainer>
</>
)}
{roll.type === ROLL_TYPE.ORACLE_TABLE && (
<>
<RollTitle
overline={roll.oracleCategoryName}
title={roll.rollLabel}
isExpanded={isExpanded}
actions={actions}
/>
<RollContainer>
<RollValues d10Results={roll.roll} isExpanded={isExpanded} />
<RollResult markdown={roll.result} />
</RollContainer>
</>
)}
{roll.type === ROLL_TYPE.CLOCK_PROGRESSION && (
<>
<RollTitle
overline={roll.oracleTitle}
title={roll.rollLabel}
isExpanded={isExpanded}
actions={actions}
/>
<RollContainer>
<RollValues d10Results={roll.roll} isExpanded={isExpanded} />
<RollResult markdown={roll.result} />
</RollContainer>
</>
)}
</Box>
</Card>
);
}
Loading
Loading