合并编辑器素材库分支

同步编辑器素材库最新资源公开展示能力

保留master已有回合入提交并整合当前分支改动

# Conflicts:
#	docs/project-memory/shared-memory/pitfalls.md
#	src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx
#	src/components/image-editor/ImageCanvasEditorModel.ts
#	src/components/image-editor/ImageCanvasEditorTypes.ts
This commit is contained in:
2026-06-24 17:30:14 +08:00
58 changed files with 1943 additions and 270 deletions
@@ -40,12 +40,12 @@
- 验证方式:运行品牌标识、绑定手机号页和平台首页相关前端测试,确认品牌图 `src``/branding/taonier-product-ip.png`;执行 `npm run check:encoding``git diff --check`
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md`
## 2026-06-22 创作主页精选展示项目内画布生成资源
## 2026-06-22 创作主页精选展示全站公开画布生成资源
- 背景:`/creation``陶泥儿精选` 曾从账号级素材库读取,并在素材为空时用公开作品图片补充,导致新创作页出现不属于任何当前图片画布项目的素材。
- 决策:`陶泥儿精选` 只从用户图片画布项目中的 `editor_project_resource` 构建素材包和素材,且只保留 `sourceType="generated"` 的画布生成资源;账号级 `editor_asset`、上传素材、公开作品图片和 `mock_generated` 都不作为精选来源
- 影响范围:`/creation` 创作主页、`creationShowcaseModel`最近项目列表资源读取、创作主页改版计划和精选素材相关测试。
- 验证方式:运行 `src/components/creation-home/creationShowcaseModel.test.ts``CreationLandingView.test.tsx`,确认项目内生成资源展示、上传素材过滤、公开作品不再 fallback。
- 决策:`陶泥儿精选` 从全站 `editor_project_resource` 构建素材包和素材,读取路径固定为 `GET /api/editor/showcase/resources`;只保留 `sourceType="generated"`、图片源非空且 `public_showcase_enabled = true` 的画布生成资源,按创建时间倒序展示。账号级 `editor_asset`、上传素材、公开作品图片和 `mock_generated` 都不作为精选来源。任意画布左侧素材列表中,单素材右键菜单承接文字 `删除``公开展示该素材` 勾选项;勾选项默认打开,修改时写回对应 `editor_project_resource.public_showcase_enabled`
- 影响范围:`/creation` 创作主页、`creationShowcaseModel`公开精选 BFF、图片画布素材列表右键菜单、账号素材库快照、创作主页改版计划和精选素材相关测试。
- 验证方式:运行 `src/components/creation-home/creationShowcaseModel.test.ts``CreationLandingView.test.tsx``ImageCanvasAssetRowView.test.tsx``useImageCanvasAssetLibrary.test.tsx``src/services/image-editor/editorProjectClient.test.ts`,确认公开生成资源展示、上传素材过滤、公开开关隐藏资源、删除入口在右键菜单中、公开作品不再 fallback。
- 关联文档:`docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md``docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
## 2026-06-22 编辑器生成模型默认定价调整
@@ -23,6 +23,14 @@
- 验证:`ImageCanvasEditorModel.test.ts` 覆盖素材库 source resource 保留,`useImageCanvasAssetCanvasBridge.test.tsx` 覆盖资源 ID 级联清理,`ImageCanvasEditorAssetsIntegration.test.tsx` 覆盖删除后保存的新 layout 不再包含被删图层。
- 关联:`src/components/image-editor/ImageCanvasEditorModel.ts``src/components/image-editor/useImageCanvasAssetCanvasBridge.ts``src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx`
## 陶泥儿精选重复先查同源同媒体画布副本
- 现象:每次从项目素材中把同一个生成素材拖到画布上,`陶泥儿精选` 都多出一张看起来相同的素材。
- 原因:素材拖入画布会为图层实例准备 `editor_project_resource`;如果该素材本来带 `sourceResourceId` 指向原始生成资源,而新资源仍按普通 generated 资源公开,精选就会把原件和每次拖拽产生的同源同媒体副本都展示出来。
- 处理:创建项目资源时保留 `source_resource_id`,并在同项目已有同源同媒体资源时复用已有 resource;确需创建同源同媒体副本时默认 `public_showcase_enabled = false`。公开精选读取和前端精选模型都跳过 `sourceResourceId` 指回同一媒体原件的副本,但不要按图片地址全局去重,避免不同生成步骤共享占位图时被误合并。
- 验证:`creationShowcaseModel.test.ts` 覆盖同源同媒体副本只展示原件;`ImageCanvasEditorAssetsIntegration.test.tsx` 覆盖拖拽生成素材到画布时继续提交 `sourceResourceId``cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 确认后端资源复用 / 精选过滤逻辑可编译。
- 关联:`server-rs/crates/spacetime-module/src/editor_project_storage.rs``src/components/creation-home/creationShowcaseModel.ts``src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx`
## 画板外部生成排队超时不是失败
- 现象:画板发起付费图片生成后,前端弹出 `生成任务仍在队列中,请稍后刷新画布查看结果`,但后端任务仍在队列或执行中,后续可能正常完成。
@@ -183,6 +191,14 @@
- 验证:`npm run test -- src/components/image-editor/useImageCanvasProjectPersistence.test.tsx -t "serializes project layout saves" --reporter verbose` 应覆盖慢保存期间不启动第二个 PATCH,首个保存完成后只发送最新待保存快照;排查发布现场时 429 行应从 `upstream_status=-` / Nginx `limit_conn` 收敛。
- 关联:`src/components/image-editor/useImageCanvasProjectPersistence.ts``src/components/image-editor/useImageCanvasProjectPersistence.test.tsx``docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
## 图片画布发布入口 429 也要查 read-url 换签爆发
- 现象:发布域名刚上线或刷新画板后出现短时间 `429`Nginx access log 中集中为同一 IP / 同一 `editor/canvas?projectid=...` referrer 的 `GET /api/assets/read-url?objectKey=generated-character-drafts/editor/ui-design-assets/.../asset-001.png` 到几十上百个 UI 设计切片;429 行常见 `request_time=0.000``upstream_status=-`error log 写 `limiting requests ... zone "genarrative_api_rps"`
- 原因:这类 429 是入口 Nginx `limit_req` 在转发前按 RPS burst 快拒,不是 api-server、SpacetimeDB、worker 或 VectorEngine 的业务 429。UI 设计提取、角色动画帧或大量私有素材恢复会让多个 `ResolvedAssetImage` 同时挂载;如果 `/api/assets/read-url` 只有同 key pending 去重和缓存,没有跨 objectKey 节流,一个页面能在同一秒内发出数百个不同 objectKey 换签请求并打满 `genarrative_api_rps` burst。
- 处理:不要先放大 Nginx 通用 API 限流;先按 access log 聚合 `read-url` 数量、状态和 referrer,确认是否同一画板页面触发。`assetReadUrlService` 必须统一承接私有 generated 资源换签,并在真实请求前做跨组件轻量节流;画板、素材库、运行态和结果页不得直接绕过该服务调用 `/api/assets/read-url`
- 验证:`npm run test -- src/services/assetReadUrlService.test.ts --reporter verbose` 应覆盖大量不同 objectKey 同时换签时首批限量放行、后续按间隔派发;发布现场同类页面刷新时,Nginx `GET /api/assets/read-url` 429 应从 `upstream_status=-` / `genarrative_api_rps` 收敛。
- 关联:`src/services/assetReadUrlService.ts``src/hooks/useResolvedAssetReadUrl.ts``src/components/ResolvedAssetImage.tsx``docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
## 图片编辑器 Seedance 2.0 参考媒体不要提交视频 Data URL
- 现象:画板生成视频选择 Seedance 2.0 并上传参考视频后,请求体暴涨、可能返回 `413` 或上游拒绝 `video_url.url`;文档示例或测试如果写 `data:video/mp4;base64,...`,后续实现很容易照抄。
@@ -46,7 +46,7 @@
- 新增 `editor_asset_folder` 表保存账号级素材文件夹:`folderId``ownerUserId`、名称、排序、折叠状态、系统默认标记、创建时间和更新时间。素材文件夹不归属于 project,同一个账号进入任一项目都能看到。
- 新增 `editor_asset` 表保存账号级素材:`assetId``ownerUserId``folderId`、名称、图片读取地址、OSS / asset object 引用、图片尺寸、来源类型、prompt、actualPrompt、model、provider、taskId、`assetKind``generationInputs`、创建时间和更新时间。素材只跟账号走,不跟 project 走;角色、图标、UI 设计图、视频和音频等生成结果的用户可见输入快照随素材保存。
- `editor_project_resource` 表保存工程画布引用过的资源快照:`resourceId``projectId``ownerUserId`、OSS / asset object 引用、图片尺寸、来源类型、prompt、actualPrompt、model、provider、taskId、sourceResourceId、`assetKind``generationInputs`、创建时间和更新时间。上传素材被拖入画布时会复制为 project resource,图层只引用 resourceId;图片、图标和 UI 素材生成 BFF 在请求携带 `projectId` 时由后端直接创建新 resource,并把 `resourceId` 随生成响应返回给前端。图片生成请求如果同时携带 `canvasCompletion`(生成器 `dialogId`、标题和占位框,或无 dialog 的右侧完成占位),BFF / worker 在生成成功后必须直接读取当前项目布局,优先使用最新 `generation-dialog` 占位框位置;只有当前布局仍存在对应 `generation-dialog` 时才插入轻量结果图层、把生成器标记为 `idle` 并写入 `generatedLayerId`,沿用后端当前 viewport 保存布局,再返回或刷新最新项目快照;前端只应用该快照刷新显示,不把生成完成态作为本地业务真相,也不在项目加载时根据资源行推断完成态。有项目上下文但后端没有返回项目快照时,前端不得本地补结果图层,只保留当前生成器交互状态等待下一次项目刷新。
- 图片、音频、视频和角色动画帧文件本体继续走 OSS / asset object;浏览器读取私有 generated 对象统一经 `/api/assets/read-url` 换签,签名 URL 可在 session 内复用,但不得作为持久化真相。
- 图片、音频、视频和角色动画帧文件本体继续走 OSS / asset object;浏览器读取私有 generated 对象统一经 `/api/assets/read-url` 换签,签名 URL 可在 session 内复用,但不得作为持久化真相。`/api/assets/read-url` 属于页面展示层高频后台请求,前端统一在 `assetReadUrlService` 内做同 key pending 去重、session 缓存和跨组件节流;UI 设计切片、角色动画帧或大量素材恢复时不得绕过该服务并发换签,否则单页可在同一秒内打满发布入口 `genarrative_api_rps` burst。
- 登录态上传和生成结果必须先落 OSS / asset object,再向 `editor_project_resource` / `editor_asset` 写入轻量 `imageSrc: "/<objectKey>"``objectKey``assetObjectId`;未登录演示态可以在内存里使用 Data URL 预览,但项目、素材库、项目资源和 `editor_canvas.layers_json` 不得写入 `data:image/*``data:video/*``data:audio/*``blob:`。旧数据读取时如果已有 `objectKey``imageSrc` 归一成 `/<objectKey>`;没有 `objectKey` 的旧 Data URL 需要走修复上传并回写轻量引用。上传到生成面板参考图槽位的图片必须先创建 `editor_project_resource` 行;没有当前工程 ID 时才创建账号级 `editor_asset` 行,随后把对应 `resourceId``assetId` 写入参考图临时状态,生成请求仍使用临时状态中的图片源或 `objectKey`
- 资源表保存资源和素材级元数据;图层位置、层级、分组选中所需 ID 和 groupId 保存在 `editor_canvas` 的布局 JSON。布局 JSON 是混合数组:普通图层按 `layerId/resourceId` 保存,生成器占位和生成器对话框按 `itemType: "generation-dialog"` 保存,不新增单独表。普通图层的新保存不再把 `assetKind/generationInputs` 写入布局 JSON;刷新时优先从 `editor_project_resource` 恢复,旧布局中的同名字段只作为兼容兜底。生成器快照必须包含生成器 ID、模式、提示词、参数、参考图、状态、占位框位置和可选 `generatedLayerId`;宣发素材生成器还必须保存并恢复 `publicationWorkflowId``publicationGameInfo``publicationReferences`,避免刷新后生成卡片字段或参考图丢失。生成器快照中的参考图同样只保存 `resourceId/sourceAssetId` 行引用和展示所需 label,不保存图片 Data URL、signed URL 或 `objectKey`;刷新时用 `editor_project_resource` / `editor_asset` 行恢复临时生成请求所需图片源。生成成功后仍保存该快照,只是渲染时由 `generatedLayerId` 锚定到成品图层而不重复显示灰色占位框。`generationInputs.references` 是用户可见输入快照中的行级索引,只允许保存 `{ title, label, refType, refId }`;生成接口所需的图片 Data URL、signed URL 或 `objectKey` 只存在于提交前的临时参考图状态和请求体字段,不进入资源 / 素材元数据。图层展示尺寸不再作为独立 `Size` 真相保存,刷新与新建图层均按 `Resolution``originalWidth/originalHeight`)原分辨率显示。图层组第一版是画布内布局语义,不单独建表。
- 图片类生成结果除作为 `editor_project_resource` 和画布图层保存外,还要写入账号级 `editor_asset` 素材库;该写入由生成 BFF 在请求携带 `assetFolderId` 时完成。`GENARRATIVE_EXTERNAL_GENERATION_MODE=queue` 下,画布图片、改图、图标素材、UI 素材提取、角色动作、视频、音效和背景音乐生成都先返回 `queueState`,前端轮询 `/api/runtime/external-generation/jobs/{jobId}` 到完成后重新读取项目快照;`inline` 或无项目上下文时才使用响应中的 resource / asset 快照做本地落画布兜底,不再把同一生成结果二次调用素材创建接口。生成请求失败、inline 完成或 queue 任务终态完成 / 失败后,右上角泥点 chip 必须通过 `/profile/dashboard` 回读余额,不做本地乐观扣减。视频结果当前只保存为画布视频资源,不进入图片素材库。
@@ -468,7 +468,7 @@ npm run check:server-rs-ddd
- Rust 结构体:`EditorProjectResource`
- 源码:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`
- 说明:图片画布工程资源元数据表,保存已经放入某个 project 画布的上传 / 生成图片资源快照、OSS 引用、尺寸、来源类型、prompt、provider、task、源资源关系、`asset_kind``generation_inputs_json``asset_kind` 标记角色、图标、UI 设计图、视频、音频等素材类别;`generation_inputs_json` 保存用户可见生成输入快照,供图片信息页刷新后恢复。图片 / 图标 / UI 提取等生成 BFF 在请求携带 `project_id` 时负责创建该表记录并把 resource 快照返回前端;前端只保存布局引用,不能把同一生成结果再次作为正式业务真相写入。账号级素材删除不级联删除该表,避免历史画布丢图。`editor_canvas.layers_json` 只保存图层几何、层级、分组、资源引用和生成器对象;新写入不再把素材生成输入快照作为图层布局真相保存,旧布局字段只作为兼容兜底读取。
- 说明:图片画布工程资源元数据表,保存已经放入某个 project 画布的上传 / 生成图片资源快照、OSS 引用、尺寸、来源类型、prompt、provider、task、源资源关系、`asset_kind``generation_inputs_json``public_showcase_enabled``asset_kind` 标记角色、图标、UI 设计图、视频、音频等素材类别;`generation_inputs_json` 保存用户可见生成输入快照,供图片信息页刷新后恢复`public_showcase_enabled` 控制该生成资源是否进入 `/creation``陶泥儿精选`,新增和旧行默认 `true`,但通过 `source_resource_id` 指向同一 owner 源资源且媒体引用相同的画布副本默认不公开。图片 / 图标 / UI 提取等生成 BFF 在请求携带 `project_id` 时负责创建该表记录并把 resource 快照返回前端;前端只保存布局引用,不能把同一生成结果再次作为正式业务真相写入。从账号级素材库把同一生成素材拖回同一项目画布时,后端优先复用同项目内同源同媒体资源,避免每个图层实例都插入新的资源行。公开精选读取走 `GET /api/editor/showcase/resources`,只返回 `source_type = generated`、图片源非空且 `public_showcase_enabled = true` 的全站资源,并跳过同源同媒体副本,按 `created_at` 倒序取前 36 条;公开开关修改走登录鉴权 `PATCH /api/editor/project-resources/{resource_id}/showcase`,只允许资源 owner 更新。账号级素材删除不级联删除该表,避免历史画布丢图。`editor_canvas.layers_json` 只保存图层几何、层级、分组、资源引用和生成器对象;新写入不再把素材生成输入快照作为图层布局真相保存,旧布局字段只作为兼容兜底读取。
- 索引:`by_editor_project_resource_project_id``by_editor_project_resource_owner_user_id`
### `editor_asset_folder`
@@ -482,7 +482,7 @@ npm run check:server-rs-ddd
- Rust 结构体:`EditorAsset`
- 源码:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`
- 说明:图片画布账号级素材表,保存用户上传 / 生成素材的名称、文件夹、图片读取地址、OSS 引用、尺寸、来源类型、prompt、provider、task、`asset_kind``generation_inputs_json`。素材在同一账号的所有项目中可见;图片 / 图标 / UI 提取等生成 BFF 在请求携带 `asset_folder_id` 时负责创建账号级生成素材并返回 asset 快照,前端只用该快照更新素材栏。素材放入画布时复制为 `editor_project_resource` 并由图层引用 resourceId,画布从 resource / asset 级元数据恢复素材类别和用户可见生成输入快照。
- 说明:图片画布账号级素材表,保存用户上传 / 生成素材的名称、文件夹、图片读取地址、OSS 引用、尺寸、来源类型、prompt、provider、task、`asset_kind``generation_inputs_json` 和可选 `source_resource_id`。素材在同一账号的所有项目中可见;图片 / 图标 / UI 提取等生成 BFF 在请求携带 `asset_folder_id` 时负责创建账号级生成素材并返回 asset 快照,若同次生成也创建了 `editor_project_resource`,则把该 `resource_id` 写入 `source_resource_id`。素材库快照通过 `source_resource_id` 回查对应 project resource 的 `public_showcase_enabled`,供左侧素材菜单展示和切换公开状态;公开开关本身仍只落在 `editor_project_resource`,不在账号素材表复制真相。素材放入画布时复制为 `editor_project_resource` 并由图层引用 resourceId,画布从 resource / asset 级元数据恢复素材类别和用户可见生成输入快照。
- 索引:`by_editor_asset_owner_user_id``by_editor_asset_folder_id`
### `inventory_slot`
@@ -15,7 +15,7 @@
- 顶级导航中“创作”进入 `/creation`,“草稿”改为“项目”并进入 `/project`
- 移动端隐藏“创作”和“项目”入口,只保留浏览和个人相关入口;移动端直达 `/creation` 时提示用户使用桌面端打开。
- 登录后在创作主页展示最近项目,并通过新建项目进入 `/editor/canvas?projectid=xxx`
- `陶泥儿精选` 展示已经存在于用户图片画布项目中的画布生成素材包和素材,不使用账号级非项目素材、公开作品补充或 mock 素材。
- `陶泥儿精选` 展示全站用户图片画布项目中允许公开展示的画布生成素材包和素材,不使用账号级非项目素材、公开作品补充或 mock 素材。
- 现有 `/creation/<play>` 玩法工作台、草稿、作品架、生成恢复和发布链路保持不变。
## 页面结构
@@ -61,9 +61,9 @@
### 陶泥儿精选
`陶泥儿精选` 是页面底部的项目内画布生成素材瀑布流,不承载玩法入口列表。瀑布流卡片按真实素材宽高设置预览比例,同一行允许出现不同高度卡片,不使用固定等高网格。创作入口配置仍继续来自 `/api/creation-entry/config`,供旧创作入口和具体 `/creation/<play>` 工作台使用,但不作为本页精选区内容。
`陶泥儿精选` 是页面底部的全站公开画布生成素材瀑布流,不承载玩法入口列表。瀑布流卡片按真实素材宽高设置预览比例,同一行允许出现不同高度卡片,不使用固定等高网格。创作入口配置仍继续来自 `/api/creation-entry/config`,供旧创作入口和具体 `/creation/<play>` 工作台使用,但不作为本页精选区内容。
精选内容只使用 `editor_project_resource``sourceType="generated"` 的项目资源,代表用户通过图片画布生成已经落入某个项目的素材。账号级 `editor_asset` 只表示跨项目素材库,不单独作为精选来源;上传素材、公开作品图片和 `mock_generated` 资源都不进入精选。若现有数据缺少提示词、作者名或成本字段,v1 显示保守占位,不伪造内容。
精选内容只使用 `editor_project_resource``sourceType="generated"``public_showcase_enabled = true` 的项目资源,代表用户通过图片画布生成已经落入某个项目并允许公开展示的素材。账号级 `editor_asset` 只表示跨项目素材库,不单独作为精选来源;上传素材、公开作品图片和 `mock_generated` 资源都不进入精选。从项目素材把同一个生成素材再次拖入画布时,只是新增图层实例或复用同源同媒体项目资源,不应额外生成新的精选候选;历史上已经产生的同源同媒体副本也需要在精选读取时跳过。若现有数据缺少提示词、作者名或成本字段,v1 显示保守占位,不伪造内容。
Tab
@@ -100,10 +100,11 @@ Tab
数据来源:
- 读取用户项目列表中的 `editor_project_resource` 快照,只保留 `sourceType="generated"` 的画布生成资源
- 读取公开 BFF `GET /api/editor/showcase/resources` 返回的全站公开 `editor_project_resource` 快照,只保留 `sourceType="generated"` `publicShowcaseEnabled !== false` 的画布生成资源;若某条资源通过 `sourceResourceId` 指回同一 owner 的源资源且媒体引用相同,则视为拖拽画布副本,不进入精选
- 素材包、角色、UI、音乐、音效、视频和美宣都必须来自已存在项目资源;素材库里尚未放入任何项目的账号级素材不展示。
- 未登录、项目为空或项目内没有画布生成资源时,不再用公开作品图片补充,只显示简洁空态。
- 未登录用户也可读取公开精选;当没有公开画布生成资源时,不再用公开作品图片补充,只显示简洁空态。
- 上传素材、公开作品图片、mock 资源和假组合都不进入精选。
- 任意画布左侧素材列表中,单个素材右键打开素材菜单;原外置删除按钮移入该菜单,以文字 `删除` 展示。菜单内 `公开展示该素材` 默认勾选,取消勾选后调用 `PATCH /api/editor/project-resources/{resourceId}/showcase` 写回 `public_showcase_enabled = false`,该资源从 `陶泥儿精选` 移除。
- 不为了填满展示区创建假素材、假作者、假泥点成本或假组合关系。
- 暂无真实数据的 Tab 保留 Tab 入口,但内容区显示简洁空态。
@@ -114,7 +115,7 @@ Tab
- 最近项目继续使用编辑器项目接口:`listEditorProjects``createEditorProject``/editor/canvas?projectid=xxx`
- 最近项目和项目页打开画布时,浏览器 history 只保留带 `projectid` 的最终画布路由。
- 项目封面逻辑复用 `/project` 项目卡已有的画布中心缩略图算法。
- `陶泥儿精选` 读取项目内画布生成资源;若字段缺失,前端只显示空值或隐藏对应元信息,不在前端推导业务真相
- `陶泥儿精选` 读取后端公开精选接口返回的全站公开画布生成资源;公开开关以后端 `editor_project_resource.public_showcase_enabled` 为准,前端只做展示过滤、同源同媒体副本兜底过滤和乐观交互回滚
- `/creation/<play>` 的玩法工作台、草稿、生成页、结果页、发布、运行态和作品架链路保持原状。
## 路由与导航
@@ -127,7 +128,7 @@ Tab
- 桌面端原“草稿”入口改为“项目”,点击 `pushAppHistoryPath('/project')`
- 桌面端平台一级页统一复用 `/creation` 的平台桌面 chrome,保留左侧完整导航栏和右侧顶部栏;推荐、发现、我的、`/creation``/project` 只替换右侧主内容区,不能作为脱离导航的独立全屏页渲染,避免用户进入任一页后无法返回或切换页签。
- 桌面端从 `/creation` 切换到 `/project` 后,“项目”页签进入选中态,“创作”页签必须恢复为普通默认态,不保留创作主页的主按钮强调表现。
- `/project` 项目页头部必须提供 `返回主站` 按钮,点击后切回平台首页 `/`,行为与桌面侧栏切回推荐页保持一致
- `/project` 项目页头部不展示 `返回主站` 按钮;用户通过左侧导航在主站页签之间切换,项目页右侧只保留项目管理动作
- 移动端导航列表不展示“创作”和“项目”。
- 移动端首次进入或刷新 `/` 平台首页时显示欢迎弹窗,文案提示移动端仅支持作品展示,体验创作工具请使用电脑端访问;点击 `好` 关闭后,本次页面生命周期内切换下方页签再回到首页不再弹出。
- 移动端直接访问 `/creation` 时显示桌面端打开引导,不加载创作工具主页;`/creation/<play>` 继续直达对应玩法工作台。`/project` 暂不新增移动端拦截。
@@ -172,8 +173,8 @@ Tab
- `appPageRoutes.test.ts`:覆盖 `/creation -> creation-home`,并确认现有 `/creation/<play>` 不回归。
- `RpgEntryHomeView.recharge.test.tsx`:桌面显示“创作”“项目”,不显示“草稿”;移动端不显示“创作”和“项目”;我的页不再出现项目快捷入口。
- `CreationLandingView.test.tsx`:覆盖首屏主视觉、九大功能、最近项目、新建入口、查看全部、未登录隐藏最近项目、素材 Tab。
- `PlatformEntryFlowShellImpl` 相关测试:点击“创作”进入 `/creation`,点击“项目”进入 `/project`项目页头部“返回主站”回到 `/`新建项目进入 `/editor/canvas?projectid=xxx`
- `ProjectGalleryView.test.tsx`:封面抽取后项目封面、头部“返回主站”、重命名、删除和选择模式保持通过。
- `PlatformEntryFlowShellImpl` 相关测试:点击“创作”进入 `/creation`,点击“项目”进入 `/project`,新建项目进入 `/editor/canvas?projectid=xxx`
- `ProjectGalleryView.test.tsx`:封面抽取后项目封面、重命名、删除和选择模式保持通过。
验证命令:
@@ -194,7 +195,7 @@ git diff --check
- 登录后显示最近项目、新建项目、最多 4 个项目和“查看全部”。
- 点击新建项目进入 `/editor/canvas?projectid=xxx`
- 桌面导航显示“项目”,不显示“草稿”。
- `/project` 头部点击“返回主站”回到 `/`,并退出项目页主内容
- `/project` 头部不展示“返回主站”,右侧只保留选择和新建等项目管理动作
- 移动视口底部不显示“创作”和“项目”。
- 移动端进入 `/` 首页时显示 `欢迎` 弹窗,正文包含移动端仅支持作品展示和电脑端访问提示,按钮为 `好`
- 移动端直达 `/creation` 显示“请在桌面端打开创作主页”,点击“返回首页”回到 `/`
@@ -35,7 +35,8 @@ use spacetime_client::{
EditorProjectDeleteRecordInput, EditorProjectGetRecordInput,
EditorProjectLayoutSaveRecordInput, EditorProjectRecord, EditorProjectRenameRecordInput,
EditorProjectResourceCreateRecordInput, EditorProjectResourceMediaRepairRecordInput,
EditorProjectResourceRecord, SpacetimeClientError,
EditorProjectResourcePublicShowcaseListRecordInput, EditorProjectResourceRecord,
EditorProjectResourceShowcaseUpdateRecordInput, SpacetimeClientError,
};
use crate::{
@@ -142,6 +143,12 @@ pub struct EditorProjectResourceCreateRequest {
generation_inputs: Option<Value>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EditorProjectResourceShowcaseUpdateRequest {
public_showcase_enabled: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EditorAssetFolderCreateRequest {
@@ -298,6 +305,12 @@ pub struct EditorProjectResourceResponse {
resource: EditorProjectResourcePayload,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EditorProjectResourceListResponse {
resources: Vec<EditorProjectResourcePayload>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EditorAssetLibraryResponse {
@@ -479,6 +492,7 @@ pub struct EditorProjectResourcePayload {
source_resource_id: Option<String>,
asset_kind: Option<String>,
generation_inputs: Option<Value>,
public_showcase_enabled: bool,
created_at: String,
updated_at: String,
}
@@ -521,6 +535,8 @@ pub struct EditorAssetPayload {
task_id: Option<String>,
asset_kind: Option<String>,
generation_inputs: Option<Value>,
source_resource_id: Option<String>,
public_showcase_enabled: Option<bool>,
created_at: String,
updated_at: String,
}
@@ -587,6 +603,28 @@ pub async fn list_editor_projects(
))
}
pub async fn list_public_editor_project_resources(
State(state): State<AppState>,
Extension(request_context): Extension<RequestContext>,
) -> Result<Json<Value>, AppError> {
let resources = state
.spacetime_client()
.list_public_editor_project_resources(EditorProjectResourcePublicShowcaseListRecordInput {
limit: 36,
})
.await
.map_err(map_editor_project_error)?;
let resources = resources
.into_iter()
.map(editor_project_resource_payload_from_record)
.collect();
Ok(json_success_body(
Some(&request_context),
EditorProjectResourceListResponse { resources },
))
}
pub async fn create_editor_project(
State(state): State<AppState>,
Extension(request_context): Extension<RequestContext>,
@@ -763,6 +801,32 @@ pub async fn create_editor_project_resource(
))
}
pub async fn update_editor_project_resource_showcase(
State(state): State<AppState>,
Path(resource_id): Path<String>,
Extension(request_context): Extension<RequestContext>,
Extension(authenticated): Extension<AuthenticatedAccessToken>,
Json(payload): Json<EditorProjectResourceShowcaseUpdateRequest>,
) -> Result<Json<Value>, AppError> {
let resource = state
.spacetime_client()
.update_editor_project_resource_showcase(EditorProjectResourceShowcaseUpdateRecordInput {
resource_id,
owner_user_id: current_owner_user_id(&authenticated),
public_showcase_enabled: payload.public_showcase_enabled,
updated_at_micros: current_utc_micros(),
})
.await
.map_err(map_editor_project_error)?;
Ok(json_success_body(
Some(&request_context),
EditorProjectResourceResponse {
resource: editor_project_resource_payload_from_record(resource),
},
))
}
pub async fn get_editor_asset_library(
State(state): State<AppState>,
Extension(request_context): Extension<RequestContext>,
@@ -892,6 +956,7 @@ pub async fn create_editor_asset(
task_id: normalize_optional_string(payload.task_id),
asset_kind: normalize_optional_string(payload.asset_kind),
generation_inputs_json,
source_resource_id: None,
now_micros: current_utc_micros(),
})
.await
@@ -2431,6 +2496,7 @@ pub(crate) fn editor_project_resource_payload_from_record(
generation_inputs: record
.generation_inputs
.map(sanitize_editor_payload_inline_media),
public_showcase_enabled: record.public_showcase_enabled,
created_at: record.created_at,
updated_at: record.updated_at,
}
@@ -2489,6 +2555,8 @@ pub(crate) fn editor_asset_payload_from_record(record: EditorAssetRecord) -> Edi
generation_inputs: record
.generation_inputs
.map(sanitize_editor_payload_inline_media),
source_resource_id: record.source_resource_id,
public_showcase_enabled: record.public_showcase_enabled,
created_at: record.created_at,
updated_at: record.updated_at,
}
@@ -2652,6 +2720,10 @@ async fn persist_editor_generated_asset(
let folder_id =
normalize_generated_asset_folder_id(input.folder_id, input.owner_user_id.as_str());
let created_resource_id = resource
.as_ref()
.map(|resource| resource.resource_id.clone())
.or_else(|| normalize_optional_string(input.source_resource_id.clone()));
let asset = if let Some(folder_id) = folder_id {
let record = state
.spacetime_client()
@@ -2673,6 +2745,7 @@ async fn persist_editor_generated_asset(
task_id: Some(input.task_id),
asset_kind: normalize_optional_string(input.asset_kind),
generation_inputs_json,
source_resource_id: created_resource_id,
now_micros,
})
.await
@@ -3798,6 +3871,7 @@ mod tests {
source_resource_id: None,
asset_kind: Some("spec".to_string()),
generation_inputs: None,
public_showcase_enabled: true,
created_at: "2026-06-23T00:00:00.000Z".to_string(),
updated_at: "2026-06-23T00:00:00.000Z".to_string(),
}];
@@ -3868,6 +3942,7 @@ mod tests {
source_resource_id: None,
asset_kind: Some("spec".to_string()),
generation_inputs: None,
public_showcase_enabled: true,
created_at: "2026-06-23T00:00:00.000Z".to_string(),
updated_at: "2026-06-23T00:00:00.000Z".to_string(),
}];
@@ -3934,6 +4009,7 @@ mod tests {
source_resource_id: None,
asset_kind: Some("character".to_string()),
generation_inputs: None,
public_showcase_enabled: true,
created_at: "2026-06-23T00:00:00.000Z".to_string(),
updated_at: "2026-06-23T00:00:00.000Z".to_string(),
};
@@ -4393,6 +4469,7 @@ mod tests {
"fields": [{"title": "角色设定", "value": "银发游侠"}],
"references": []
})),
public_showcase_enabled: true,
created_at: "2026-06-21T12:16:22.000Z".to_string(),
updated_at: "2026-06-21T12:16:22.000Z".to_string(),
};
@@ -4489,6 +4566,7 @@ mod tests {
source_resource_id: None,
asset_kind: Some("character".to_string()),
generation_inputs: None,
public_showcase_enabled: true,
created_at: "2026-06-21T12:16:22.000Z".to_string(),
updated_at: "2026-06-21T12:16:22.000Z".to_string(),
};
@@ -4569,6 +4647,7 @@ mod tests {
source_resource_id: None,
asset_kind: Some("character".to_string()),
generation_inputs: None,
public_showcase_enabled: true,
created_at: "2026-06-21T12:16:22.000Z".to_string(),
updated_at: "2026-06-21T12:16:22.000Z".to_string(),
};
@@ -4690,6 +4769,7 @@ mod tests {
source_resource_id: None,
asset_kind: Some("character".to_string()),
generation_inputs: None,
public_showcase_enabled: true,
created_at: "2026-06-21T12:16:22.000Z".to_string(),
updated_at: "2026-06-21T12:16:22.000Z".to_string(),
};
@@ -490,6 +490,7 @@ pub async fn create_external_editor_asset(
task_id: normalize_optional_string(payload.task_id),
asset_kind: normalize_optional_string(payload.asset_kind),
generation_inputs_json,
source_resource_id: None,
now_micros: current_utc_micros(),
})
.await
@@ -13,8 +13,9 @@ use crate::{
delete_editor_project, edit_editor_image, extract_editor_ui_design_assets,
generate_editor_icon_spritesheet, generate_editor_image, get_editor_asset_library,
get_editor_generation_pricing, get_editor_project, list_editor_projects,
load_recent_editor_project, rename_editor_project, save_editor_project_layout,
update_editor_asset, update_editor_asset_folder,
list_public_editor_project_resources, load_recent_editor_project, rename_editor_project,
save_editor_project_layout, update_editor_asset, update_editor_asset_folder,
update_editor_project_resource_showcase,
},
state::AppState,
};
@@ -27,6 +28,10 @@ pub fn router(state: AppState) -> Router<AppState> {
"/api/editor/generation-pricing",
get(get_editor_generation_pricing),
)
.route(
"/api/editor/showcase/resources",
get(list_public_editor_project_resources),
)
.route(
"/api/editor/projects/recent",
get(load_recent_editor_project).route_layer(middleware::from_fn_with_state(
@@ -67,6 +72,12 @@ pub fn router(state: AppState) -> Router<AppState> {
require_bearer_auth,
)),
)
.route(
"/api/editor/project-resources/{resource_id}/showcase",
patch(update_editor_project_resource_showcase).route_layer(
middleware::from_fn_with_state(state.clone(), require_bearer_auth),
),
)
.route(
"/api/editor/assets/library",
get(get_editor_asset_library).route_layer(middleware::from_fn_with_state(
@@ -235,6 +235,56 @@ impl SpacetimeClient {
.await
}
pub async fn update_editor_project_resource_showcase(
&self,
input: EditorProjectResourceShowcaseUpdateRecordInput,
) -> Result<EditorProjectResourceRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"update_editor_project_resource_showcase_and_return",
move |connection, sender| {
connection
.procedures()
.update_editor_project_resource_showcase_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_resource_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn list_public_editor_project_resources(
&self,
input: EditorProjectResourcePublicShowcaseListRecordInput,
) -> Result<Vec<EditorProjectResourceRecord>, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"list_public_editor_project_resources_and_return",
move |connection, sender| {
connection
.procedures()
.list_public_editor_project_resources_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_resource_list_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn get_editor_asset_library(
&self,
owner_user_id: String,
+2 -1
View File
@@ -38,7 +38,8 @@ pub use mapper::{
EditorProjectDeleteRecordInput, EditorProjectGetRecordInput, EditorProjectLayoutSaveAckRecord,
EditorProjectLayoutSaveRecordInput, EditorProjectRecord, EditorProjectRenameRecordInput,
EditorProjectResourceCreateRecordInput, EditorProjectResourceMediaRepairRecordInput,
EditorProjectResourceRecord, ExternalApiKeyAuthenticateRecordInput,
EditorProjectResourcePublicShowcaseListRecordInput, EditorProjectResourceRecord,
EditorProjectResourceShowcaseUpdateRecordInput, ExternalApiKeyAuthenticateRecordInput,
ExternalApiKeyCreateRecordInput, ExternalApiKeyRecord, ExternalApiKeyRevokeRecordInput,
ExternalGenerationJobAcknowledgeRecordInput, ExternalGenerationJobClaimRecordInput,
ExternalGenerationJobCompleteRecordInput, ExternalGenerationJobEnqueueRecordInput,
@@ -81,7 +81,8 @@ pub use self::editor_project::{
EditorProjectDeleteRecordInput, EditorProjectGetRecordInput, EditorProjectLayoutSaveAckRecord,
EditorProjectLayoutSaveRecordInput, EditorProjectRecord, EditorProjectRenameRecordInput,
EditorProjectResourceCreateRecordInput, EditorProjectResourceMediaRepairRecordInput,
EditorProjectResourceRecord,
EditorProjectResourcePublicShowcaseListRecordInput, EditorProjectResourceRecord,
EditorProjectResourceShowcaseUpdateRecordInput,
};
pub use self::external_api_key::{
ExternalApiKeyAuthenticateRecordInput, ExternalApiKeyCreateRecordInput, ExternalApiKeyRecord,
@@ -210,7 +211,9 @@ pub(crate) use self::editor_project::{
map_editor_asset_library_procedure_result, map_editor_asset_procedure_result,
map_editor_project_delete_procedure_result, map_editor_project_layout_save_procedure_result,
map_editor_project_list_procedure_result, map_editor_project_optional_procedure_result,
map_editor_project_required_procedure_result, map_editor_project_resource_procedure_result,
map_editor_project_required_procedure_result,
map_editor_project_resource_list_procedure_result,
map_editor_project_resource_procedure_result,
};
pub(crate) use self::external_api_key::{
map_external_api_key_list_procedure_result, map_external_api_key_single_procedure_result,
@@ -57,6 +57,7 @@ pub struct EditorProjectResourceRecord {
pub source_resource_id: Option<String>,
pub asset_kind: Option<String>,
pub generation_inputs: Option<serde_json::Value>,
pub public_showcase_enabled: bool,
pub created_at: String,
pub updated_at: String,
}
@@ -90,6 +91,8 @@ pub struct EditorAssetRecord {
pub task_id: Option<String>,
pub asset_kind: Option<String>,
pub generation_inputs: Option<serde_json::Value>,
pub source_resource_id: Option<String>,
pub public_showcase_enabled: Option<bool>,
pub created_at: String,
pub updated_at: String,
}
@@ -169,6 +172,19 @@ pub struct EditorProjectResourceMediaRepairRecordInput {
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditorProjectResourceShowcaseUpdateRecordInput {
pub resource_id: String,
pub owner_user_id: String,
pub public_showcase_enabled: bool,
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditorProjectResourcePublicShowcaseListRecordInput {
pub limit: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditorAssetFolderCreateRecordInput {
pub folder_id: String,
@@ -213,6 +229,7 @@ pub struct EditorAssetCreateRecordInput {
pub task_id: Option<String>,
pub asset_kind: Option<String>,
pub generation_inputs_json: Option<String>,
pub source_resource_id: Option<String>,
pub now_micros: i64,
}
@@ -341,6 +358,27 @@ impl From<EditorProjectResourceMediaRepairRecordInput>
}
}
impl From<EditorProjectResourceShowcaseUpdateRecordInput>
for crate::module_bindings::EditorProjectResourceShowcaseUpdateInput
{
fn from(input: EditorProjectResourceShowcaseUpdateRecordInput) -> Self {
Self {
resource_id: input.resource_id,
owner_user_id: input.owner_user_id,
public_showcase_enabled: input.public_showcase_enabled,
updated_at_micros: input.updated_at_micros,
}
}
}
impl From<EditorProjectResourcePublicShowcaseListRecordInput>
for crate::module_bindings::EditorProjectResourcePublicShowcaseListInput
{
fn from(input: EditorProjectResourcePublicShowcaseListRecordInput) -> Self {
Self { limit: input.limit }
}
}
impl From<EditorAssetFolderCreateRecordInput>
for crate::module_bindings::EditorAssetFolderCreateInput
{
@@ -401,6 +439,7 @@ impl From<EditorAssetCreateRecordInput> for crate::module_bindings::EditorAssetC
task_id: input.task_id,
asset_kind: input.asset_kind,
generation_inputs_json: input.generation_inputs_json,
source_resource_id: input.source_resource_id,
now_micros: input.now_micros,
}
}
@@ -518,6 +557,20 @@ pub(crate) fn map_editor_project_resource_procedure_result(
.ok_or_else(|| SpacetimeClientError::missing_snapshot("图片画布资源快照"))
}
pub(crate) fn map_editor_project_resource_list_procedure_result(
result: EditorProjectResourceListProcedureResult,
) -> Result<Vec<EditorProjectResourceRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
result
.resources
.into_iter()
.map(map_editor_project_resource_snapshot)
.collect()
}
pub(crate) fn map_editor_asset_library_procedure_result(
result: EditorAssetLibraryProcedureResult,
) -> Result<EditorAssetLibraryRecord, SpacetimeClientError> {
@@ -638,6 +691,7 @@ fn map_editor_project_resource_snapshot(
source_resource_id: snapshot.source_resource_id,
asset_kind: snapshot.asset_kind,
generation_inputs,
public_showcase_enabled: snapshot.public_showcase_enabled,
created_at: format_timestamp_micros(snapshot.created_at_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
})
@@ -698,6 +752,8 @@ fn map_editor_asset_snapshot(
task_id: snapshot.task_id,
asset_kind: snapshot.asset_kind,
generation_inputs,
source_resource_id: snapshot.source_resource_id,
public_showcase_enabled: snapshot.public_showcase_enabled,
created_at: format_timestamp_micros(snapshot.created_at_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
})
@@ -1,7 +1,7 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
// This was generated using spacetimedb cli version 2.5.0 (commit ca16958ef0a5f8c816700d2255a0b20ecacff901).
// This was generated using spacetimedb cli version 2.5.0 (commit 19cbf2b219ac9fd7fdca6c5e2c61a3a2f0319515).
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
@@ -391,8 +391,11 @@ pub mod editor_project_list_procedure_result_type;
pub mod editor_project_procedure_result_type;
pub mod editor_project_rename_input_type;
pub mod editor_project_resource_create_input_type;
pub mod editor_project_resource_list_procedure_result_type;
pub mod editor_project_resource_media_repair_input_type;
pub mod editor_project_resource_procedure_result_type;
pub mod editor_project_resource_public_showcase_list_input_type;
pub mod editor_project_resource_showcase_update_input_type;
pub mod editor_project_resource_snapshot_type;
pub mod editor_project_resource_table;
pub mod editor_project_resource_type;
@@ -578,6 +581,7 @@ pub mod list_match_3_d_works_procedure;
pub mod list_platform_browse_history_procedure;
pub mod list_profile_save_archives_procedure;
pub mod list_profile_wallet_ledger_procedure;
pub mod list_public_editor_project_resources_and_return_procedure;
pub mod list_puzzle_clear_works_procedure;
pub mod list_puzzle_gallery_procedure;
pub mod list_puzzle_works_procedure;
@@ -1120,6 +1124,7 @@ pub mod unpublish_custom_world_profile_reducer;
pub mod update_bark_battle_draft_config_procedure;
pub mod update_editor_asset_and_return_procedure;
pub mod update_editor_asset_folder_and_return_procedure;
pub mod update_editor_project_resource_showcase_and_return_procedure;
pub mod update_jump_hop_work_procedure;
pub mod update_match_3_d_work_procedure;
pub mod update_puzzle_clear_work_procedure;
@@ -1614,8 +1619,11 @@ pub use editor_project_list_procedure_result_type::EditorProjectListProcedureRes
pub use editor_project_procedure_result_type::EditorProjectProcedureResult;
pub use editor_project_rename_input_type::EditorProjectRenameInput;
pub use editor_project_resource_create_input_type::EditorProjectResourceCreateInput;
pub use editor_project_resource_list_procedure_result_type::EditorProjectResourceListProcedureResult;
pub use editor_project_resource_media_repair_input_type::EditorProjectResourceMediaRepairInput;
pub use editor_project_resource_procedure_result_type::EditorProjectResourceProcedureResult;
pub use editor_project_resource_public_showcase_list_input_type::EditorProjectResourcePublicShowcaseListInput;
pub use editor_project_resource_showcase_update_input_type::EditorProjectResourceShowcaseUpdateInput;
pub use editor_project_resource_snapshot_type::EditorProjectResourceSnapshot;
pub use editor_project_resource_table::*;
pub use editor_project_resource_type::EditorProjectResource;
@@ -1801,6 +1809,7 @@ pub use list_match_3_d_works_procedure::list_match_3_d_works;
pub use list_platform_browse_history_procedure::list_platform_browse_history;
pub use list_profile_save_archives_procedure::list_profile_save_archives;
pub use list_profile_wallet_ledger_procedure::list_profile_wallet_ledger;
pub use list_public_editor_project_resources_and_return_procedure::list_public_editor_project_resources_and_return;
pub use list_puzzle_clear_works_procedure::list_puzzle_clear_works;
pub use list_puzzle_gallery_procedure::list_puzzle_gallery;
pub use list_puzzle_works_procedure::list_puzzle_works;
@@ -2343,6 +2352,7 @@ pub use unpublish_custom_world_profile_reducer::unpublish_custom_world_profile;
pub use update_bark_battle_draft_config_procedure::update_bark_battle_draft_config;
pub use update_editor_asset_and_return_procedure::update_editor_asset_and_return;
pub use update_editor_asset_folder_and_return_procedure::update_editor_asset_folder_and_return;
pub use update_editor_project_resource_showcase_and_return_procedure::update_editor_project_resource_showcase_and_return;
pub use update_jump_hop_work_procedure::update_jump_hop_work;
pub use update_match_3_d_work_procedure::update_match_3_d_work;
pub use update_puzzle_clear_work_procedure::update_puzzle_clear_work;
@@ -24,6 +24,7 @@ pub struct EditorAssetCreateInput {
pub task_id: Option<String>,
pub asset_kind: Option<String>,
pub generation_inputs_json: Option<String>,
pub source_resource_id: Option<String>,
pub now_micros: i64,
}
@@ -23,6 +23,8 @@ pub struct EditorAssetSnapshot {
pub task_id: Option<String>,
pub asset_kind: Option<String>,
pub generation_inputs_json: Option<String>,
pub source_resource_id: Option<String>,
pub public_showcase_enabled: Option<bool>,
pub created_at_micros: i64,
pub updated_at_micros: i64,
}
@@ -26,6 +26,7 @@ pub struct EditorAsset {
pub updated_at: __sdk::Timestamp,
pub asset_kind: Option<String>,
pub generation_inputs_json: Option<String>,
pub source_resource_id: Option<String>,
}
impl __sdk::InModule for EditorAsset {
@@ -55,6 +56,7 @@ pub struct EditorAssetCols {
pub updated_at: __sdk::__query_builder::Col<EditorAsset, __sdk::Timestamp>,
pub asset_kind: __sdk::__query_builder::Col<EditorAsset, Option<String>>,
pub generation_inputs_json: __sdk::__query_builder::Col<EditorAsset, Option<String>>,
pub source_resource_id: __sdk::__query_builder::Col<EditorAsset, Option<String>>,
}
impl __sdk::__query_builder::HasCols for EditorAsset {
@@ -83,6 +85,7 @@ impl __sdk::__query_builder::HasCols for EditorAsset {
table_name,
"generation_inputs_json",
),
source_resource_id: __sdk::__query_builder::Col::new(table_name, "source_resource_id"),
}
}
}
@@ -0,0 +1,19 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::editor_project_resource_snapshot_type::EditorProjectResourceSnapshot;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct EditorProjectResourceListProcedureResult {
pub ok: bool,
pub resources: Vec<EditorProjectResourceSnapshot>,
pub error_message: Option<String>,
}
impl __sdk::InModule for EditorProjectResourceListProcedureResult {
type Module = super::RemoteModule;
}
@@ -0,0 +1,15 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct EditorProjectResourcePublicShowcaseListInput {
pub limit: u32,
}
impl __sdk::InModule for EditorProjectResourcePublicShowcaseListInput {
type Module = super::RemoteModule;
}
@@ -0,0 +1,18 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct EditorProjectResourceShowcaseUpdateInput {
pub resource_id: String,
pub owner_user_id: String,
pub public_showcase_enabled: bool,
pub updated_at_micros: i64,
}
impl __sdk::InModule for EditorProjectResourceShowcaseUpdateInput {
type Module = super::RemoteModule;
}
@@ -23,6 +23,7 @@ pub struct EditorProjectResourceSnapshot {
pub source_resource_id: Option<String>,
pub asset_kind: Option<String>,
pub generation_inputs_json: Option<String>,
pub public_showcase_enabled: bool,
pub created_at_micros: i64,
pub updated_at_micros: i64,
}
@@ -26,6 +26,7 @@ pub struct EditorProjectResource {
pub updated_at: __sdk::Timestamp,
pub asset_kind: Option<String>,
pub generation_inputs_json: Option<String>,
pub public_showcase_enabled: bool,
}
impl __sdk::InModule for EditorProjectResource {
@@ -55,6 +56,7 @@ pub struct EditorProjectResourceCols {
pub updated_at: __sdk::__query_builder::Col<EditorProjectResource, __sdk::Timestamp>,
pub asset_kind: __sdk::__query_builder::Col<EditorProjectResource, Option<String>>,
pub generation_inputs_json: __sdk::__query_builder::Col<EditorProjectResource, Option<String>>,
pub public_showcase_enabled: __sdk::__query_builder::Col<EditorProjectResource, bool>,
}
impl __sdk::__query_builder::HasCols for EditorProjectResource {
@@ -83,6 +85,10 @@ impl __sdk::__query_builder::HasCols for EditorProjectResource {
table_name,
"generation_inputs_json",
),
public_showcase_enabled: __sdk::__query_builder::Col::new(
table_name,
"public_showcase_enabled",
),
}
}
}
@@ -0,0 +1,62 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::editor_project_resource_list_procedure_result_type::EditorProjectResourceListProcedureResult;
use super::editor_project_resource_public_showcase_list_input_type::EditorProjectResourcePublicShowcaseListInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct ListPublicEditorProjectResourcesAndReturnArgs {
pub input: EditorProjectResourcePublicShowcaseListInput,
}
impl __sdk::InModule for ListPublicEditorProjectResourcesAndReturnArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `list_public_editor_project_resources_and_return`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait list_public_editor_project_resources_and_return {
fn list_public_editor_project_resources_and_return(
&self,
input: EditorProjectResourcePublicShowcaseListInput,
) {
self.list_public_editor_project_resources_and_return_then(input, |_, _| {});
}
fn list_public_editor_project_resources_and_return_then(
&self,
input: EditorProjectResourcePublicShowcaseListInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<EditorProjectResourceListProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
impl list_public_editor_project_resources_and_return for super::RemoteProcedures {
fn list_public_editor_project_resources_and_return_then(
&self,
input: EditorProjectResourcePublicShowcaseListInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<EditorProjectResourceListProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, EditorProjectResourceListProcedureResult>(
"list_public_editor_project_resources_and_return",
ListPublicEditorProjectResourcesAndReturnArgs { input },
__callback,
);
}
}
@@ -0,0 +1,62 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::editor_project_resource_procedure_result_type::EditorProjectResourceProcedureResult;
use super::editor_project_resource_showcase_update_input_type::EditorProjectResourceShowcaseUpdateInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct UpdateEditorProjectResourceShowcaseAndReturnArgs {
pub input: EditorProjectResourceShowcaseUpdateInput,
}
impl __sdk::InModule for UpdateEditorProjectResourceShowcaseAndReturnArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `update_editor_project_resource_showcase_and_return`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait update_editor_project_resource_showcase_and_return {
fn update_editor_project_resource_showcase_and_return(
&self,
input: EditorProjectResourceShowcaseUpdateInput,
) {
self.update_editor_project_resource_showcase_and_return_then(input, |_, _| {});
}
fn update_editor_project_resource_showcase_and_return_then(
&self,
input: EditorProjectResourceShowcaseUpdateInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<EditorProjectResourceProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
impl update_editor_project_resource_showcase_and_return for super::RemoteProcedures {
fn update_editor_project_resource_showcase_and_return_then(
&self,
input: EditorProjectResourceShowcaseUpdateInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<EditorProjectResourceProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, EditorProjectResourceProcedureResult>(
"update_editor_project_resource_showcase_and_return",
UpdateEditorProjectResourceShowcaseAndReturnArgs { input },
__callback,
);
}
}
File diff suppressed because it is too large Load Diff
@@ -1394,6 +1394,22 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde
.or_insert(serde_json::Value::Null);
}
}
if table_name == "editor_project_resource" {
if let Some(object) = next_value.as_object_mut() {
// 中文注释:精选公开开关晚于画布资源表加入,旧生成资源按默认公开兼容。
object
.entry("public_showcase_enabled".to_string())
.or_insert_with(|| serde_json::Value::Bool(true));
}
}
if table_name == "editor_asset" {
if let Some(object) = next_value.as_object_mut() {
// 中文注释:账号素材与项目资源绑定晚于素材库表加入,旧素材按未绑定兼容。
object
.entry("source_resource_id".to_string())
.or_insert(serde_json::Value::Null);
}
}
next_value
}

Some files were not shown because too many files have changed in this diff Show More