This commit is contained in:
2023-06-23 09:09:52 +08:00
parent 319adc7e93
commit b9132406e7
8 changed files with 196 additions and 4 deletions

View File

@@ -0,0 +1,119 @@
import { Component, ContextType } from "preact";
import { SettingsCtx } from "./SettingsContext.tsx";
import { ConfigType } from "../config.ts";
import Select from "preact-material-components/Select";
import { Ref, StateUpdater, useRef } from "preact/hooks";
interface obj {
toString(): string;
}
type Props<T extends obj> = {
name: keyof ConfigType;
list: { value: T; text?: string; disabled?: boolean }[];
description: string;
/**@default {0}*/
selectedIndex?: number;
/**@default {false}*/
disabled?: boolean;
/**@default {false}*/
box?: boolean;
/**@default {false}*/
outlined?: boolean;
hintText?: string;
set_value?: StateUpdater<T>;
};
type State = {
selectedIndex: number;
};
export default class SettingsSelect<T extends obj>
extends Component<Props<T>, State> {
static contextType = SettingsCtx;
declare context: ContextType<typeof SettingsCtx>;
ref: Ref<Select | undefined> | undefined;
constructor(props: Props<T>) {
super(props);
if (!props.list.length) throw Error("No list.");
this.state = { selectedIndex: props.selectedIndex || 0 };
}
componentWillReceiveProps(
nextProps: Readonly<Props<T>>,
_nextContext: unknown,
): void {
const selectedIndex = nextProps.selectedIndex || 0;
this.setState({ selectedIndex });
this.update(selectedIndex);
}
componentDidMount(): void {
this.update(this.state.selectedIndex);
}
render() {
this.ref = useRef<Select>();
const id = `s-${this.props.name}`;
return (
<div class="s-select" id={id}>
<label>{this.props.description}</label>
<Select
ref={this.ref}
hintText={this.props.hintText}
disabled={this.props.disabled}
box={this.props.box}
outlined={this.props.outlined}
onChange={(e: Event) => {
if (!e.target) return;
/**@ts-ignore */
const selectedIndex: number = e.target.selectedIndex;
this.setState({ selectedIndex });
this.set_value(selectedIndex);
}}
>
{this.props.list.map((v) => {
const t = v.text ? v.text : v.value.toString();
return (
<Select.Item disabled={v.disabled}>{t}</Select.Item>
);
})}
</Select>
</div>
);
}
set_changed() {
if (this.context) {
this.context.set_changed((v) => {
v.add(this.props.name);
return v;
});
}
}
set_value(index: number) {
const value = this.props.list[index].value;
if (this.props.set_value) {
this.props.set_value(value);
this.set_changed();
} else if (this.context) {
this.context.set_settings((v) => {
if (v) {
const t: Record<string, unknown> = v;
t[this.props.name] = value;
this.set_changed();
return t as ConfigType;
}
});
}
}
update(index: number) {
const e = this.ref?.current;
if (e) {
const b = e.base;
if (b) {
const t = b as HTMLElement;
const s = t.querySelector("select");
if (s) {
s.selectedIndex = index;
}
}
}
}
}

View File

@@ -30,6 +30,7 @@ export type SettingsTextProps<T extends keyof TextType> = {
set_value?: StateUpdater<TextType[T]>;
min?: DataType[T];
max?: DataType[T];
outlined?: boolean;
};
export default class SettingsText<T extends keyof TextType>
@@ -99,6 +100,7 @@ export default class SettingsText<T extends keyof TextType>
const id = `s-${this.props.name}`;
let cn = "text";
if (this.props.helpertext) cn += " helper";
if (this.props.outlined) cn += " outlined";
return (
<div class={cn} id={id}>
<label>{this.props.description}</label>
@@ -118,6 +120,7 @@ export default class SettingsText<T extends keyof TextType>
}}
min={this.props.min}
max={this.props.max}
outlined={this.props.outlined}
/>
{this.props.children}
</div>

View File

