yggdrasil/pages/admin/settings/
image_section.rs1use 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#[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#[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}