Simplify changing reader desktop quick settings
This commit is contained in:
@@ -29,7 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
- (**Migration**) Change migration match exclude/include icons
|
||||
- (**Migration**) Show the exclude/include button only for an entry with a selected match
|
||||
- (**Source/Extension**) Rename language "All" to "Multi"
|
||||
-
|
||||
- (**Reader**) Simplify changing settings in desktop sidebar
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -6,12 +6,20 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import type { StackProps } from '@mui/material/Stack';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import type { ButtonProps } from '@mui/material/Button';
|
||||
import Button from '@mui/material/Button';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
import { Superscript } from '@/base/components/texts/Superscript.tsx';
|
||||
import type { ValueToDisplayData } from '@/base/Base.types.ts';
|
||||
import type { ReactNode, RefObject } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
||||
import { assertIsDefined } from '@/base/Asserts.ts';
|
||||
import RestartAltIcon from '@mui/icons-material/RestartAlt';
|
||||
import { useElementSize } from '@mantine/hooks';
|
||||
|
||||
export interface SelectButtonBaseProps<Value extends string | number, MultiValue extends Value | Value[] = Value> {
|
||||
value: MultiValue;
|
||||
@@ -19,6 +27,9 @@ export interface SelectButtonBaseProps<Value extends string | number, MultiValue
|
||||
values: Value[];
|
||||
setValue: (value: MultiValue) => void;
|
||||
valueToDisplayData: ValueToDisplayData<Value>;
|
||||
isCollapsible?: MultiValue extends Value[] ? never : boolean;
|
||||
tooltip?: MultiValue extends Value[] ? never : ReactNode;
|
||||
defaultIcon?: MultiValue extends Value[] ? never : ReactNode;
|
||||
}
|
||||
|
||||
export interface SelectButtonDefaultableProps<
|
||||
@@ -33,7 +44,8 @@ export type SelectButtonProps<Value extends string | number, MultiValue extends
|
||||
| (SelectButtonBaseProps<Value, MultiValue> & PropertiesNever<SelectButtonDefaultableProps<Value, MultiValue>>)
|
||||
| SelectButtonDefaultableProps<Value, MultiValue>;
|
||||
|
||||
export const SelectButton = <Value extends string | number, MultiValue extends Value | Value[] = Value>({
|
||||
const SelectButtonBase = <Value extends string | number, MultiValue extends Value | Value[] = Value>({
|
||||
ref,
|
||||
value,
|
||||
values,
|
||||
defaultValue,
|
||||
@@ -41,15 +53,37 @@ export const SelectButton = <Value extends string | number, MultiValue extends V
|
||||
valueToDisplayData,
|
||||
isDefaultable,
|
||||
onDefault,
|
||||
}: SelectButtonProps<Value, MultiValue>) => {
|
||||
slotProps,
|
||||
}: SelectButtonProps<Value, MultiValue> & {
|
||||
ref?: RefObject<HTMLDivElement>;
|
||||
slotProps?: {
|
||||
stack?: StackProps;
|
||||
defaultButton?: ButtonProps & { hideText?: boolean };
|
||||
button?: ButtonProps & { hideText?: boolean; tooltip?: (isDefault: boolean, title: string) => ReactNode };
|
||||
};
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<Stack sx={{ flexDirection: 'row', flexWrap: 'wrap', gap: 1 }}>
|
||||
<Stack
|
||||
{...slotProps?.stack}
|
||||
ref={ref}
|
||||
sx={{ flexDirection: 'row', flexWrap: 'wrap', gap: 1, ...slotProps?.stack?.sx }}
|
||||
>
|
||||
{isDefaultable && (
|
||||
<Button key="default" onClick={onDefault} variant={value === undefined ? 'contained' : 'outlined'}>
|
||||
{t`Default`}
|
||||
<CustomTooltip title={slotProps?.defaultButton?.hideText ? t`Default` : null}>
|
||||
<Button
|
||||
onClick={onDefault}
|
||||
variant={value === undefined ? 'contained' : 'outlined'}
|
||||
{...slotProps?.defaultButton}
|
||||
startIcon={!slotProps?.defaultButton?.hideText && slotProps?.defaultButton?.startIcon}
|
||||
endIcon={!slotProps?.defaultButton?.hideText && slotProps?.defaultButton?.endIcon}
|
||||
>
|
||||
{!slotProps?.defaultButton?.hideText
|
||||
? t`Default`
|
||||
: (slotProps?.defaultButton?.startIcon ?? slotProps?.defaultButton?.endIcon)}
|
||||
</Button>
|
||||
</CustomTooltip>
|
||||
)}
|
||||
{values.map((displayValue) => {
|
||||
const isDefault = value === undefined && displayValue === defaultValue;
|
||||
@@ -74,13 +108,27 @@ export const SelectButton = <Value extends string | number, MultiValue extends V
|
||||
: t(valueToDisplayData[displayValue].title);
|
||||
|
||||
return (
|
||||
<CustomTooltip key={displayValue} title={isDefault ? t`Active setting` : ''}>
|
||||
<Button
|
||||
onClick={() => setValue(newValue)}
|
||||
variant={isSelected ? 'contained' : 'outlined'}
|
||||
startIcon={valueToDisplayData[displayValue].icon}
|
||||
<CustomTooltip
|
||||
key={displayValue}
|
||||
title={slotProps?.button?.tooltip?.(isDefault, text) ?? (isDefault ? t`Active setting` : '')}
|
||||
>
|
||||
{isDefault ? <Superscript superscript="*" text={text} /> : text}
|
||||
<Button
|
||||
variant={isSelected ? 'contained' : 'outlined'}
|
||||
startIcon={!slotProps?.button?.hideText && valueToDisplayData[displayValue].icon}
|
||||
{...slotProps?.button}
|
||||
onClick={() => setValue(newValue)}
|
||||
>
|
||||
{(() => {
|
||||
if (slotProps?.button?.hideText) {
|
||||
return isDefault ? (
|
||||
<Superscript superscript="*" text={valueToDisplayData[displayValue].icon} />
|
||||
) : (
|
||||
valueToDisplayData[displayValue].icon
|
||||
);
|
||||
}
|
||||
|
||||
return isDefault ? <Superscript superscript="*" text={text} /> : text;
|
||||
})()}
|
||||
</Button>
|
||||
</CustomTooltip>
|
||||
);
|
||||
@@ -88,3 +136,108 @@ export const SelectButton = <Value extends string | number, MultiValue extends V
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const SelectButtonCollapsible = <Value extends string | number, MultiValue extends Value | Value[] = Value>(
|
||||
props: SelectButtonProps<Value, MultiValue>,
|
||||
) => {
|
||||
const { tooltip, value, valueToDisplayData, defaultValue, onDefault, setValue, defaultIcon } = props;
|
||||
|
||||
const { t } = useLingui();
|
||||
const { ref, height } = useElementSize();
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const finalOnDefault = useCallback(() => {
|
||||
setIsExpanded(false);
|
||||
onDefault?.();
|
||||
}, [onDefault]);
|
||||
|
||||
const finalSetValue = useCallback(
|
||||
(...args: Parameters<typeof setValue>) => {
|
||||
setIsExpanded(false);
|
||||
setValue(...args);
|
||||
},
|
||||
[setValue],
|
||||
);
|
||||
|
||||
if (!isExpanded) {
|
||||
return (
|
||||
<CustomTooltip title={tooltip}>
|
||||
<Button
|
||||
ref={ref}
|
||||
onClick={() => setIsExpanded(true)}
|
||||
variant="contained"
|
||||
size="large"
|
||||
startIcon={defaultIcon}
|
||||
sx={{ justifyContent: 'start', textTransform: 'unset', flexGrow: 1 }}
|
||||
>
|
||||
{(() => {
|
||||
const currentValue = (value as Value) ?? defaultValue;
|
||||
|
||||
assertIsDefined(currentValue);
|
||||
|
||||
const { title } = valueToDisplayData[currentValue];
|
||||
|
||||
return (
|
||||
<Superscript
|
||||
superscript={`(${t`Default`})`}
|
||||
text={typeof title === 'string' ? title : t(title)}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
</Button>
|
||||
</CustomTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ClickAwayListener onClickAway={() => setIsExpanded(false)}>
|
||||
<SelectButtonBase
|
||||
{...props}
|
||||
onDefault={finalOnDefault}
|
||||
setValue={finalSetValue}
|
||||
slotProps={{
|
||||
stack: {
|
||||
sx: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
},
|
||||
defaultButton: {
|
||||
hideText: true,
|
||||
startIcon: <RestartAltIcon />,
|
||||
size: 'large',
|
||||
sx: {
|
||||
flexGrow: 1,
|
||||
height,
|
||||
minWidth: 'unset',
|
||||
px: '10px',
|
||||
},
|
||||
},
|
||||
button: {
|
||||
hideText: true,
|
||||
tooltip: (isDefault, title) => (isDefault ? t`Active setting (${title})` : title),
|
||||
size: 'large',
|
||||
sx: {
|
||||
flexGrow: 1,
|
||||
height,
|
||||
minWidth: 'unset',
|
||||
px: '10px',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ClickAwayListener>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectButton = <Value extends string | number, MultiValue extends Value | Value[] = Value>(
|
||||
props: SelectButtonProps<Value, MultiValue>,
|
||||
) => {
|
||||
const { isCollapsible } = props;
|
||||
|
||||
if (isCollapsible) {
|
||||
return <SelectButtonCollapsible {...props} />;
|
||||
}
|
||||
|
||||
return <SelectButtonBase {...props} />;
|
||||
};
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface SuperscriptProps {
|
||||
superscript: string;
|
||||
text: string;
|
||||
text: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,6 @@ import Stack from '@mui/material/Stack';
|
||||
import FitScreenIcon from '@mui/icons-material/FitScreen';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
import { ValueRotationButton } from '@/base/components/buttons/ValueRotationButton.tsx';
|
||||
import type {
|
||||
IReaderSettings,
|
||||
IReaderSettingsWithDefaultFlag,
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
} from '@/features/reader/settings/ReaderSettings.constants.tsx';
|
||||
import { CustomIconButton } from '@/base/components/buttons/CustomIconButton.tsx';
|
||||
import type { SelectButtonDefaultableProps } from '@/base/components/buttons/SelectButton.tsx';
|
||||
import { SelectButton } from '@/base/components/buttons/SelectButton.tsx';
|
||||
|
||||
export const ReaderNavBarDesktopPageScale = ({
|
||||
pageScaleMode,
|
||||
@@ -40,7 +40,7 @@ export const ReaderNavBarDesktopPageScale = ({
|
||||
|
||||
return (
|
||||
<Stack sx={{ flexDirection: 'row', gap: 1 }}>
|
||||
<ValueRotationButton
|
||||
<SelectButton<ReaderPageScaleMode>
|
||||
{...buttonSelectInputProps}
|
||||
tooltip={t`Scale type`}
|
||||
value={pageScaleMode.isDefault ? undefined : pageScaleMode.value}
|
||||
@@ -49,6 +49,7 @@ export const ReaderNavBarDesktopPageScale = ({
|
||||
setValue={(value) => updateSetting('pageScaleMode', value)}
|
||||
valueToDisplayData={PAGE_SCALE_VALUE_TO_DISPLAY_DATA}
|
||||
defaultIcon={PAGE_SCALE_VALUE_TO_DISPLAY_DATA[pageScaleMode.value].icon}
|
||||
isCollapsible
|
||||
/>
|
||||
{READER_PAGE_SCALE_MODE_TO_SCALING_ALLOWED[pageScaleMode.value] && (
|
||||
<CustomTooltip title={t`Stretch small pages`}>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { ValueRotationButton } from '@/base/components/buttons/ValueRotationButton.tsx';
|
||||
import type { IReaderSettingsWithDefaultFlag, ReadingDirection } from '@/features/reader/Reader.types.ts';
|
||||
import {
|
||||
READING_DIRECTION_VALUES,
|
||||
@@ -15,6 +14,7 @@ import {
|
||||
} from '@/features/reader/settings/ReaderSettings.constants.tsx';
|
||||
|
||||
import type { SelectButtonDefaultableProps } from '@/base/components/buttons/SelectButton.tsx';
|
||||
import { SelectButton } from '@/base/components/buttons/SelectButton.tsx';
|
||||
|
||||
export const ReaderNavBarDesktopReadingDirection = ({
|
||||
readingDirection,
|
||||
@@ -27,7 +27,7 @@ export const ReaderNavBarDesktopReadingDirection = ({
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<ValueRotationButton
|
||||
<SelectButton
|
||||
{...buttonSelectInputProps}
|
||||
tooltip={t`Reading direction`}
|
||||
value={readingDirection.isDefault ? undefined : readingDirection.value}
|
||||
@@ -36,6 +36,7 @@ export const ReaderNavBarDesktopReadingDirection = ({
|
||||
setValue={setReadingDirection}
|
||||
valueToDisplayData={READING_DIRECTION_VALUE_TO_DISPLAY_DATA}
|
||||
defaultIcon={READING_DIRECTION_VALUE_TO_DISPLAY_DATA[readingDirection.value].icon}
|
||||
isCollapsible
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { ValueRotationButton } from '@/base/components/buttons/ValueRotationButton.tsx';
|
||||
import type { IReaderSettingsWithDefaultFlag, ReadingMode } from '@/features/reader/Reader.types.ts';
|
||||
import {
|
||||
READING_MODE_VALUE_TO_DISPLAY_DATA,
|
||||
@@ -15,6 +14,7 @@ import {
|
||||
} from '@/features/reader/settings/ReaderSettings.constants.tsx';
|
||||
|
||||
import type { SelectButtonDefaultableProps } from '@/base/components/buttons/SelectButton.tsx';
|
||||
import { SelectButton } from '@/base/components/buttons/SelectButton.tsx';
|
||||
|
||||
export const ReaderNavBarDesktopReadingMode = ({
|
||||
readingMode,
|
||||
@@ -27,7 +27,7 @@ export const ReaderNavBarDesktopReadingMode = ({
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<ValueRotationButton
|
||||
<SelectButton
|
||||
{...buttonSelectInputProps}
|
||||
tooltip={t`Reading mode`}
|
||||
value={readingMode.isDefault ? undefined : readingMode.value}
|
||||
@@ -36,6 +36,7 @@ export const ReaderNavBarDesktopReadingMode = ({
|
||||
setValue={setReadingMode}
|
||||
valueToDisplayData={READING_MODE_VALUE_TO_DISPLAY_DATA}
|
||||
defaultIcon={READING_MODE_VALUE_TO_DISPLAY_DATA[readingMode.value].icon}
|
||||
isCollapsible
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -358,6 +358,10 @@ msgstr "Active device"
|
||||
msgid "Active setting"
|
||||
msgstr "Active setting"
|
||||
|
||||
#: src/base/components/buttons/SelectButton.tsx
|
||||
msgid "Active setting ({title})"
|
||||
msgstr "Active setting ({title})"
|
||||
|
||||
#: src/base/components/settings/MutableListSetting.tsx
|
||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||
#: src/features/reader/hotkeys/settings/components/ReaderSettingHotkey.tsx
|
||||
|
||||
Reference in New Issue
Block a user