@@ -18,8 +18,15 @@ export type ConfigType = {
meili_host?: string;
meili_search_api_key?: string;
meili_update_api_key?: string;
ffmpeg_path: string;
thumbnail_method: ThumbnailMethod;
};
export enum ThumbnailMethod {
FFMPEG_BINARY,
FFMPEG_API,
}
export class Config {
_data;
constructor(data: JsonValue) {
@@ -109,6 +116,14 @@ export class Config {
get meili_update_api_key() {
return this._return_string("meili_update_api_key");
}
get ffmpeg_path() {
return this._return_string("ffmpeg_path") || "ffmpeg";
}
get thumbnail_method() {
const n = this._return_number("thumbnail") || 0;
if (n < 0 || n > 1) return ThumbnailMethod.FFMPEG_BINARY;
return n as ThumbnailMethod;
}
to_json(): ConfigType {
return {
cookies: typeof this.cookies === "string",
@@ -127,6 +142,8 @@ export class Config {
meili_host: this.meili_host,
meili_search_api_key: this.meili_search_api_key,
meili_update_api_key: this.meili_update_api_key,
ffmpeg_path: this.ffmpeg_path,
thumbnail_method: this.thumbnail_method,
};
}
}

View File

@@ -5,11 +5,12 @@ import Dialog from "preact-material-components/Dialog";
import Snackbar from "preact-material-components/Snackbar";
import { tw } from "twind";
import { GlobalCtx } from "../components/GlobalContext.tsx";
import { ConfigType } from "../config.ts";
import { ConfigType, ThumbnailMethod } from "../config.ts";
import SettingsCheckbox from "../components/SettingsCheckbox.tsx";
import SettingsContext from "../components/SettingsContext.tsx";
import SettingsText from "../components/SettingsText.tsx";
import t from "../server/i18n.ts";
import SettingsSelect from "../components/SettingsSelect.tsx";
export type SettingsProps = {
show: boolean;
@@ -122,12 +123,14 @@ export default class Settings extends Component<SettingsProps> {
value={settings.base}
description={t("settings.base")}
type="text"
outlined={true}
/>
<SettingsText
name="ua"
value={settings.ua ? settings.ua : ""}
description={t("settings.ua")}
type="text"
outlined={true}
ref={ref}
>
<Button
@@ -154,6 +157,7 @@ export default class Settings extends Component<SettingsProps> {
settings.cookies ? "_new" : ""
}_cookies`,
)}
outlined={true}
/>
<SettingsText
name="max_task_count"
@@ -161,6 +165,7 @@ export default class Settings extends Component<SettingsProps> {
description={t("settings.max_task_count")}
type="number"
min={1}
outlined={true}
/>
<SettingsText
name="max_retry_count"
@@ -168,6 +173,7 @@ export default class Settings extends Component<SettingsProps> {
description={t("settings.max_retry_count")}
type="number"
min={1}
outlined={true}
/>
<SettingsText
name="max_download_img_count"
@@ -175,6 +181,7 @@ export default class Settings extends Component<SettingsProps> {
description={t("settings.max_download_img_count")}
type="number"
min={1}
outlined={true}
/>
<SettingsText
name="db_path"
@@ -182,6 +189,7 @@ export default class Settings extends Component<SettingsProps> {
type="text"
description={t("settings.db_path")}
helpertext={t("settings.db_path_help")}
outlined={true}
/>
<SettingsText
name="port"
@@ -190,30 +198,55 @@ export default class Settings extends Component<SettingsProps> {
type="number"
min={0}
max={65535}
outlined={true}
/>
<SettingsText
name="hostname"
value={settings.hostname}
description={t("settings.hostname")}
type="text"
outlined={true}
/>
<SettingsText
name="meili_host"
value={settings.meili_host || ""}
description={t("settings.meili_host")}
type="text"
outlined={true}
/>
<SettingsText
name="meili_update_api_key"
value={settings.meili_update_api_key || ""}
description={t("settings.meili_update_api_key")}
type="text"
outlined={true}
/>
<SettingsText
name="meili_search_api_key"
value={settings.meili_search_api_key || ""}
description={t("settings.meili_search_api_key")}
type="text"
outlined={true}
/>
<SettingsText
name="ffmpeg_path"
value={settings.ffmpeg_path}
description={t("settings.ffmpeg_path")}
type="text"
outlined={true}
/>
<SettingsSelect
name="thumbnail_method"
list={[{
value: ThumbnailMethod.FFMPEG_BINARY,
text: t("settings.thumbnail_method0"),
}, {
value: ThumbnailMethod.FFMPEG_API,
text: t("settings.thumbnail_method1"),
}]}
description={t("settings.thumbnail_method")}
selectedIndex={settings.thumbnail_method}
outlined={true}
/>
</SettingsContext>
<Button onClick={loadData}>{t("common.reload")}</Button>

View File

@@ -7,7 +7,7 @@ import {
GetFileResponseOptions,
} from "../server/get_file_response.ts";
const STATIC_FILES = ["/sw.js", "/sw.js.map"];
const STATIC_FILES = ["/common.css", "/sw.js", "/sw.js.map"];
export async function handler(req: Request, ctx: MiddlewareHandlerContext) {
const url = new URL(req.url);

View File

@@ -44,3 +44,15 @@
.settings div.text.helper label {
margin-top: 14px;
}
.settings div.text.outlined .mdc-text-field label {
top: 14px;
}
.settings div.text.outlined .mdc-text-field.mdc-text-field--focused label {
top: 22px;
}
.settings div.text.outlined {
margin-top: 5px;
}

View File

@@ -23,5 +23,9 @@
"hostname": "Listening host: ",
"meili_host": "Meilisearch server host: ",
"meili_update_api_key": "Meilisearch API key for updating gallery metadata: ",
"meili_search_api_key": "Meilisearch API key for searching: "
"meili_search_api_key": "Meilisearch API key for searching: ",
"ffmpeg_path": "The path to the ffmpeg binary: ",
"thumbnail_method": "The method used to generate thumbnail: ",
"thumbnail_method0": "ffmpeg binary",
"thumbnail_method1": "ffmpeg API"
}

View File

@@ -23,5 +23,9 @@
"hostname": "监听主机:",
"meili_host": "Meilisearch服务器主机:",
"meili_update_api_key": "用于更新画廊元数据的Meilisearch API密钥:",
"meili_search_api_key": "用于搜索的Meilisearch API密钥:"
"meili_search_api_key": "用于搜索的Meilisearch API密钥:",
"ffmpeg_path": "FFMPEG二进制的位置:",
"thumbnail_method": "生成缩略图的方式:",
"thumbnail_method0": "FFMPEG二进制",
"thumbnail_method1": "FFMPEG API"
}