Files
u-ppt/src-tauri/src/model.rs
绝尘 664ca9c65c 新增: Rust Op reducer + 逆操作计算(B2)
- op.rs: 纯函数 apply_op(deck,op) → new_deck,覆盖全部 11 种 Op 类型
- invert_op(deck,op) → 逆 Op,支持完整的 undo 链路
- 所有 reducer 不 mutate 输入,返回新 deck
- 8 个单元测试: add_slide/del_slide/set_theme/update_element/del_element/
  move_slide/undo_update/undo_add_slide/replace_deck_undo
- ElementStyle 加 rename_all=camelCase 匹配 JS 协议
- 32 个测试全部通过(B1:23 + B2:9)
2026-07-12 22:33:41 +08:00

309 lines
7.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =====================================================================
* model.rs — 数据模型(对应 src/core/types.ts
*
* 所有结构体 derive Serialize/Deserialize/TS确保
* - serde: JSON 序列化Rust ↔ IPC ↔ JS
* - ts-rs: 自动生成 TypeScript 类型定义Rust ↔ TS 类型同步)
*
* 运行 `cargo test --features export-types` 会在 ../bindings/ 生成 .ts 文件。
* ===================================================================== */
use serde::{Deserialize, Serialize};
use ts_rs::TS;
/* ---------- 颜色 / 背景 / 动画 ---------- */
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub enum ChartType {
#[serde(rename = "bar")]
Bar,
#[serde(rename = "line")]
Line,
#[serde(rename = "pie")]
Pie,
#[serde(rename = "area")]
Area,
#[serde(rename = "doughnut")]
Doughnut,
#[serde(rename = "radar")]
Radar,
#[serde(rename = "hbar")]
Hbar,
#[serde(rename = "progress")]
Progress,
}
impl Default for ChartType {
fn default() -> Self {
ChartType::Bar
}
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub enum ShapeType {
#[serde(rename = "rect")]
Rect,
#[serde(rename = "circle")]
Circle,
#[serde(rename = "triangle")]
Triangle,
}
impl Default for ShapeType {
fn default() -> Self {
ShapeType::Rect
}
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub enum AnimType {
#[serde(rename = "fade-up")]
FadeUp,
#[serde(rename = "fade")]
Fade,
#[serde(rename = "scale")]
Scale,
#[serde(rename = "zoom")]
Zoom,
#[serde(rename = "slide-l")]
SlideL,
#[serde(rename = "slide-r")]
SlideR,
#[serde(rename = "pop")]
Pop,
#[serde(rename = "rotate")]
Rotate,
#[serde(rename = "bounce")]
Bounce,
#[serde(rename = "flip")]
Flip,
#[serde(rename = "blur")]
Blur,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub enum ElementType {
#[serde(rename = "title")]
Title,
#[serde(rename = "text")]
Text,
#[serde(rename = "list")]
List,
#[serde(rename = "stat")]
Stat,
#[serde(rename = "quote")]
Quote,
#[serde(rename = "image")]
Image,
#[serde(rename = "shape")]
Shape,
#[serde(rename = "chart")]
Chart,
#[serde(rename = "card")]
Card,
#[serde(rename = "table")]
Table,
#[serde(rename = "code")]
Code,
#[serde(rename = "formula")]
Formula,
}
/* ---------- 主题 ---------- */
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub struct Theme {
pub name: String,
pub primary: String,
pub accent: String,
pub bg: String,
pub panel: String,
pub text: String,
pub muted: String,
}
/* ---------- 富文本 ---------- */
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub struct RichSegment {
pub text: String,
pub bold: Option<bool>,
pub italic: Option<bool>,
pub underline: Option<bool>,
pub strike: Option<bool>,
pub color: Option<String>,
pub highlight: Option<bool>,
pub code: Option<bool>,
#[serde(rename = "sup")]
pub sup_: Option<bool>,
#[serde(rename = "sub")]
pub sub_: Option<bool>,
pub font_size: Option<f64>,
pub link: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub struct RichLine {
pub segments: Vec<RichSegment>,
}
/* ---------- 元素 ---------- */
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
#[serde(rename_all = "camelCase")]
pub struct ElementStyle {
pub font_size: Option<f64>,
pub color: Option<String>,
pub align: Option<String>,
pub bold: Option<bool>,
pub italic: Option<bool>,
pub anim: Option<String>,
// stat
pub label: Option<String>,
pub label_color: Option<String>,
pub label_size: Option<f64>,
// image
pub fit: Option<String>,
// shape
pub shape_type: Option<ShapeType>,
pub fill: Option<String>,
pub gradient: Option<bool>,
pub opacity: Option<f64>,
pub radius: Option<f64>,
// chart
pub max: Option<f64>,
pub chart_type: Option<ChartType>,
pub legend: Option<bool>,
pub stack: Option<bool>,
pub grid: Option<bool>,
// table
pub header: Option<bool>,
// code
pub lang: Option<String>,
// formula
#[serde(rename = "inline")]
pub inline_: Option<bool>,
// card
pub icon: Option<String>,
pub accent: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub struct SlideElement {
pub id: String,
#[serde(rename = "type")]
pub element_type: String,
pub x: f64,
pub y: f64,
pub w: f64,
pub h: f64,
pub content: String,
pub style: ElementStyle,
pub segments: Option<Vec<RichLine>>,
}
/* ---------- 幻灯片 / Deck ---------- */
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub struct Slide {
pub id: String,
pub background: String,
pub elements: Vec<SlideElement>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub struct Deck {
pub v: i32,
pub theme: String,
pub slides: Vec<Slide>,
pub chat_id: Option<String>,
}
/* ---------- AI 配置 ---------- */
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub struct AiCfg {
pub preset: String,
pub protocol: String,
pub base: String,
pub key: String,
pub model: String,
pub proxy: String,
pub img_base: Option<String>,
pub img_key: Option<String>,
pub img_model: Option<String>,
}
/* ---------- 文库 ---------- */
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub struct LibItem {
pub id: String,
pub name: String,
pub deck: Deck,
pub created_at: f64,
pub updated_at: f64,
}
/* ---------- 模板 ---------- */
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub struct PageTemplate {
pub id: String,
pub name: String,
pub category: String,
pub background: String,
pub elements: Vec<SlideElement>,
pub created_at: Option<f64>,
}
/* ---------- 会话 ---------- */
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub struct ChatMessage {
pub role: String,
pub content: String,
}
/* ---------- Op Log对应 src/core/op.ts ---------- */
/// Op 的 payload 是任意 JSON不导出 TS因为 payload 是动态的)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Op {
#[serde(rename = "type")]
pub op_type: String,
pub client_id: Option<String>,
pub timestamp: Option<f64>,
#[serde(flatten)]
pub payload: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryEntry {
pub forward: Op,
pub backward: Op,
}
/* ---------- 图表数据 ---------- */
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../bindings/types.ts")]
pub struct ChartItem {
pub label: Option<String>,
pub value: Option<f64>,
}