Fixes some character prefs issues (#80721)

## About The Pull Request
Issue with #80719 was resolved by using `TrackOutsideClicks` component
rather than the new `onOutsideClick` prop on `Popper`. I think it was
getting confused due to the fact it's nested - first the popup, then the
dropdown. The dropdown selection is working.

Issue #80689 was resolved by fixing the props on `Dropdown` &
`RandomizationButton`. Width and color specifically.

Now for why I'm asking for GBP for this...
#79251 added features to quirks which allowed for dropdown
customization. It's a cool concept, but its implementation is very
complex. I extracted components out of this into a simpler format which
I think is wholly better than calling useState within .map. Even with
some props drilling now in its place - I think it's a better
alternative.
## Why It's Good For The Game
Bug fixes
Fixes #80719
Fixes #80689
## Changelog
🆑
fix: Randomization button in prefs should look normal again.
fix: Quirk customization shouldn't close immediately.
/🆑
This commit is contained in:
Jeremiah
2024-01-05 01:59:20 +01:00
committed by GitHub
parent 40c4941b38
commit 9dfc098cff
3 changed files with 282 additions and 224 deletions
+4 -8
View File
@@ -1,7 +1,7 @@
import { classes } from 'common/react';
import { ReactNode, useState } from 'react';
import { BoxProps } from './Box';
import { Box, BoxProps } from './Box';
import { Button } from './Button';
import { Icon } from './Icon';
import { Popper } from './Popper';
@@ -55,6 +55,7 @@ export function Dropdown(props: Props) {
options = [],
over,
selected,
width,
} = props;
const [open, setOpen] = useState(false);
@@ -129,12 +130,7 @@ export function Dropdown(props: Props) {
</div>
}
>
<div
className="Dropdown"
style={{
minWidth: menuWidth,
}}
>
<Box className="Dropdown" width={width}>
<div
className={classes([
'Dropdown__control',
@@ -191,7 +187,7 @@ export function Dropdown(props: Props) {
/>
</>
)}
</div>
</Box>
</Popper>
);
}
@@ -1,13 +1,22 @@
import { filterMap } from 'common/collections';
import { useState } from 'react';
import { useBackend, useLocalState } from '../../backend';
import { Box, Button, Icon, Popper, Stack, Tooltip } from '../../components';
import { useBackend } from '../../backend';
import {
Box,
Button,
Icon,
Popper,
Stack,
Tooltip,
TrackOutsideClicks,
} from '../../components';
import { PreferencesMenuData, Quirk, RandomSetting, ServerData } from './data';
import { getRandomization, PreferenceList } from './MainPage';
import { ServerPreferencesFetcher } from './ServerPreferencesFetcher';
import { useRandomToggleState } from './useRandomToggleState';
const getValueClass = (value: number): string => {
function getValueClass(value: number) {
if (value > 0) {
return 'positive';
} else if (value < 0) {
@@ -15,12 +24,12 @@ const getValueClass = (value: number): string => {
} else {
return 'neutral';
}
};
}
const getCorrespondingPreferences = (
function getCorrespondingPreferences(
customization_options: string[],
relevant_preferences: Record<string, string>,
): Record<string, unknown> => {
) {
return Object.fromEntries(
filterMap(Object.keys(relevant_preferences), (key) => {
if (!customization_options.includes(key)) {
@@ -30,202 +39,256 @@ const getCorrespondingPreferences = (
return [key, relevant_preferences[key]];
}),
);
}
type QuirkEntry = [string, Quirk & { failTooltip?: string }];
type QuirkListProps = {
quirks: QuirkEntry[];
};
const QuirkList = (props: {
quirks: [
string,
Quirk & {
failTooltip?: string;
},
][];
type QuirkProps = {
// eslint-disable-next-line react/no-unused-prop-types
onClick: (quirkName: string, quirk: Quirk) => void;
randomBodyEnabled: boolean;
selected: boolean;
serverData: ServerData;
randomBodyEnabled: boolean;
}) => {
const { act, data } = useBackend<PreferencesMenuData>();
};
function QuirkList(props: QuirkProps & QuirkListProps) {
const {
quirks = [],
selected,
onClick,
serverData,
randomBodyEnabled,
} = props;
return (
// Stack is not used here for a variety of IE flex bugs
<Box className="PreferencesMenu__Quirks__QuirkList">
{props.quirks.map(([quirkKey, quirk]) => {
const [customizationExpanded, setCustomizationExpanded] =
useLocalState<boolean>(quirk.name + ' customization', false);
const className = 'PreferencesMenu__Quirks__QuirkList__quirk';
const hasExpandableCustomization =
quirk.customizable &&
props.selected &&
customizationExpanded &&
quirk.customization_options &&
Object.entries(quirk.customization_options).length > 0;
const child = (
<Box
className={className}
key={quirkKey}
onClick={() => {
if (props.selected) {
setCustomizationExpanded(false);
}
props.onClick(quirkKey, quirk);
}}
>
<Stack fill>
<Stack.Item
align="center"
style={{
minWidth: '15%',
maxWidth: '15%',
textAlign: 'center',
}}
>
<Icon color="#333" fontSize={3} name={quirk.icon} />
</Stack.Item>
<Stack.Item
align="stretch"
ml={0}
style={{
borderRight: '1px solid black',
}}
/>
<Stack.Item
grow
ml={0}
style={{
// Fixes an IE bug for text overflowing in Flex boxes
minWidth: '0%',
}}
>
<Stack vertical fill>
<Stack.Item
className={`${className}--${getValueClass(quirk.value)}`}
style={{
borderBottom: '1px solid black',
padding: '2px',
}}
>
<Stack
fill
style={{
fontSize: '1.2em',
}}
>
<Stack.Item grow basis="content">
<b>{quirk.name}</b>
</Stack.Item>
<Stack.Item>
<b>{quirk.value}</b>
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item
grow
basis="content"
mt={0}
style={{
padding: '3px',
}}
>
{quirk.description}
{!!quirk.customizable && (
<Popper
placement="bottom-end"
isOpen={customizationExpanded}
onClickOutside={() => {
setCustomizationExpanded(false);
}}
popperContent={
<Box>
{!!quirk.customization_options &&
hasExpandableCustomization && (
<Box
mt="1px"
style={{
boxShadow:
'0px 4px 8px 3px rgba(0, 0, 0, 0.7)',
}}
>
<Stack
onClick={(e) => {
e.stopPropagation();
}}
maxWidth="300px"
backgroundColor="black"
px="5px"
py="3px"
>
<Stack.Item>
<PreferenceList
act={act}
preferences={getCorrespondingPreferences(
quirk.customization_options,
data.character_preferences
.manually_rendered_features,
)}
randomizations={getRandomization(
getCorrespondingPreferences(
quirk.customization_options,
data.character_preferences
.manually_rendered_features,
),
props.serverData,
props.randomBodyEnabled,
)}
maxHeight="100px"
/>
</Stack.Item>
</Stack>
</Box>
)}
</Box>
}
>
{props.selected && (
<Button
selected={customizationExpanded}
icon="cog"
tooltip="Customize"
onClick={(e) => {
e.stopPropagation();
setCustomizationExpanded(!customizationExpanded);
}}
style={{
float: 'right',
}}
/>
)}
</Popper>
)}
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Box>
);
if (quirk.failTooltip) {
return (
<Tooltip key={quirkKey} content={quirk.failTooltip}>
{child}
</Tooltip>
);
} else {
return child;
}
})}
{quirks.map(([quirkKey, quirk]) => (
<QuirkDisplay
key={quirkKey}
onClick={onClick}
quirk={quirk}
quirkKey={quirkKey}
randomBodyEnabled={randomBodyEnabled}
selected={selected}
serverData={serverData}
/>
))}
</Box>
);
};
}
type QuirkDisplayProps = {
quirk: Quirk & { failTooltip?: string };
// bugged
// eslint-disable-next-line react/no-unused-prop-types
quirkKey: string;
} & QuirkProps;
function QuirkDisplay(props: QuirkDisplayProps) {
const { quirk, quirkKey, onClick, selected } = props;
const { icon, value, name, description, customizable, failTooltip } = quirk;
const [customizationExpanded, setCustomizationExpanded] = useState(false);
const className = 'PreferencesMenu__Quirks__QuirkList__quirk';
const child = (
<Box
className={className}
onClick={(event) => {
event.stopPropagation();
if (selected) {
setCustomizationExpanded(false);
}
onClick(quirkKey, quirk);
}}
>
<Stack fill>
<Stack.Item
align="center"
style={{
minWidth: '15%',
maxWidth: '15%',
textAlign: 'center',
}}
>
<Icon color="#333" fontSize={3} name={icon} />
</Stack.Item>
<Stack.Item
align="stretch"
ml={0}
style={{
borderRight: '1px solid black',
}}
/>
<Stack.Item
grow
ml={0}
style={{
// Fixes an IE bug for text overflowing in Flex boxes
minWidth: '0%',
}}
>
<Stack vertical fill>
<Stack.Item
className={`${className}--${getValueClass(value)}`}
style={{
borderBottom: '1px solid black',
padding: '2px',
}}
>
<Stack
fill
style={{
fontSize: '1.2em',
}}
>
<Stack.Item grow basis="content">
<b>{name}</b>
</Stack.Item>
<Stack.Item>
<b>{value}</b>
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item
grow
basis="content"
mt={0}
style={{
padding: '3px',
}}
>
{description}
{!!customizable && (
<QuirkPopper
{...props}
customizationExpanded={customizationExpanded}
setCustomizationExpanded={setCustomizationExpanded}
/>
)}
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Box>
);
if (failTooltip) {
return <Tooltip content={failTooltip}>{child}</Tooltip>;
} else {
return child;
}
}
type QuirkPopperProps = {
customizationExpanded: boolean;
setCustomizationExpanded: (expanded: boolean) => void;
} & QuirkDisplayProps;
function QuirkPopper(props: QuirkPopperProps) {
const { act, data } = useBackend<PreferencesMenuData>();
const {
customizationExpanded,
quirk,
randomBodyEnabled,
selected,
serverData,
setCustomizationExpanded,
} = props;
const { customizable, customization_options } = quirk;
const { character_preferences } = data;
const hasExpandableCustomization =
customizable &&
selected &&
customizationExpanded &&
customization_options &&
Object.entries(customization_options).length > 0;
return (
<Popper
placement="bottom-end"
isOpen={customizationExpanded}
popperContent={
<TrackOutsideClicks
onOutsideClick={() => setCustomizationExpanded(false)}
>
<Box>
{!!customization_options && hasExpandableCustomization && (
<Box
mt="1px"
style={{
boxShadow: '0px 4px 8px 3px rgba(0, 0, 0, 0.7)',
}}
>
<Stack
onClick={(e) => {
e.stopPropagation();
}}
maxWidth="300px"
backgroundColor="black"
px="5px"
py="3px"
>
<Stack.Item>
<PreferenceList
act={act}
preferences={getCorrespondingPreferences(
customization_options,
character_preferences.manually_rendered_features,
)}
randomizations={getRandomization(
getCorrespondingPreferences(
customization_options,
character_preferences.manually_rendered_features,
),
serverData,
randomBodyEnabled,
)}
maxHeight="100px"
/>
</Stack.Item>
</Stack>
</Box>
)}
</Box>
</TrackOutsideClicks>
}
>
{selected && (
<Button
selected={customizationExpanded}
icon="cog"
tooltip="Customize"
onClick={(e) => {
e.stopPropagation();
setCustomizationExpanded(!customizationExpanded);
}}
style={{
float: 'right',
}}
/>
)}
</Popper>
);
}
function StatDisplay(props) {
const { children } = props;
const StatDisplay = (props) => {
return (
<Box
backgroundColor="#eee"
@@ -235,12 +298,12 @@ const StatDisplay = (props) => {
px={3}
py={0.5}
>
{props.children}
{children}
</Box>
);
};
}
export const QuirksPage = (props) => {
export function QuirksPage(props) {
const { act, data } = useBackend<PreferencesMenuData>();
// this is mainly just here to copy from MainPage.tsx
@@ -249,10 +312,7 @@ export const QuirksPage = (props) => {
data.character_preferences.non_contextual.random_body !==
RandomSetting.Disabled || randomToggleEnabled;
const [selectedQuirks, setSelectedQuirks] = useLocalState(
`selectedQuirks_${data.active_slot}`,
data.selected_quirks,
);
const [selectedQuirks, setSelectedQuirks] = useState(data.selected_quirks);
return (
<ServerPreferencesFetcher
@@ -447,4 +507,4 @@ export const QuirksPage = (props) => {
}}
/>
);
};
}
@@ -3,6 +3,23 @@ import { exhaustiveCheck } from 'common/exhaustive';
import { Dropdown, Icon } from '../../components';
import { RandomSetting } from './data';
const options = [
{
displayText: 'Do not randomize',
value: RandomSetting.Disabled,
},
{
displayText: 'Always randomize',
value: RandomSetting.Enabled,
},
{
displayText: 'Randomize when antagonist',
value: RandomSetting.AntagOnly,
},
];
export const RandomizationButton = (props: {
dropdownProps?: Record<string, unknown>;
setValue: (newValue: RandomSetting) => void;
@@ -28,30 +45,15 @@ export const RandomizationButton = (props: {
return (
<Dropdown
backgroundColor={color}
color={color}
{...dropdownProps}
clipSelectedText={false}
displayText={<Icon name="dice-d20" mr="-0.25em" />}
options={[
{
displayText: 'Do not randomize',
value: RandomSetting.Disabled,
},
{
displayText: 'Always randomize',
value: RandomSetting.Enabled,
},
{
displayText: 'Randomize when antagonist',
value: RandomSetting.AntagOnly,
},
]}
options={options}
noChevron
onSelected={setValue}
menuWidth="120px"
width="auto"
width={1.85}
/>
);
};