mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-30 16:47:13 +01:00
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:
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user