diff --git a/.gitignore b/.gitignore index b947077..1f3a407 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ dist/ +*.crx diff --git a/build.js b/build.js index 69808ea..c3fe140 100644 --- a/build.js +++ b/build.js @@ -2,6 +2,7 @@ import esbuild from 'esbuild'; import process from 'node:process'; import fs from 'node:fs'; import path from 'node:path'; +import colors from 'colors'; const is_dev = process.argv.includes('--dev'); const is_dbg = process.argv.includes('--debug'); @@ -13,8 +14,36 @@ function sourcemap(is_content_script = false) { return true; } +function displaySize(bytes) { + const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']; + let i = 0; + while (bytes >= 1024 && i < units.length - 1) { + bytes /= 1024; + i++; + } + return `${bytes.toFixed(2)} ${units[i]}`; +} + +/**@param {esbuild.BuildResult} result */ +function displayResult(result) { + for (const outfile in result.metafile.outputs) { + const output = result.metafile.outputs[outfile]; + let totalInBytes = 0; + for (const input in output.inputs) { + const inp = result.metafile.inputs[input]; + totalInBytes += inp.bytes; + } + let inInfo = ''; + if (totalInBytes > 0) { + const ratio = (output.bytes / totalInBytes * 100).toFixed(2); + inInfo = ` - Input size: ${colors.green(displaySize(totalInBytes))} (${totalInBytes} B), Ratio: ${colors.yellow(ratio + '%')}`; + } + console.log(`${colors.cyan(outfile)}: ${colors.yellow(displaySize(output.bytes))} (${output.bytes} B)${inInfo}`); + } +} + async function build(name, is_content_script = true) { - return await esbuild.build({ + const result = await esbuild.build({ entryPoints: [`src/${name}.ts`], bundle: true, minify: !is_dbg, @@ -22,7 +51,10 @@ async function build(name, is_content_script = true) { platform: 'browser', target: ['chrome100'], sourcemap: sourcemap(is_content_script), + metafile: true, }) + displayResult(result); + return result; } async function buildTsx(names) { @@ -30,7 +62,7 @@ async function buildTsx(names) { for (const name of names) { entryPoints.push(`src/${name}.tsx`); } - await esbuild.build({ + const result = await esbuild.build({ entryPoints: entryPoints, bundle: true, minify: !is_dbg, @@ -42,12 +74,15 @@ async function buildTsx(names) { loader: { '.css': 'global-css', '.module.css': 'local-css' }, splitting: true, format: 'esm', + metafile: true, }); for (const name of names) { const srcHtmlPath = path.join('src', `${name}.html`); const distHtmlPath = path.join('dist', `${name}.html`); fs.copyFileSync(srcHtmlPath, distHtmlPath); } + displayResult(result); + return result; } fs.rmSync('dist', { recursive: true, force: true }); diff --git a/package.json b/package.json index 8e69bd4..c57a984 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "antd": "^6.3.0", + "colors": "^1.4.0", "esbuild": "^0.27.3", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/package.py b/package.py new file mode 100644 index 0000000..ae9c64b --- /dev/null +++ b/package.py @@ -0,0 +1,26 @@ +from zipfile import ZipFile, ZIP_DEFLATED +import os + +NEED_PACKED = [ + 'dist', + 'ico', + 'manifest.json', + 'LICENSE', +] + +def pack(): + with ZipFile('bookdownload.crx', 'w', compression=ZIP_DEFLATED, compresslevel=9) as zip: + for item in NEED_PACKED: + if not os.path.exists(item): + print(f'Warning: {item} does not exist, skipping.') + continue + if os.path.isdir(item): + for foldername, subfolders, filenames in os.walk(item): + for filename in filenames: + zip.write(os.path.join(foldername, filename)) + else: + zip.write(item) + + +if __name__ == '__main__': + pack() diff --git a/src/components/SwitchLabel.module.css b/src/components/SwitchLabel.module.css index 0b4ef82..b263984 100644 --- a/src/components/SwitchLabel.module.css +++ b/src/components/SwitchLabel.module.css @@ -1,3 +1,4 @@ .switch-label > span { cursor: pointer; + margin-left: 4px; } diff --git a/src/components/SwitchLabel.tsx b/src/components/SwitchLabel.tsx index abd00bb..ba6054f 100644 --- a/src/components/SwitchLabel.tsx +++ b/src/components/SwitchLabel.tsx @@ -10,8 +10,8 @@ export type SwitchLabelProps = { export default function SwitchLabel({ label, checked, onChange }: SwitchLabelProps) { return (
- onChange(!checked)}>{label} + onChange(!checked)}>{label}
); } \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index b09a732..f581019 100644 --- a/src/config.ts +++ b/src/config.ts @@ -31,3 +31,66 @@ export class QdConfig { this.config.AutoSaveChapter = value; } } + +type IndexedDbConfigData = { + compress?: boolean; +} + +export enum DbType { + IndexedDb, +} + +type DbConfigData = { + IndexedDb?: IndexedDbConfigData; + DbType?: DbType; +} + +export class IndexedDbConfig { + config: IndexedDbConfigData; + constructor(config: IndexedDbConfigData) { + this.config = config; + } + get compress(): boolean { + return this.config.compress ?? false; + } + set compress(value: boolean) { + this.config.compress = value; + } +} + +export class DbConfig { + static STORAGE_KEY = 'db_config'; + config?: DbConfigData; + constructor() { + } + async init() { + this.config = await loadConfig(DbConfig.STORAGE_KEY, {}); + } + async save() { + if (!this.config) { + throw new Error('Config not initialized'); + } + await saveConfig(DbConfig.STORAGE_KEY, this.config); + } + reset() { + this.config = {}; + } + get IndexedDb(): IndexedDbConfig { + if (!this.config) { + throw new Error('Config not initialized'); + } + if (!this.config.IndexedDb) { + this.config.IndexedDb = {}; + } + return new IndexedDbConfig(this.config.IndexedDb); + } + get DbType(): DbType { + return this.config?.DbType ?? DbType.IndexedDb; + } + set DbType(value: DbType) { + if (!this.config) { + throw new Error('Config not initialized'); + } + this.config.DbType = value; + } +} diff --git a/src/settings.tsx b/src/settings.tsx index 3e29f35..fb9541d 100644 --- a/src/settings.tsx +++ b/src/settings.tsx @@ -2,15 +2,21 @@ import { createRoot } from "react-dom/client"; import { Typography, Tabs } from "antd"; import type { TabsProps } from 'antd'; import QdSettings from "./settings/QdSettings"; +import DbSettings from "./settings/DbSettings"; const { Title } = Typography; const items: TabsProps['items'] = [ { 'key': '1', + 'label': `数据库设置`, + 'children': , + }, + { + 'key': '2', 'label': `起点设置`, 'children': , - } + }, ]; function Settings() { diff --git a/src/settings/DbSettings.tsx b/src/settings/DbSettings.tsx new file mode 100644 index 0000000..e2cabab --- /dev/null +++ b/src/settings/DbSettings.tsx @@ -0,0 +1,71 @@ +import { FloatButton, Affix, Button, Space, Select, Input } from "antd"; +import { SaveTwoTone, SaveOutlined, SyncOutlined } from "@ant-design/icons"; +import { useEffect, useState } from "react"; +import { DbConfig, DbType, IndexedDbConfig } from "../config"; +import AlertWarn from "../components/AlertWarn"; +import SwitchLabel from "../components/SwitchLabel"; + +function IndexedDbSettings({config}: { config: IndexedDbConfig }) { + const [compress, setCompress] = useState(false); + useEffect(() => { + setCompress(config.compress); + }, [config]); + function handleCompressChange(value: boolean) { + setCompress(value); + config.compress = value; + } + return (<> + + ) +} + +export default function DbSettings() { + const [container, setContainer] = useState(null); + const [config] = useState(new DbConfig()); + const [alert, setAlert] = useState<{ title?: string; content: string } | null>(null); + const [dbType, setDbType] = useState(DbType.IndexedDb); + const [indexedDbConfig, setIndexedDbConfig] = useState(new IndexedDbConfig({})); + function handleConfig() { + setDbType(config.DbType); + setIndexedDbConfig(config.IndexedDb); + } + useEffect(() => { + config.init().then(() => { + handleConfig(); + }).catch(e => { + setAlert({ content: "加载设置失败:" + (e instanceof Error ? e.message : "未知错误"), title: "错误" }); + }); + }, []); + function saveSettings() { + config.DbType = dbType; + config.save().then(() => { + setAlert({ content: "设置已保存!", title: "通知" }); + }).catch(e => { + setAlert({ content: e instanceof Error ? e.message : "未知错误", title: "错误" }); + }); + } + function resetSettings() { + config.reset(); + handleConfig(); + } + return ( +
+ container}> + + + +
+ +