forked from akai/readest
feat: add an option to invert image color in dark mode (#1223)
This commit is contained in:
@@ -324,5 +324,6 @@
|
||||
"Translated by {{provider}}.": "ترجم بواسطة {{provider}}.",
|
||||
"DeepL": "ديب إل",
|
||||
"Google Translate": "ترجمة جوجل",
|
||||
"Azure Translator": "مترجم أزور"
|
||||
"Azure Translator": "مترجم أزور",
|
||||
"Invert Image In Dark Mode": "عكس الصورة في الوضع المظلم"
|
||||
}
|
||||
|
||||
@@ -320,5 +320,6 @@
|
||||
"Translated by {{provider}}.": "Übersetzt von {{provider}}.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Übersetzer",
|
||||
"Azure Translator": "Azure Übersetzer"
|
||||
"Azure Translator": "Azure Übersetzer",
|
||||
"Invert Image In Dark Mode": "Invertiere Bild im Dunkeln"
|
||||
}
|
||||
|
||||
@@ -320,5 +320,6 @@
|
||||
"Translated by {{provider}}.": "Μεταφρασμένο από {{provider}}.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Μετάφραση Google",
|
||||
"Azure Translator": "Μεταφραστής Azure"
|
||||
"Azure Translator": "Μεταφραστής Azure",
|
||||
"Invert Image In Dark Mode": "Αντιστροφή σε σκούρο"
|
||||
}
|
||||
|
||||
@@ -321,5 +321,6 @@
|
||||
"Translated by {{provider}}.": "Traducido por {{provider}}.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Translate",
|
||||
"Azure Translator": "Azure Translator"
|
||||
"Azure Translator": "Azure Translator",
|
||||
"Invert Image In Dark Mode": "Invertir imagen en oscuro"
|
||||
}
|
||||
|
||||
@@ -321,5 +321,6 @@
|
||||
"Translated by {{provider}}.": "Traduit par {{provider}}.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Traduction",
|
||||
"Azure Translator": "Traducteur Azure"
|
||||
"Azure Translator": "Traducteur Azure",
|
||||
"Invert Image In Dark Mode": "Inverser l'image en mode sombre"
|
||||
}
|
||||
|
||||
@@ -320,5 +320,6 @@
|
||||
"Translated by {{provider}}.": "{{provider}} द्वारा अनुवादित।",
|
||||
"DeepL": "डीपएल",
|
||||
"Google Translate": "गूगल अनुवाद",
|
||||
"Azure Translator": "एज़्योर अनुवादक"
|
||||
"Azure Translator": "एज़्योर अनुवादक",
|
||||
"Invert Image In Dark Mode": "अंधेरे में छवि को उलटें"
|
||||
}
|
||||
|
||||
@@ -319,5 +319,6 @@
|
||||
"Translated by {{provider}}.": "Terjemahan oleh {{provider}}.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Translate",
|
||||
"Azure Translator": "Azure Translator"
|
||||
"Azure Translator": "Azure Translator",
|
||||
"Invert Image In Dark Mode": "Inversi Gambar di Gelap"
|
||||
}
|
||||
|
||||
@@ -321,5 +321,6 @@
|
||||
"Translated by {{provider}}.": "Tradotto da {{provider}}.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Translate",
|
||||
"Azure Translator": "Traduttore Azure"
|
||||
"Azure Translator": "Traduttore Azure",
|
||||
"Invert Image In Dark Mode": "Inverti immagine in scuro"
|
||||
}
|
||||
|
||||
@@ -319,5 +319,6 @@
|
||||
"Translated by {{provider}}.": "{{provider}}によって翻訳されました。",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google翻訳",
|
||||
"Azure Translator": "Azure翻訳"
|
||||
"Azure Translator": "Azure翻訳",
|
||||
"Invert Image In Dark Mode": "ダークモードで画像を反転"
|
||||
}
|
||||
|
||||
@@ -319,5 +319,6 @@
|
||||
"Translated by {{provider}}.": "{{provider}}에 의해 번역됨.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "구글 번역",
|
||||
"Azure Translator": "Azure 번역기"
|
||||
"Azure Translator": "Azure 번역기",
|
||||
"Invert Image In Dark Mode": "어두운 곳에서 이미지 반전"
|
||||
}
|
||||
|
||||
@@ -320,5 +320,6 @@
|
||||
"Translated by {{provider}}.": "Vertaald door {{provider}}.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Vertalen",
|
||||
"Azure Translator": "Azure Vertaler"
|
||||
"Azure Translator": "Azure Vertaler",
|
||||
"Invert Image In Dark Mode": "Inverteer afbeelding in het donker"
|
||||
}
|
||||
|
||||
@@ -322,5 +322,6 @@
|
||||
"Translated by {{provider}}.": "Przetłumaczone przez {{provider}}.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Tłumacz Google",
|
||||
"Azure Translator": "Azure Translator"
|
||||
"Azure Translator": "Azure Translator",
|
||||
"Invert Image In Dark Mode": "Odwróć obraz w ciemności"
|
||||
}
|
||||
|
||||
@@ -321,5 +321,6 @@
|
||||
"Translated by {{provider}}.": "Traduzido por {{provider}}.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Tradutor",
|
||||
"Azure Translator": "Tradutor Azure"
|
||||
"Azure Translator": "Tradutor Azure",
|
||||
"Invert Image In Dark Mode": "Inverter Imagem no Escuro"
|
||||
}
|
||||
|
||||
@@ -322,5 +322,6 @@
|
||||
"Translated by {{provider}}.": "Переведено {{provider}}.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Translate",
|
||||
"Azure Translator": "Azure Translator"
|
||||
"Azure Translator": "Azure Translator",
|
||||
"Invert Image In Dark Mode": "Инвертировать в тёмной теме"
|
||||
}
|
||||
|
||||
@@ -320,5 +320,6 @@
|
||||
"Translated by {{provider}}.": "{{provider}} tarafından çevrildi.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Çeviri",
|
||||
"Azure Translator": "Azure Çevirmen"
|
||||
"Azure Translator": "Azure Çevirmen",
|
||||
"Invert Image In Dark Mode": "Karartılmış Görüntüyü Ters Çevir"
|
||||
}
|
||||
|
||||
@@ -322,5 +322,6 @@
|
||||
"Translated by {{provider}}.": "Перекладено {{provider}}.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Translate",
|
||||
"Azure Translator": "Azure Translator"
|
||||
"Azure Translator": "Azure Translator",
|
||||
"Invert Image In Dark Mode": "Інвертувати зображення в темряві"
|
||||
}
|
||||
|
||||
@@ -319,5 +319,6 @@
|
||||
"Translated by {{provider}}.": "Được dịch bởi {{provider}}.",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Dịch",
|
||||
"Azure Translator": "Azure Translator"
|
||||
"Azure Translator": "Azure Translator",
|
||||
"Invert Image In Dark Mode": "Đảo ảnh chế độ tối"
|
||||
}
|
||||
|
||||
@@ -319,5 +319,6 @@
|
||||
"Translated by {{provider}}.": "由 {{provider}} 提供翻译",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Translate",
|
||||
"Azure Translator": "Azure Translator"
|
||||
"Azure Translator": "Azure Translator",
|
||||
"Invert Image In Dark Mode": "深色主题下图片反色"
|
||||
}
|
||||
|
||||
@@ -319,5 +319,6 @@
|
||||
"Translated by {{provider}}.": "由 {{provider}} 提供翻譯",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "Google Translate",
|
||||
"Azure Translator": "Azure Translator"
|
||||
"Azure Translator": "Azure Translator",
|
||||
"Invert Image In Dark Mode": "深色主題下圖片反相"
|
||||
}
|
||||
|
||||
@@ -33,9 +33,12 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
const { getView, getViewSettings, setViewSettings } = useReaderStore();
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
|
||||
const { themeMode, setThemeMode } = useThemeStore();
|
||||
const { themeMode, isDarkMode, setThemeMode } = useThemeStore();
|
||||
const [isScrolledMode, setScrolledMode] = useState(viewSettings!.scrolled);
|
||||
const [zoomLevel, setZoomLevel] = useState(viewSettings!.zoomLevel!);
|
||||
const [invertImgColorInDark, setInvertImgColorInDark] = useState(
|
||||
viewSettings!.invertImgColorInDark,
|
||||
);
|
||||
|
||||
const zoomIn = () => setZoomLevel((prev) => Math.min(prev + ZOOM_STEP, MAX_ZOOM_LEVEL));
|
||||
const zoomOut = () => setZoomLevel((prev) => Math.max(prev - ZOOM_STEP, MIN_ZOOM_LEVEL));
|
||||
@@ -76,6 +79,12 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [zoomLevel]);
|
||||
|
||||
useEffect(() => {
|
||||
if (invertImgColorInDark === viewSettings.invertImgColorInDark) return;
|
||||
saveViewSettings(envConfig, bookKey, 'invertImgColorInDark', invertImgColorInDark, true, true);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [invertImgColorInDark]);
|
||||
|
||||
return (
|
||||
<div
|
||||
tabIndex={0}
|
||||
@@ -135,6 +144,12 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
Icon={themeMode === 'dark' ? BiMoon : themeMode === 'light' ? BiSun : TbSunMoon}
|
||||
onClick={cycleThemeMode}
|
||||
/>
|
||||
<MenuItem
|
||||
label={_('Invert Image In Dark Mode')}
|
||||
disabled={!isDarkMode}
|
||||
Icon={invertImgColorInDark ? MdCheck : undefined}
|
||||
onClick={() => setInvertImgColorInDark(!invertImgColorInDark)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,24 +13,38 @@ import {
|
||||
themes,
|
||||
} from '@/styles/themes';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { saveViewSettings } from '../../utils/viewSettingsHelper';
|
||||
import ThemeEditor from './ThemeEditor';
|
||||
|
||||
const ColorPanel: React.FC<{ bookKey: string }> = ({}) => {
|
||||
const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const _ = useTranslation();
|
||||
const { themeMode, themeColor, isDarkMode, setThemeMode, setThemeColor, saveCustomTheme } =
|
||||
useThemeStore();
|
||||
const { envConfig } = useEnv();
|
||||
const { settings, setSettings } = useSettingsStore();
|
||||
const { getViewSettings } = useReaderStore();
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
const [invertImgColorInDark, setInvertImgColorInDark] = useState(
|
||||
viewSettings.invertImgColorInDark,
|
||||
);
|
||||
|
||||
const iconSize16 = useResponsiveSize(16);
|
||||
const iconSize24 = useResponsiveSize(24);
|
||||
const [editTheme, setEditTheme] = useState<CustomTheme | null>(null);
|
||||
const [customThems, setCustomThemes] = useState<Theme[]>([]);
|
||||
const [showCustomThemeEditor, setShowCustomThemeEditor] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (invertImgColorInDark === viewSettings.invertImgColorInDark) return;
|
||||
saveViewSettings(envConfig, bookKey, 'invertImgColorInDark', invertImgColorInDark);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [invertImgColorInDark]);
|
||||
|
||||
useEffect(() => {
|
||||
const customThemes = settings.globalReadSettings.customThemes ?? [];
|
||||
setCustomThemes(
|
||||
@@ -112,6 +126,17 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({}) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center justify-between'>
|
||||
<h2 className='font-medium'>{_('Invert Image In Dark Mode')}</h2>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='toggle'
|
||||
checked={invertImgColorInDark}
|
||||
disabled={!isDarkMode}
|
||||
onChange={() => setInvertImgColorInDark(!invertImgColorInDark)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className='mb-2 font-medium'>{_('Theme Color')}</h2>
|
||||
<div className='grid grid-cols-3 gap-4'>
|
||||
|
||||
@@ -162,6 +162,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
}, [writingMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (overrideLayout === viewSettings.overrideLayout) return;
|
||||
saveViewSettings(envConfig, bookKey, 'overrideLayout', overrideLayout);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [overrideLayout]);
|
||||
|
||||
@@ -118,7 +118,7 @@ export const DEFAULT_BOOK_STYLE: BookStyle = {
|
||||
textIndent: 0,
|
||||
fullJustification: true,
|
||||
hyphenation: true,
|
||||
invert: false,
|
||||
invertImgColorInDark: false,
|
||||
theme: 'light',
|
||||
overrideFont: false,
|
||||
overrideLayout: false,
|
||||
|
||||
@@ -99,7 +99,7 @@ export interface BookStyle {
|
||||
textIndent: number;
|
||||
fullJustification: boolean;
|
||||
hyphenation: boolean;
|
||||
invert: boolean;
|
||||
invertImgColorInDark: boolean;
|
||||
theme: string;
|
||||
overrideFont: boolean;
|
||||
overrideLayout: boolean;
|
||||
|
||||
@@ -29,7 +29,7 @@ const getFontStyles = (
|
||||
overrideFont: boolean,
|
||||
themeCode: ThemeCode,
|
||||
) => {
|
||||
const { fg, primary } = themeCode;
|
||||
const { fg, primary, isDarkMode } = themeCode;
|
||||
const lastSerifFonts = ['Georgia', 'Times New Roman'];
|
||||
const serifFonts = [
|
||||
serif,
|
||||
@@ -87,15 +87,9 @@ const getFontStyles = (
|
||||
${overrideFont ? 'font-family: revert !important;' : ''}
|
||||
}
|
||||
a:any-link, a:any-link * {
|
||||
${overrideFont ? `color: ${primary};` : ''}
|
||||
${overrideFont ? `color: ${primary};` : isDarkMode ? `color: lightblue;` : ''}
|
||||
text-decoration: none;
|
||||
}
|
||||
/* https://github.com/whatwg/html/issues/5426 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
a:any-link, a:any-link * {
|
||||
${overrideFont ? `color: ${primary};` : `color: lightblue;`}
|
||||
}
|
||||
}
|
||||
/* override inline hardcoded text color */
|
||||
*[style*="color: rgb(0,0,0)"], *[style*="color: rgb(0, 0, 0)"],
|
||||
*[style*="color: #000"], *[style*="color: #000000"], *[style*="color: black"],
|
||||
@@ -188,6 +182,7 @@ const getLayoutStyles = (
|
||||
zoomLevel: number,
|
||||
writingMode: string,
|
||||
vertical: boolean,
|
||||
invertImgColorInDark: boolean,
|
||||
themeCode: ThemeCode,
|
||||
) => {
|
||||
const { bg, fg, primary, isDarkMode } = themeCode;
|
||||
@@ -293,11 +288,13 @@ const getLayoutStyles = (
|
||||
color: unset;
|
||||
}
|
||||
|
||||
img {
|
||||
${isDarkMode && invertImgColorInDark ? 'filter: invert(100%);' : ''}
|
||||
}
|
||||
/* inline images without dimension */
|
||||
p img, span img, sup img {
|
||||
height: 1em;
|
||||
mix-blend-mode: ${isDarkMode ? 'screen' : 'multiply'};
|
||||
${isDarkMode ? 'filter: invert(100%);' : ''}
|
||||
}
|
||||
p:has(img), span:has(img) {
|
||||
background-color: ${bg};
|
||||
@@ -345,12 +342,6 @@ export const getFootnoteStyles = () => `
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
a:any-link, a:any-link * {
|
||||
color: unset;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -423,6 +414,7 @@ export const getStyles = (viewSettings: ViewSettings, themeCode?: ThemeCode) =>
|
||||
viewSettings.zoomLevel! / 100.0,
|
||||
viewSettings.writingMode!,
|
||||
viewSettings.vertical!,
|
||||
viewSettings.invertImgColorInDark!,
|
||||
themeCode,
|
||||
);
|
||||
// scale the font size on-the-fly so that we can sync the same font size on different devices
|
||||
|
||||
Reference in New Issue
Block a user