调整图片画布路由和画布数据模型
将图片画布入口改为 /editor/canvas 新增 editor_canvas 表并关联 editor_project 默认画布 更新 project API 响应中的 canvas 快照兼容层 统一图片画布侧栏列表项和图标按钮组件 同步前端测试、SpacetimeDB bindings、技术文档和 TRACKING 记录
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use crate::*;
|
||||
|
||||
const EDITOR_PROJECT_DEFAULT_TITLE: &str = "未命名画布";
|
||||
const EDITOR_CANVAS_DEFAULT_TITLE: &str = "默认画布";
|
||||
const EDITOR_PROJECT_MAX_TITLE_CHARS: usize = 80;
|
||||
const EDITOR_PROJECT_MAX_LAYOUT_JSON_BYTES: usize = 256 * 1024;
|
||||
const EDITOR_PROJECT_SOURCE_TYPES: [&str; 3] = ["uploaded", "generated", "mock_generated"];
|
||||
@@ -22,6 +23,25 @@ pub struct EditorProject {
|
||||
updated_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = editor_canvas,
|
||||
index(accessor = by_editor_canvas_project_id, btree(columns = [project_id])),
|
||||
index(accessor = by_editor_canvas_owner_user_id, btree(columns = [owner_user_id]))
|
||||
)]
|
||||
pub struct EditorCanvas {
|
||||
#[primary_key]
|
||||
canvas_id: String,
|
||||
project_id: String,
|
||||
owner_user_id: String,
|
||||
title: String,
|
||||
viewport_x: f64,
|
||||
viewport_y: f64,
|
||||
viewport_scale: f64,
|
||||
layers_json: String,
|
||||
created_at: Timestamp,
|
||||
updated_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = editor_project_resource,
|
||||
index(accessor = by_editor_project_resource_project_id, btree(columns = [project_id])),
|
||||
@@ -123,13 +143,23 @@ pub struct EditorProjectResourceSnapshot {
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, SpacetimeType)]
|
||||
pub struct EditorCanvasSnapshot {
|
||||
pub canvas_id: String,
|
||||
pub project_id: String,
|
||||
pub title: String,
|
||||
pub viewport: EditorProjectViewportSnapshot,
|
||||
pub layers_json: String,
|
||||
pub created_at_micros: i64,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, SpacetimeType)]
|
||||
pub struct EditorProjectSnapshot {
|
||||
pub project_id: String,
|
||||
pub owner_user_id: String,
|
||||
pub title: String,
|
||||
pub viewport: EditorProjectViewportSnapshot,
|
||||
pub layers_json: String,
|
||||
pub canvas: EditorCanvasSnapshot,
|
||||
pub resources: Vec<EditorProjectResourceSnapshot>,
|
||||
pub created_at_micros: i64,
|
||||
pub updated_at_micros: i64,
|
||||
@@ -233,7 +263,7 @@ fn create_editor_project(
|
||||
|
||||
ctx.db.editor_project().insert(EditorProject {
|
||||
project_id: project_id.clone(),
|
||||
owner_user_id,
|
||||
owner_user_id: owner_user_id.clone(),
|
||||
title,
|
||||
viewport_x: 0.0,
|
||||
viewport_y: 0.0,
|
||||
@@ -242,6 +272,13 @@ fn create_editor_project(
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
ensure_default_canvas(
|
||||
ctx,
|
||||
project_id.as_str(),
|
||||
owner_user_id.as_str(),
|
||||
None,
|
||||
now,
|
||||
)?;
|
||||
|
||||
build_project_snapshot(ctx, project_id.as_str())
|
||||
}
|
||||
@@ -290,7 +327,28 @@ fn save_editor_project_layout(
|
||||
let owner_user_id = normalize_required(&input.owner_user_id, "editor_project.owner_user_id")?;
|
||||
let layers_json = normalize_layout_json(input.layers_json)?;
|
||||
let project = require_owned_project(ctx, project_id.as_str(), owner_user_id.as_str())?;
|
||||
let now = Timestamp::from_micros_since_unix_epoch(input.updated_at_micros);
|
||||
let canvas = ensure_default_canvas(
|
||||
ctx,
|
||||
project.project_id.as_str(),
|
||||
project.owner_user_id.as_str(),
|
||||
Some(&project),
|
||||
now,
|
||||
)?;
|
||||
|
||||
ctx.db.editor_canvas().canvas_id().delete(&canvas.canvas_id);
|
||||
ctx.db.editor_canvas().insert(EditorCanvas {
|
||||
canvas_id: canvas.canvas_id,
|
||||
project_id: project.project_id.clone(),
|
||||
owner_user_id: project.owner_user_id.clone(),
|
||||
title: canvas.title,
|
||||
viewport_x: input.viewport.x,
|
||||
viewport_y: input.viewport.y,
|
||||
viewport_scale: input.viewport.scale.clamp(0.01, 8.0),
|
||||
layers_json: layers_json.clone(),
|
||||
created_at: canvas.created_at,
|
||||
updated_at: now,
|
||||
});
|
||||
ctx.db.editor_project().project_id().delete(&project_id);
|
||||
ctx.db.editor_project().insert(EditorProject {
|
||||
project_id: project.project_id.clone(),
|
||||
@@ -301,7 +359,7 @@ fn save_editor_project_layout(
|
||||
viewport_scale: input.viewport.scale.clamp(0.01, 8.0),
|
||||
layers_json,
|
||||
created_at: project.created_at,
|
||||
updated_at: Timestamp::from_micros_since_unix_epoch(input.updated_at_micros),
|
||||
updated_at: now,
|
||||
});
|
||||
|
||||
build_project_snapshot(ctx, project_id.as_str())
|
||||
@@ -384,6 +442,13 @@ fn build_project_snapshot(
|
||||
.project_id()
|
||||
.find(&project_key)
|
||||
.ok_or_else(|| "图片画布工程不存在".to_string())?;
|
||||
let canvas = ensure_default_canvas(
|
||||
ctx,
|
||||
project.project_id.as_str(),
|
||||
project.owner_user_id.as_str(),
|
||||
Some(&project),
|
||||
project.created_at,
|
||||
)?;
|
||||
let mut resources = ctx
|
||||
.db
|
||||
.editor_project_resource()
|
||||
@@ -401,18 +466,77 @@ fn build_project_snapshot(
|
||||
project_id: project.project_id,
|
||||
owner_user_id: project.owner_user_id,
|
||||
title: project.title,
|
||||
viewport: EditorProjectViewportSnapshot {
|
||||
x: project.viewport_x,
|
||||
y: project.viewport_y,
|
||||
scale: project.viewport_scale,
|
||||
},
|
||||
layers_json: project.layers_json,
|
||||
canvas: canvas_snapshot_from_row(canvas),
|
||||
resources,
|
||||
created_at_micros: project.created_at.to_micros_since_unix_epoch(),
|
||||
updated_at_micros: project.updated_at.to_micros_since_unix_epoch(),
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_default_canvas(
|
||||
ctx: &ReducerContext,
|
||||
project_id: &str,
|
||||
owner_user_id: &str,
|
||||
legacy_project: Option<&EditorProject>,
|
||||
now: Timestamp,
|
||||
) -> Result<EditorCanvas, String> {
|
||||
let canvas_id = default_canvas_id(project_id);
|
||||
if let Some(canvas) = ctx.db.editor_canvas().canvas_id().find(&canvas_id) {
|
||||
return Ok(canvas);
|
||||
}
|
||||
|
||||
let legacy_view = legacy_project
|
||||
.map(|project| {
|
||||
(
|
||||
project.viewport_x,
|
||||
project.viewport_y,
|
||||
project.viewport_scale,
|
||||
project.layers_json.clone(),
|
||||
project.created_at,
|
||||
project.updated_at,
|
||||
)
|
||||
})
|
||||
.unwrap_or((0.0, 0.0, 1.0, "[]".to_string(), now, now));
|
||||
let canvas = EditorCanvas {
|
||||
canvas_id: canvas_id.clone(),
|
||||
project_id: project_id.to_string(),
|
||||
owner_user_id: owner_user_id.to_string(),
|
||||
title: EDITOR_CANVAS_DEFAULT_TITLE.to_string(),
|
||||
viewport_x: legacy_view.0,
|
||||
viewport_y: legacy_view.1,
|
||||
viewport_scale: legacy_view.2,
|
||||
layers_json: legacy_view.3,
|
||||
created_at: legacy_view.4,
|
||||
updated_at: legacy_view.5,
|
||||
};
|
||||
ctx.db.editor_canvas().insert(canvas);
|
||||
ctx.db
|
||||
.editor_canvas()
|
||||
.canvas_id()
|
||||
.find(&canvas_id)
|
||||
.ok_or_else(|| "图片画布创建失败".to_string())
|
||||
}
|
||||
|
||||
fn default_canvas_id(project_id: &str) -> String {
|
||||
format!("{project_id}:canvas:default")
|
||||
}
|
||||
|
||||
fn canvas_snapshot_from_row(row: EditorCanvas) -> EditorCanvasSnapshot {
|
||||
EditorCanvasSnapshot {
|
||||
canvas_id: row.canvas_id,
|
||||
project_id: row.project_id,
|
||||
title: row.title,
|
||||
viewport: EditorProjectViewportSnapshot {
|
||||
x: row.viewport_x,
|
||||
y: row.viewport_y,
|
||||
scale: row.viewport_scale,
|
||||
},
|
||||
layers_json: row.layers_json,
|
||||
created_at_micros: row.created_at.to_micros_since_unix_epoch(),
|
||||
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
|
||||
}
|
||||
}
|
||||
|
||||
fn require_owned_project(
|
||||
ctx: &ReducerContext,
|
||||
project_id: &str,
|
||||
|
||||
@@ -230,6 +230,7 @@ macro_rules! migration_tables {
|
||||
asset_entity_binding,
|
||||
asset_event,
|
||||
editor_project,
|
||||
editor_canvas,
|
||||
editor_project_resource,
|
||||
puzzle_agent_session,
|
||||
puzzle_background_compile_task,
|
||||
|
||||
Reference in New Issue
Block a user