Skip to main content

yggdrasil/pages/admin/settings/
image_section.rs

1//! 图片处理分区:WebP 编码质量与方法、尺寸上限、尺寸缓存 TTL。
2//!
3//! 写入数据库,下次启动生效(编码器与解码上限在启动时读取)。
4
5use leptos::prelude::*;
6
7use super::kit::{use_section, SettingRow, SettingsCard};
8use super::SettingsSection;
9use crate::api::error::ApiError;
10use crate::api::settings::{
11    get_image_limit_settings, get_webp_settings, update_image_limit_settings, update_webp_settings,
12};
13use crate::components::forms::NumberInput;
14use crate::components::ui::{SegmentedControl, SegmentedItem, SegmentedKind, SegmentedSize};
15use crate::models::settings::{ImageLimitSettings, WebpSettings};
16
17/// WebP 编码与尺寸上限一起编辑、一起保存。
18#[derive(Clone, Debug, Default, PartialEq)]
19struct ImageDraft {
20    webp: WebpSettings,
21    limit: ImageLimitSettings,
22}
23
24async fn load() -> Result<ImageDraft, ApiError> {
25    Ok(ImageDraft {
26        webp: get_webp_settings().await?,
27        limit: get_image_limit_settings().await?,
28    })
29}
30
31async fn save(d: ImageDraft) -> Result<ImageDraft, ApiError> {
32    Ok(ImageDraft {
33        webp: update_webp_settings(d.webp.quality, d.webp.method).await?,
34        limit: update_image_limit_settings(
35            d.limit.max_dimension,
36            d.limit.max_pixels,
37            d.limit.dimensions_cache_ttl_secs,
38        )
39        .await?,
40    })
41}
42
43/// 图片处理分区组件。
44#[component]
45pub fn ImageSection() -> impl IntoView {
46    let st = use_section(SettingsSection::Image, load, save);
47    let quality = st.field(|d| d.webp.quality.round() as i64);
48    let method_items = Signal::stored(
49        (0..=6u32)
50            .map(|n| SegmentedItem::new(n, n.to_string()))
51            .collect::<Vec<_>>(),
52    );
53    view! {
54        <SettingsCard
55            section=SettingsSection::Image
56            description="上传图片转码为 WebP 的质量与速度,以及解码尺寸上限。"
57            ready=st.ready()
58            load_failed=st.load_failed()
59            failed=st.failed()
60        >
61            <SettingRow label="质量系数" html_for="webp-quality" hint="0 体积最小,100 质量最好。默认 85。">
62                <div class="settings-range">
63                    <input
64                        id="webp-quality"
65                        type="range"
66                        min="0"
67                        max="100"
68                        step="1"
69                        prop:value=move || quality.get().to_string()
70                        on:input=move |e| {
71                            if let Ok(v) = event_target_value(&e).parse::<f32>() {
72                                st.edit(|d| d.webp.quality = v);
73                            }
74                        }
75                    />
76                    <output for="webp-quality">{move || quality.get()}</output>
77                </div>
78            </SettingRow>
79            <SettingRow label="编码方法" hint="越大压缩率越高、越慢。默认 2。">
80                <SegmentedControl
81                    items=method_items
82                    value=st.field(|d| d.webp.method)
83                    on_change=move |n| st.edit(|d| d.webp.method = n)
84                    aria_label="编码方法"
85                    kind=SegmentedKind::Radio
86                    size=SegmentedSize::Sm
87                    class="settings-segmented-square"
88                />
89            </SettingRow>
90            <SettingRow label="单边最大尺寸" html_for="img-dim">
91                <NumberInput
92                    id="img-dim"
93                    value=st.field(|d| i64::from(d.limit.max_dimension))
94                    min=512
95                    max=65_535
96                    step=512
97                    unit="像素"
98                    hint="上传与动态缩放的宽、高上限。默认 8192。"
99                    onchange=move |v: i64| st.edit(|d| d.limit.max_dimension = v as u32)
100                />
101            </SettingRow>
102            <SettingRow label="总像素上限" html_for="img-px">
103                <NumberInput
104                    id="img-px"
105                    value=st.field(|d| (d.limit.max_pixels / 1_000_000) as i64)
106                    min=1
107                    max=1_000
108                    unit="百万"
109                    hint="决定单图解码内存(像素 × 4 + 1 MB)。默认 50 百万,约 7000 × 7000。"
110                    onchange=move |v: i64| st.edit(|d| d.limit.max_pixels = v as u64 * 1_000_000)
111                />
112            </SettingRow>
113            <SettingRow label="尺寸缓存" html_for="img-ttl">
114                <NumberInput
115                    id="img-ttl"
116                    value=st.field(|d| (d.limit.dimensions_cache_ttl_secs / 3600) as i64)
117                    min=1
118                    max=8_760
119                    unit="小时"
120                    hint="图片宽高的缓存时长,用于预留图片位置。默认 24 小时。"
121                    onchange=move |v: i64| st.edit(|d| d.limit.dimensions_cache_ttl_secs = v as u64 * 3600)
122                />
123            </SettingRow>
124        </SettingsCard>
125    }
126}