use crate::*; #[spacetimedb::table(accessor = creation_entry_config)] pub struct CreationEntryConfig { #[primary_key] pub(crate) config_id: String, pub(crate) start_title: String, pub(crate) start_description: String, pub(crate) start_idle_badge: String, pub(crate) start_busy_badge: String, pub(crate) modal_title: String, pub(crate) modal_description: String, pub(crate) updated_at: Timestamp, } #[spacetimedb::table( accessor = creation_entry_type_config, index(accessor = by_creation_entry_type_sort_order, btree(columns = [sort_order])) )] pub struct CreationEntryTypeConfig { #[primary_key] pub(crate) id: String, pub(crate) title: String, pub(crate) subtitle: String, pub(crate) badge: String, pub(crate) image_src: String, pub(crate) visible: bool, pub(crate) open: bool, pub(crate) sort_order: i32, pub(crate) updated_at: Timestamp, } #[spacetimedb::procedure] pub fn get_creation_entry_config( ctx: &mut ProcedureContext, ) -> CreationEntryConfigProcedureResult { match ctx.try_with_tx(|tx| get_or_seed_creation_entry_config_snapshot(tx)) { Ok(record) => CreationEntryConfigProcedureResult { ok: true, record: Some(record), error_message: None, }, Err(message) => CreationEntryConfigProcedureResult { ok: false, record: None, error_message: Some(message), }, } } fn get_or_seed_creation_entry_config_snapshot( ctx: &ReducerContext, ) -> Result { seed_creation_entry_config_if_missing(ctx); let header = ctx .db .creation_entry_config() .config_id() .find(CREATION_ENTRY_CONFIG_GLOBAL_ID.to_string()) .ok_or_else(|| "创作入口配置初始化失败".to_string())?; let mut creation_types = ctx .db .creation_entry_type_config() .iter() .map(|row| CreationEntryTypeSnapshot { id: row.id, title: row.title, subtitle: row.subtitle, badge: row.badge, image_src: row.image_src, visible: row.visible, open: row.open, sort_order: row.sort_order, updated_at_micros: row.updated_at.to_micros_since_unix_epoch(), }) .collect::>(); creation_types.sort_by(|left, right| { left.sort_order .cmp(&right.sort_order) .then_with(|| left.id.cmp(&right.id)) }); Ok(CreationEntryConfigSnapshot { config_id: header.config_id, start_card: CreationEntryStartCardSnapshot { title: header.start_title, description: header.start_description, idle_badge: header.start_idle_badge, busy_badge: header.start_busy_badge, }, type_modal: CreationEntryTypeModalSnapshot { title: header.modal_title, description: header.modal_description, }, creation_types, updated_at_micros: header.updated_at.to_micros_since_unix_epoch(), }) } fn seed_creation_entry_config_if_missing(ctx: &ReducerContext) { let now = ctx.timestamp; if ctx .db .creation_entry_config() .config_id() .find(CREATION_ENTRY_CONFIG_GLOBAL_ID.to_string()) .is_none() { ctx.db.creation_entry_config().insert(CreationEntryConfig { config_id: CREATION_ENTRY_CONFIG_GLOBAL_ID.to_string(), start_title: DEFAULT_CREATION_ENTRY_START_TITLE.to_string(), start_description: DEFAULT_CREATION_ENTRY_START_DESCRIPTION.to_string(), start_idle_badge: DEFAULT_CREATION_ENTRY_START_IDLE_BADGE.to_string(), start_busy_badge: DEFAULT_CREATION_ENTRY_START_BUSY_BADGE.to_string(), modal_title: DEFAULT_CREATION_ENTRY_MODAL_TITLE.to_string(), modal_description: DEFAULT_CREATION_ENTRY_MODAL_DESCRIPTION.to_string(), updated_at: now, }); } for seed in default_creation_entry_type_configs(now) { if ctx .db .creation_entry_type_config() .id() .find(&seed.id) .is_none() { ctx.db.creation_entry_type_config().insert(seed); } } } fn default_creation_entry_type_configs(now: Timestamp) -> Vec { vec![ build_creation_entry_type_seed("rpg", "文字冒险", "经典 RPG 体验", "内测", "/creation-type-references/rpg.webp", false, true, 10, now), build_creation_entry_type_seed("big-fish", "摸鱼", "轻量闯关玩法", "可创建", "/creation-type-references/big-fish.webp", false, true, 20, now), build_creation_entry_type_seed("puzzle", "拼图", "拼图关卡创作", "可创建", "/creation-type-references/puzzle.webp", true, true, 30, now), build_creation_entry_type_seed("match3d", "抓大鹅", "3D 消除关卡", "可创建", "/creation-type-references/match3d.webp", true, true, 40, now), build_creation_entry_type_seed("square-hole", "方洞", "形状投放挑战", "可创建", "/creation-type-references/square-hole.webp", true, true, 50, now), build_creation_entry_type_seed("visual-novel", "视觉小说", "分支叙事体验", "可创建", "/creation-type-references/visual-novel.webp", true, true, 60, now), build_creation_entry_type_seed("airp", "AI RPG", "原生角色扮演", "即将开放", "/creation-type-references/airp.webp", true, false, 70, now), build_creation_entry_type_seed("creative-agent", "智能体创作", "对话式创作实验", "内测", "/creation-type-references/creative-agent.webp", false, true, 80, now), ] } #[allow(clippy::too_many_arguments)] fn build_creation_entry_type_seed( id: &str, title: &str, subtitle: &str, badge: &str, image_src: &str, visible: bool, open: bool, sort_order: i32, now: Timestamp, ) -> CreationEntryTypeConfig { CreationEntryTypeConfig { id: id.to_string(), title: title.to_string(), subtitle: subtitle.to_string(), badge: badge.to_string(), image_src: image_src.to_string(), visible, open, sort_order, updated_at: now, } }