Skip to content

Commit

Permalink
Merge branch 'main' into prod
Browse files Browse the repository at this point in the history
  • Loading branch information
ItsSammyM committed Jan 14, 2025
2 parents 3bb2b00 + d93e3d0 commit 2780a97
Show file tree
Hide file tree
Showing 71 changed files with 2,513 additions and 1,022 deletions.
394 changes: 373 additions & 21 deletions LICENSE

Large diffs are not rendered by default.

88 changes: 51 additions & 37 deletions client/src/components/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import DOMPurify from "dompurify";
import GraveComponent from "./grave";
import { RoleOutline, translateRoleOutline } from "../game/roleListState.d";
import { CopyButton } from "./ClipboardButtons";
import { useGameState, useLobbyOrGameState, usePlayerState } from "./useHooks";
import { useGameState, useLobbyOrGameState, usePlayerNames, usePlayerState, useSpectator } from "./useHooks";
import { KiraResult, KiraResultDisplay } from "../menu/game/gameScreenContent/AbilityMenu/AbilitySelectionTypes/KiraSelectionMenu";
import { AuditorResult } from "../menu/game/gameScreenContent/AbilityMenu/RoleSpecificMenus/AuditorMenu";
import { ControllerID, AbilitySelection, translateControllerID, controllerIdToLink } from "../game/abilityInput";
Expand All @@ -37,7 +37,8 @@ const ChatElement = React.memo((
const [mouseHovering, setMouseHovering] = React.useState(false);

const message = props.message;
const playerNames = props.playerNames ?? GAME_MANAGER.getPlayerNames();
const realPlayerNames = usePlayerNames();
const playerNames = props.playerNames ?? realPlayerNames;
const chatMessageStyles = require("../resources/styling/chatMessage.json");
if(message.variant === undefined){
console.error("ChatElement message with undefined variant:");
Expand Down Expand Up @@ -132,33 +133,13 @@ const ChatElement = React.memo((
/>
</div>
case "playerDied":

let graveRoleString: string;
switch (message.variant.grave.information.type) {
case "obscured":
graveRoleString = translate("obscured");
break;
case "normal":
graveRoleString = translate("role."+message.variant.grave.information.role+".name");
break;
}

return <div className={"chat-message-div"}>
<DetailsSummary
summary={
<StyledText className={"chat-message " + style}
playerKeywordData={props.playerKeywordData}
>
{(chatGroupIcon??"")} {translate("chatMessage.playerDied",
playerNames[message.variant.grave.player], graveRoleString
)}
</StyledText>
}
defaultOpen={GAME_MANAGER.getMySpectator()}
>
<GraveComponent grave={message.variant.grave} playerNames={playerNames}/>
</DetailsSummary>
</div>;
return <PlayerDiedChatMessage
playerKeywordData={props.playerKeywordData}
style={style}
chatGroupIcon={chatGroupIcon}
playerNames={playerNames}
message={message as any}
/>
}

return <div
Expand All @@ -179,6 +160,43 @@ const ChatElement = React.memo((
</div>;
});

function PlayerDiedChatMessage(props: Readonly<{
playerKeywordData?: KeywordDataMap,
style: string,
chatGroupIcon: string | null,
playerNames: string[],
message: ChatMessage & { variant: { type: "playerDied" } }
}>): ReactElement {
let graveRoleString: string;
switch (props.message.variant.grave.information.type) {
case "obscured":
graveRoleString = translate("obscured");
break;
case "normal":
graveRoleString = translate("role."+props.message.variant.grave.information.role+".name");
break;
}

const spectator = useSpectator();

return <div className={"chat-message-div"}>
<DetailsSummary
summary={
<StyledText className={"chat-message " + props.style}
playerKeywordData={props.playerKeywordData}
>
{(props.chatGroupIcon ?? "")} {translate("chatMessage.playerDied",
props.playerNames[props.message.variant.grave.player], graveRoleString
)}
</StyledText>
}
defaultOpen={spectator}
>
<GraveComponent grave={props.message.variant.grave} playerNames={props.playerNames}/>
</DetailsSummary>
</div>;
}

function LobbyChatMessage(props: Readonly<{
message: ChatMessage & { variant: { type: "lobbyMessage" } }
playerNames: string[],
Expand Down Expand Up @@ -326,14 +344,9 @@ export function sanitizePlayerMessage(text: string): string {

export function translateChatMessage(
message: ChatMessageVariant,
playerNames?: string[],
playerNames: string[],
roleList?: RoleOutline[]
): string {

if (playerNames === undefined) {
playerNames = GAME_MANAGER.getPlayerNames();
}

switch (message.type) {
case "lobbyMessage":
return sanitizePlayerMessage(replaceMentions(message.text, playerNames));
Expand Down Expand Up @@ -386,7 +399,7 @@ export function translateChatMessage(
);
}
case "playerQuit":
return translate("chatMessage.playerQuit",
return translate(`chatMessage.playerQuit${message.gameOver ? ".gameOver" : ""}`,
playerNames[message.playerIndex]
);
case "youDied":
Expand Down Expand Up @@ -522,7 +535,7 @@ export function translateChatMessage(
out = translate("chatMessage.abilityUsed.selection.twoRoleOutlineOption", first, second);
break;
case "string":
out = translate("chatMessage.abilityUsed.selection.string", sanitizePlayerMessage(replaceMentions(message.selection.selection)));
out = translate("chatMessage.abilityUsed.selection.string", sanitizePlayerMessage(replaceMentions(message.selection.selection, playerNames)));
break;
case "integer":
let text = translateChecked("controllerId."+controllerIdToLink(message.abilityId).replace(/\//g, ".") + ".integer." + message.selection.selection);
Expand Down Expand Up @@ -821,6 +834,7 @@ export type ChatMessageVariant = {
} | {
type: "playerQuit",
playerIndex: PlayerIndex
gameOver: boolean,
} | {
type: "phaseChange",
phase: PhaseState,
Expand Down
127 changes: 127 additions & 0 deletions client/src/components/Popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React, { ReactElement, ReactNode, useEffect, useMemo, useRef } from "react";
import ReactDOM from "react-dom/client";
import { THEME_CSS_ATTRIBUTES } from "..";

export default function Popover<T extends HTMLElement = HTMLElement>(props: Readonly<{
open: boolean,
children: ReactNode,
setOpenOrClosed: (open: boolean) => void,
onRender?: (popoverElement: HTMLDivElement, anchorElement?: T | undefined) => void
anchorRef?: React.RefObject<T>,
className?: string
}>): ReactElement {
const thisRef = useRef<HTMLDivElement>(null);
const popoverRef = useRef<HTMLDivElement>(document.createElement('div'));

const popoverRoot = useMemo(() => {
const popoverElement = popoverRef.current;
popoverElement.style.position = "absolute";

document.body.appendChild(popoverElement);
return ReactDOM.createRoot(popoverElement);
}, [])

//set ref
useEffect(() => {
const initialPopover = popoverRef.current;
return () => {
setTimeout(() => {
popoverRoot.unmount();
})
initialPopover.remove();

popoverRef.current = document.createElement('div');
}
}, [popoverRoot])

//match css styles
useEffect(() => {
const styleBenefactor = props.anchorRef?.current ?? thisRef.current;
const popoverElement = popoverRef.current;

if (styleBenefactor) {
// Match styles
THEME_CSS_ATTRIBUTES.forEach(prop => {
popoverElement.style.setProperty(`--${prop}`, getComputedStyle(styleBenefactor).getPropertyValue(`--${prop}`))
})

popoverElement.className = 'popover ' + (props.className ?? '')
}
}, [props.anchorRef, props.className])

// This is for the popover's anchor, not the element named Anchor
const [anchorLocation, setAnchorLocation] = React.useState(() => {
const bounds = props.anchorRef?.current?.getBoundingClientRect();

if (bounds) {
return { top: bounds.top, left: bounds.left }
} else {
return {top: 0, left: 0}
}
});

//close on scroll
useEffect(() => {
const listener = () => {
const bounds = props.anchorRef?.current?.getBoundingClientRect();
if (
bounds &&
props.open &&
(
anchorLocation.top !== bounds?.top ||
anchorLocation.left !== bounds?.left
)
)
props.setOpenOrClosed(false);
};

window.addEventListener("scroll", listener, true);
window.addEventListener("resize", listener);
return () => {
window.removeEventListener("scroll", listener, true);
window.removeEventListener("resize", listener);
}
})

//open and set position
useEffect(() => {
const popoverElement = popoverRef.current;
const anchorElement = props.anchorRef?.current;

if (props.open) {
popoverRoot.render(props.children);

if (anchorElement) {
const anchorBounds = anchorElement.getBoundingClientRect();

setAnchorLocation({top: anchorBounds.top, left: anchorBounds.left});
}

setTimeout(() => {
popoverElement.hidden = false;

if (props.onRender) {
props.onRender(popoverElement, anchorElement ?? undefined)
}
})
} else {
popoverElement.hidden = true;
}
}, [props, popoverRoot])

//close on click outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (!popoverRef.current?.contains(event.target as Node) && props.open) {
props.setOpenOrClosed(false);
}
};

setTimeout(() => {
document.addEventListener("click", handleClickOutside);
})
return () => document.removeEventListener("click", handleClickOutside);
}, [props]);

return <div ref={thisRef} />
}
Loading

0 comments on commit 2780a97

Please sign in to comment.