Add user custom style in MiscPanel

This commit is contained in:
chrox
2024-11-03 14:31:21 +01:00
parent 5661208453
commit 59cec59f78
7 changed files with 110 additions and 22 deletions
+2
View File
@@ -24,6 +24,7 @@
"@tauri-apps/plugin-os": "^2.0.0",
"@zip.js/zip.js": "^2.7.52",
"clsx": "^2.1.1",
"cssbeautify": "^0.3.1",
"epubjs": "^0.3.93",
"foliate-js": "workspace:*",
"js-md5": "^0.8.3",
@@ -36,6 +37,7 @@
},
"devDependencies": {
"@tauri-apps/cli": "2.0.4",
"@types/cssbeautify": "^0.3.5",
"@types/node": "^20",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
@@ -15,18 +15,20 @@ const ColorPanel: React.FC<{ bookKey: string }> = () => {
];
return (
<div>
<h2 className='mb-2 font-medium'>Color Settings</h2>
<div className='mt-4 grid grid-cols-3 gap-2'>
{themes.map((theme) => (
<button
key={theme}
className={`btn ${selectedTheme === theme ? 'btn-active' : ''}`}
onClick={() => setSelectedTheme(theme)}
>
{theme}
</button>
))}
<div className='my-4 w-full space-y-6'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Color Settings</h2>
<div className='mt-4 grid grid-cols-3 gap-2'>
{themes.map((theme) => (
<button
key={theme}
className={`btn ${selectedTheme === theme ? 'btn-active' : ''}`}
onClick={() => setSelectedTheme(theme)}
>
{theme}
</button>
))}
</div>
</div>
</div>
);
@@ -134,7 +134,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
return (
<div className='my-4 w-full space-y-6'>
<div className='cell-font-size w-full'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Font Size</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
@@ -143,7 +143,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
label='Default Font Size'
value={defaultFontSize}
onChange={setDefaultFontSize}
min={1}
min={minFontSize}
max={120}
/>
<NumberInput
@@ -158,7 +158,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
</div>
</div>
<div className='cell-font-family w-full'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Font Family</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
@@ -185,7 +185,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
</div>
</div>
<div className='cell-font-family w-full'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Font Face</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
@@ -92,7 +92,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
return (
<div className='my-4 w-full space-y-6'>
<div className='cell-font-size w-full'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Paragraph</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
@@ -127,7 +127,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
</div>
</div>
<div className='cell-font-family w-full'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Page</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
@@ -1,5 +1,10 @@
import React, { useEffect, useState } from 'react';
import { useReaderStore } from '@/store/readerStore';
import cssbeautify from 'cssbeautify';
import { getStyles } from '@/utils/style';
const cssRegex =
/((?:\s*)([\w#.@*,:\-.:>+~\[\]\"=(),*\s]+)\s*{(?:[\s]*)((?:[A-Za-z\- \s]+[:]\s*['"0-9\w .,\/\()\-!#%]+;?)*)*\s*}(?:\s*))/gim;
const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { books, settings, setSettings, setConfig, getFoliateView } = useReaderStore();
@@ -8,6 +13,48 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const config = bookState.config!;
const [animated, setAnimated] = useState(config.viewSettings!.animated!);
const [userStylesheet, setUserStylesheet] = useState(config.viewSettings!.userStylesheet!);
const [error, setError] = useState<string | null>(null);
let cssInput = userStylesheet;
const validateCSS = (css: string) => {
return cssRegex.test(css);
};
const handleUserStylesheetChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
cssInput = e.target.value;
try {
const formattedCSS = cssbeautify(cssInput, {
indent: ' ',
openbrace: 'end-of-line',
autosemicolon: true,
});
setUserStylesheet(formattedCSS);
if (cssInput && !validateCSS(cssInput)) {
throw new Error('Invalid CSS');
}
setError(null);
config.viewSettings!.userStylesheet = formattedCSS;
setConfig(bookKey, config);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.userStylesheet = formattedCSS;
setSettings(settings);
}
getFoliateView(bookKey)?.renderer.setStyles?.(getStyles(config));
} catch (err) {
setError('Invalid CSS: Please check your input.');
console.log('CSS Error:', err);
}
};
const handleInput = (e: React.FormEvent<HTMLTextAreaElement>) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
};
useEffect(() => {
config.viewSettings!.animated = animated;
@@ -25,7 +72,7 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
return (
<div className='my-4 w-full space-y-6'>
<div className='cell-font-size w-full'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Animation</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
@@ -41,6 +88,26 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
</div>
</div>
</div>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Custom CSS</h2>
<div className={`card bg-base-100 border shadow ${error ? 'border-red-500' : ''}`}>
<div className='divide-y'>
<div className='css-text-area config-item-top config-item-bottom p-1'>
<textarea
className='textarea textarea-ghost h-48 w-full border-0 p-3 !outline-none'
placeholder='Enter your custom CSS here...'
spellCheck='false'
value={cssInput}
onInput={handleInput}
onKeyDown={handleInput}
onKeyUp={handleInput}
onChange={handleUserStylesheetChange}
/>
</div>
</div>
</div>
</div>
</div>
);
};
@@ -30,10 +30,10 @@ const NumberInput: React.FC<NumberInputProps> = ({
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value === '' ? 0 : parseInt(e.target.value);
const newValue = e.target.value === '' ? 0 : parseFloat(e.target.value);
setLocalValue(newValue);
if (!isNaN(newValue)) {
const roundedValue = Math.round(newValue * 10) / 10;
setLocalValue(roundedValue);
onChange(Math.max(min, Math.min(max, roundedValue)));
}
};
@@ -70,7 +70,7 @@ const NumberInput: React.FC<NumberInputProps> = ({
onKeyDown={handleInput}
onKeyUp={handleInput}
onBlur={handleOnBlur}
className='input input-ghost settings-content w-20 max-w-xs rounded px-3 py-1 text-right'
className='input input-ghost settings-content w-20 max-w-xs rounded border-0 bg-transparent px-3 py-1 text-right !outline-none'
onFocus={(e) => e.target.select()}
/>
<button
+17
View File
@@ -59,6 +59,9 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
cssbeautify:
specifier: ^0.3.1
version: 0.3.1
epubjs:
specifier: ^0.3.93
version: 0.3.93
@@ -90,6 +93,9 @@ importers:
'@tauri-apps/cli':
specifier: 2.0.4
version: 2.0.4
'@types/cssbeautify':
specifier: ^0.3.5
version: 0.3.5
'@types/node':
specifier: ^20
version: 20.16.6
@@ -700,6 +706,9 @@ packages:
'@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
'@types/cssbeautify@0.3.5':
resolution: {integrity: sha512-bkxuJdUu7Liw14EouI8IFNvJXvd5IioAZdDvlCFBXv+Sel8uPK9rcxfvOchQKqw2AtppxRvcYaWmimi0t0Yuow==}
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
@@ -1382,6 +1391,10 @@ packages:
css-selector-tokenizer@0.8.0:
resolution: {integrity: sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==}
cssbeautify@0.3.1:
resolution: {integrity: sha512-ljnSOCOiMbklF+dwPbpooyB78foId02vUrTDogWzu6ca2DCNB7Kc/BHEGBnYOlUYtwXvSW0mWTwaiO2pwFIoRg==}
hasBin: true
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@@ -3863,6 +3876,8 @@ snapshots:
'@tsconfig/node16@1.0.4':
optional: true
'@types/cssbeautify@0.3.5': {}
'@types/estree@1.0.6': {}
'@types/json-schema@7.0.15': {}
@@ -4933,6 +4948,8 @@ snapshots:
cssesc: 3.0.0
fastparse: 1.1.2
cssbeautify@0.3.1: {}
cssesc@3.0.0: {}
csstype@3.1.3: {}