-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(rolls): Added ability to copy rolls to the clipboard
- Loading branch information
1 parent
dad0418
commit 9823f3d
Showing
20 changed files
with
775 additions
and
495 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
src/components/features/charactersAndCampaigns/RollDisplay/NormalRollActions.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
</> | ||
); | ||
} |
11 changes: 11 additions & 0 deletions
11
src/components/features/charactersAndCampaigns/RollDisplay/RollContainer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
122 changes: 122 additions & 0 deletions
122
src/components/features/charactersAndCampaigns/RollDisplay/RollDisplay.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
47 changes: 47 additions & 0 deletions
47
src/components/features/charactersAndCampaigns/RollDisplay/RollResult.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { Box, Typography } from "@mui/material"; | ||
import { MarkdownRenderer } from "components/shared/MarkdownRenderer"; | ||
|
||
export interface RollResultProps { | ||
result?: string; | ||
markdown?: string; | ||
extras?: string[]; | ||
} | ||
|
||
export function RollResult(props: RollResultProps) { | ||
const { result, markdown, extras } = props; | ||
|
||
return ( | ||
<Box | ||
display={"flex"} | ||
flexDirection={"column"} | ||
alignItems={"flex-start"} | ||
justifyContent={"center"} | ||
> | ||
{result && ( | ||
<Typography | ||
color={"white"} | ||
variant={"h5"} | ||
component={"p"} | ||
fontFamily={(theme) => theme.fontFamilyTitle} | ||
> | ||
{result} | ||
</Typography> | ||
)} | ||
{markdown && ( | ||
<MarkdownRenderer markdown={markdown} inheritColor disableLinks /> | ||
)} | ||
{Array.isArray(extras) && | ||
extras.map((extra) => ( | ||
<Typography | ||
key={extra} | ||
color={(theme) => theme.palette.grey[200]} | ||
variant={"caption"} | ||
component={"p"} | ||
fontFamily={(theme) => theme.fontFamilyTitle} | ||
> | ||
{extra} | ||
</Typography> | ||
))} | ||
</Box> | ||
); | ||
} |
Oops, something went wrong.