forked from akai/readest
173 lines
6.2 KiB
TypeScript
173 lines
6.2 KiB
TypeScript
import clsx from 'clsx';
|
|
import React, { useState } from 'react';
|
|
import { FaCheckCircle } from 'react-icons/fa';
|
|
import { HighlightColor, HighlightStyle } from '@/types/book';
|
|
import { useEnv } from '@/context/EnvContext';
|
|
import { useThemeStore } from '@/store/themeStore';
|
|
import { useSettingsStore } from '@/store/settingsStore';
|
|
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
|
import { saveSysSettings } from '@/helpers/settings';
|
|
|
|
const styles = ['highlight', 'underline', 'squiggly'] as HighlightStyle[];
|
|
const colors = ['red', 'violet', 'blue', 'green', 'yellow'] as HighlightColor[];
|
|
|
|
interface HighlightOptionsProps {
|
|
isVertical: boolean;
|
|
popupWidth: number;
|
|
popupHeight: number;
|
|
triangleDir: 'up' | 'down' | 'left' | 'right';
|
|
selectedStyle: HighlightStyle;
|
|
selectedColor: HighlightColor;
|
|
onHandleHighlight: (update: boolean) => void;
|
|
}
|
|
|
|
const OPTIONS_HEIGHT_PIX = 28;
|
|
const OPTIONS_PADDING_PIX = 16;
|
|
|
|
const HighlightOptions: React.FC<HighlightOptionsProps> = ({
|
|
isVertical,
|
|
popupWidth,
|
|
popupHeight,
|
|
triangleDir,
|
|
selectedStyle: _selectedStyle,
|
|
selectedColor: _selectedColor,
|
|
onHandleHighlight,
|
|
}) => {
|
|
const { envConfig } = useEnv();
|
|
const { settings } = useSettingsStore();
|
|
const { isDarkMode } = useThemeStore();
|
|
const globalReadSettings = settings.globalReadSettings;
|
|
const isEink = settings.globalViewSettings.isEink;
|
|
const einkBgColor = isDarkMode ? '#000000' : '#ffffff';
|
|
const einkFgColor = isDarkMode ? '#ffffff' : '#000000';
|
|
const customColors = globalReadSettings.customHighlightColors;
|
|
const [selectedStyle, setSelectedStyle] = useState<HighlightStyle>(_selectedStyle);
|
|
const [selectedColor, setSelectedColor] = useState<HighlightColor>(_selectedColor);
|
|
const size16 = useResponsiveSize(16);
|
|
const size28 = useResponsiveSize(28);
|
|
const highlightOptionsHeightPx = useResponsiveSize(OPTIONS_HEIGHT_PIX);
|
|
const highlightOptionsPaddingPx = useResponsiveSize(OPTIONS_PADDING_PIX);
|
|
|
|
const handleSelectStyle = (style: HighlightStyle) => {
|
|
const newGlobalReadSettings = { ...globalReadSettings, highlightStyle: style };
|
|
saveSysSettings(envConfig, 'globalReadSettings', newGlobalReadSettings);
|
|
setSelectedStyle(style);
|
|
setSelectedColor(globalReadSettings.highlightStyles[style]);
|
|
onHandleHighlight(true);
|
|
};
|
|
const handleSelectColor = (color: HighlightColor) => {
|
|
const newGlobalReadSettings = {
|
|
...globalReadSettings,
|
|
highlightStyle: selectedStyle,
|
|
highlightStyles: { ...globalReadSettings.highlightStyles, [selectedStyle]: color },
|
|
};
|
|
saveSysSettings(envConfig, 'globalReadSettings', newGlobalReadSettings);
|
|
setSelectedColor(color);
|
|
onHandleHighlight(true);
|
|
};
|
|
return (
|
|
<div
|
|
className={clsx(
|
|
'highlight-options absolute flex items-center justify-between',
|
|
isVertical ? 'flex-col' : 'flex-row',
|
|
)}
|
|
style={{
|
|
width: `${popupWidth}px`,
|
|
height: `${popupHeight}px`,
|
|
...(isVertical
|
|
? {
|
|
left: `${
|
|
(highlightOptionsHeightPx + highlightOptionsPaddingPx) *
|
|
(triangleDir === 'left' ? -1 : 1)
|
|
}px`,
|
|
}
|
|
: {
|
|
top: `${
|
|
(highlightOptionsHeightPx + highlightOptionsPaddingPx) *
|
|
(triangleDir === 'up' ? -1 : 1)
|
|
}px`,
|
|
}),
|
|
}}
|
|
>
|
|
<div
|
|
className={clsx('flex gap-2', isVertical ? 'flex-col' : 'flex-row')}
|
|
style={isVertical ? { width: size28 } : { height: size28 }}
|
|
>
|
|
{styles.map((style) => (
|
|
<button
|
|
key={style}
|
|
onClick={() => handleSelectStyle(style)}
|
|
className='not-eink:bg-gray-700 eink-bordered flex items-center justify-center rounded-full p-0'
|
|
style={{ width: size28, height: size28, minHeight: size28 }}
|
|
>
|
|
<div
|
|
style={{
|
|
width: size16,
|
|
height: size16,
|
|
...(style === 'highlight' &&
|
|
selectedStyle === 'highlight' && {
|
|
backgroundColor: isEink ? einkFgColor : customColors[selectedColor],
|
|
color: isEink ? einkBgColor : '#d1d5db',
|
|
paddingTop: '2px',
|
|
}),
|
|
...(style === 'highlight' &&
|
|
selectedStyle !== 'highlight' && {
|
|
backgroundColor: '#d1d5db',
|
|
paddingTop: '2px',
|
|
}),
|
|
...((style === 'underline' || style === 'squiggly') && {
|
|
color: isEink ? einkFgColor : '#d1d5db',
|
|
textDecoration: 'underline',
|
|
textDecorationThickness: '2px',
|
|
textDecorationColor:
|
|
selectedStyle === style
|
|
? isEink
|
|
? einkFgColor
|
|
: customColors[selectedColor]
|
|
: '#d1d5db',
|
|
...(style === 'squiggly' && { textDecorationStyle: 'wavy' }),
|
|
}),
|
|
}}
|
|
className='w-4 p-0 text-center leading-none'
|
|
>
|
|
A
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<div
|
|
className={clsx(
|
|
'not-eink:bg-gray-700 eink-bordered flex items-center justify-center gap-2 rounded-3xl',
|
|
isVertical ? 'flex-col py-2' : 'flex-row px-2',
|
|
)}
|
|
style={isVertical ? { width: size28 } : { height: size28 }}
|
|
>
|
|
{colors
|
|
.filter((c) => (isEink ? selectedColor === c : true))
|
|
.map((color) => (
|
|
<button
|
|
key={color}
|
|
onClick={() => handleSelectColor(color)}
|
|
style={{
|
|
width: size16,
|
|
height: size16,
|
|
backgroundColor: selectedColor !== color ? customColors[color] : 'transparent',
|
|
}}
|
|
className='rounded-full p-0'
|
|
>
|
|
{selectedColor === color && (
|
|
<FaCheckCircle
|
|
size={size16}
|
|
style={{ fill: isEink ? einkFgColor : customColors[color] }}
|
|
/>
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default HighlightOptions;
|