docs(media): add standalone package guide

This commit is contained in:
Maofeng
2026-09-20 16:44:22 +08:00
parent c519d4e74b
commit eebf29c3d5
17 changed files with 326 additions and 78 deletions
@@ -1,34 +0,0 @@
---
title: Media library
description: Supply a storage adapter and add browsing, upload, folders, and asset selection.
order: 21
toc:
- id: implement-the-adapter
title: Implement the adapter
- id: provide-media
title: Provide media
- id: selection-flows
title: Selection flows
---
## Implement the adapter {#implement-the-adapter}
`MediaAdapter` is the media feature's complete persistence boundary. Implement its query, upload, folder, update, and delete operations with the product's storage service.
## Provide media {#provide-media}
Mount the adapter once around the media surfaces:
```tsx
import { MediaLibrary, MediaProvider } from "@workspace/blocks/media"
;<MediaProvider adapter={mediaAdapter} notify={showNotice}>
<MediaLibrary />
</MediaProvider>
```
The optional `notify` callback translates operational results into the application's toast or notification system.
## Selection flows {#selection-flows}
Use `MediaPickerDialog` for single or multiple asset selection. `readMediaDimensions`, `formatMediaFileSize`, and `defaultMediaReference` cover the common presentation work around uploads and previews.
@@ -33,7 +33,7 @@ Import the block stylesheet once from the application entry:
import "@workspace/blocks/globals.css"
```
Keep feature imports on their explicit subpaths. For example, importing `@workspace/blocks/media` does not pull chat into the same module graph.
Keep feature imports on their explicit subpaths. For example, importing `@workspace/blocks/notifications` does not pull chat into the same module graph. Media workflows are provided separately by `@workspace/media`.
## Application providers {#application-providers}
+2 -2
View File
@@ -13,7 +13,7 @@ toc:
## What you will build {#what-you-will-build}
This tutorial builds an application shell step by step. You will begin with the shared layout, connect navigation, then add notifications, chat, media, and appearance preferences.
This tutorial builds an application shell step by step. You will begin with the shared layout, connect navigation, then add notifications, chat, and appearance preferences.
By the end, the application owns its data and business rules while `@workspace/blocks` owns the reusable presentation and interaction patterns.
@@ -29,5 +29,5 @@ Blocks sit above `@workspace/ui`. They combine low-level components into feature
2. Create the application shell.
3. Define navigation and route state.
4. Connect notifications.
5. Add chat and media workflows.
5. Add chat workflows.
6. Finish with persistent appearance controls.
@@ -1,34 +0,0 @@
---
title: 媒体库
description: 提供存储 Adapter,并加入浏览、上传、文件夹和资源选择能力。
order: 21
toc:
- id: implement-the-adapter
title: 实现 Adapter
- id: provide-media
title: 提供媒体能力
- id: selection-flows
title: 选择流程
---
## 实现 Adapter {#implement-the-adapter}
`MediaAdapter` 是媒体功能完整的持久化边界。通过产品存储服务实现查询、上传、文件夹、更新和删除操作。
## 提供媒体能力 {#provide-media}
在媒体界面外挂载一次 Adapter:
```tsx
import { MediaLibrary, MediaProvider } from "@workspace/blocks/media"
;<MediaProvider adapter={mediaAdapter} notify={showNotice}>
<MediaLibrary />
</MediaProvider>
```
可选的 `notify` 回调把操作结果转换为应用的 Toast 或通知。
## 选择流程 {#selection-flows}
单选或多选资源使用 `MediaPickerDialog`。`readMediaDimensions`、`formatMediaFileSize` 和 `defaultMediaReference` 覆盖上传与预览相关的常用展示工作。
@@ -33,7 +33,7 @@ Blocks 使用 `@workspace/ui`、React Query 和工作区国际化运行时。工
import "@workspace/blocks/globals.css"
```
功能模块应始终从显式子路径导入。例如,导入 `@workspace/blocks/media` 不会把聊天功能加入同一个模块图。
功能模块应始终从显式子路径导入。例如,导入 `@workspace/blocks/notifications` 不会把聊天功能加入同一个模块图。媒体工作流由独立的 `@workspace/media` 提供。
## 应用 Providers {#application-providers}
+2 -2
View File
@@ -13,7 +13,7 @@ toc:
## 将要构建的内容 {#what-you-will-build}
本教程会逐步构建一个应用外壳。你将从共享布局开始,接入导航,再添加通知、聊天、媒体和外观偏好。
本教程会逐步构建一个应用外壳。你将从共享布局开始,接入导航,再添加通知、聊天和外观偏好。
完成后,应用负责数据和业务规则,`@workspace/blocks` 负责可复用的展示与交互模式。
@@ -29,5 +29,5 @@ Blocks 位于 `@workspace/ui` 之上。它们将底层组件组合成完整功
2. 创建应用外壳。
3. 定义导航和路由状态。
4. 接入通知。
5. 添加聊天和媒体工作流。
5. 添加聊天工作流。
6. 使用持久化外观控件完成应用。
@@ -0,0 +1,41 @@
---
title: Storage adapter
description: Connect queries, uploads, mutations, URL resolution, and pagination to product infrastructure.
order: 20
toc:
- id: implement-the-contract
title: Implement the contract
- id: separate-display-and-reference-urls
title: Separate display and reference URLs
- id: preserve-query-semantics
title: Preserve query semantics
---
## Implement the contract {#implement-the-contract}
`MediaAdapter` is the package's complete persistence boundary. Implement its asset queries, folder operations, uploads, updates, and deletes with the product's storage service.
```ts
import type { MediaAdapter } from "@workspace/media"
export const mediaAdapter: MediaAdapter = {
listAssets: (query) => api.media.list(query),
listFolders: () => api.media.listFolders(),
upload: (input) => api.media.upload(input),
createFolder: (input) => api.media.createFolder(input),
updateFolder: (input) => api.media.updateFolder(input),
deleteFolder: (id) => api.media.deleteFolder(id),
moveAsset: (id, folderId) => api.media.move(id, folderId),
renameAsset: (id, name) => api.media.rename(id, name),
updateFavorite: (id, favorite) => api.media.favorite(id, favorite),
deleteAsset: (id) => api.media.delete(id),
}
```
## Separate display and reference URLs {#separate-display-and-reference-urls}
Use `resolveUrl` for short-lived signed URLs or image transformations used while rendering. Use `resolveReference` for the value returned by the picker and stored in another document. When `resolveReference` is absent, images use `defaultMediaReference`, which preserves intrinsic dimensions in the URL.
## Preserve query semantics {#preserve-query-semantics}
Treat `MediaAssetQuery` as an immutable request. Apply `allowedKinds`, `filter`, `folder`, and `keyword` before pagination, then return `hasNextPage` based on the filtered result. This keeps desktop and mobile filters consistent.
@@ -0,0 +1,34 @@
---
title: Asset picker
description: Reuse the media library as a controlled single- or multiple-selection dialog.
order: 30
toc:
- id: open-the-picker
title: Open the picker
- id: constrain-selection
title: Constrain selection
- id: consume-results
title: Consume results
---
## Open the picker {#open-the-picker}
`MediaPickerDialog` reuses the same adapter and folder model as the full library:
```tsx
import { MediaPickerDialog } from "@workspace/media"
;<MediaPickerDialog
open={open}
onOpenChange={setOpen}
onConfirm={(assets) => editor.insertImages(assets)}
/>
```
## Constrain selection {#constrain-selection}
Pass `allowedKinds={["image"]}` for an image-only workflow. The dialog selects one asset by default; pass `multiple` when the consumer accepts several assets, and provide `initialSelection` when editing an existing value.
## Consume results {#consume-results}
The confirmation callback receives complete `MediaAsset` records. Store the adapter-provided reference rather than a temporary preview URL when the result must survive signed-URL expiration.
+48
View File
@@ -0,0 +1,48 @@
---
title: Installation and setup
description: Add Media, register its catalog, and connect the storage adapter.
order: 10
toc:
- id: add-the-package
title: Add the package
- id: register-styles-and-catalogs
title: Register styles and catalogs
- id: provide-media
title: Provide media
---
## Add the package {#add-the-package}
Add Media to the consuming application:
```json
{
"dependencies": {
"@workspace/media": "workspace:*"
}
}
```
## Register styles and catalogs {#register-styles-and-catalogs}
Import the stylesheet once so Tailwind includes utilities used by the package:
```css
@import "@workspace/media/globals.css";
```
Add `@workspace/media/locales/{locale}` to the application's internationalization catalog sources.
## Provide media {#provide-media}
Mount the adapter once around every media surface that should share storage behavior and notifications:
```tsx
import { MediaLibrary, MediaProvider } from "@workspace/media"
;<MediaProvider adapter={mediaAdapter} notify={showNotice}>
<MediaLibrary />
</MediaProvider>
```
The optional `notify` callback turns operation results into the application's toast or notification system.
+31
View File
@@ -0,0 +1,31 @@
---
title: Overview
description: Understand the Media package, its storage boundary, and the workflows it owns.
order: 1
toc:
- id: package-role
title: Package role
- id: capabilities
title: Capabilities
- id: design-boundary
title: Design boundary
---
## Package role {#package-role}
`@workspace/media` provides reusable media-library and asset-picker surfaces. The host application supplies persistence through `MediaAdapter`; Media owns browsing, filtering, pagination, uploads, folders, selection, and asset actions.
## Capabilities {#capabilities}
- Browse images and videos with search, filters, folders, favorites, and pagination.
- Upload to local or cloud storage targets exposed by the adapter.
- Create, rename, nest, and delete organizational folders.
- Select one or multiple assets through `MediaPickerDialog`.
- Resolve display URLs and stable references independently.
- Ship `en-US` and `zh-Hans` message catalogs.
## Design boundary {#design-boundary}
Media does not choose an API, database, object store, CDN, or signing strategy. The application implements those decisions behind `MediaAdapter` and can replace them without changing the interface components.
> Folders classify assets inside the library. They do not need to mirror object-storage paths or change public URLs.
@@ -0,0 +1,41 @@
---
title: 存储 Adapter
description: 将查询、上传、变更、URL 解析和分页连接到产品基础设施。
order: 20
toc:
- id: implement-the-contract
title: 实现完整契约
- id: separate-display-and-reference-urls
title: 区分展示与引用 URL
- id: preserve-query-semantics
title: 保持查询语义
---
## 实现完整契约 {#implement-the-contract}
`MediaAdapter` 是这个包完整的持久化边界。通过产品的存储服务实现资源查询、目录操作、上传、更新和删除。
```ts
import type { MediaAdapter } from "@workspace/media"
export const mediaAdapter: MediaAdapter = {
listAssets: (query) => api.media.list(query),
listFolders: () => api.media.listFolders(),
upload: (input) => api.media.upload(input),
createFolder: (input) => api.media.createFolder(input),
updateFolder: (input) => api.media.updateFolder(input),
deleteFolder: (id) => api.media.deleteFolder(id),
moveAsset: (id, folderId) => api.media.move(id, folderId),
renameAsset: (id, name) => api.media.rename(id, name),
updateFavorite: (id, favorite) => api.media.favorite(id, favorite),
deleteAsset: (id) => api.media.delete(id),
}
```
## 区分展示与引用 URL {#separate-display-and-reference-urls}
使用 `resolveUrl` 返回渲染时需要的短期签名 URL 或图片转换地址。使用 `resolveReference` 返回选择器写入其他文档的值。未提供 `resolveReference` 时,图片会使用 `defaultMediaReference`,在 URL 中保留固有尺寸。
## 保持查询语义 {#preserve-query-semantics}
将 `MediaAssetQuery` 当作不可变请求。在分页前依次应用 `allowedKinds`、`filter`、`folder` 和 `keyword`,然后根据筛选后的结果返回 `hasNextPage`。这样可以保持桌面端与移动端筛选行为一致。
@@ -0,0 +1,34 @@
---
title: 资源选择器
description: 将媒体库复用为受控的单选或多选对话框。
order: 30
toc:
- id: open-the-picker
title: 打开选择器
- id: constrain-selection
title: 限制选择范围
- id: consume-results
title: 使用选择结果
---
## 打开选择器 {#open-the-picker}
`MediaPickerDialog` 会复用完整媒体库相同的 Adapter 和目录模型:
```tsx
import { MediaPickerDialog } from "@workspace/media"
;<MediaPickerDialog
open={open}
onOpenChange={setOpen}
onConfirm={(assets) => editor.insertImages(assets)}
/>
```
## 限制选择范围 {#constrain-selection}
图片专用流程传入 `allowedKinds={["image"]}`。对话框默认单选;消费方可以接受多个资源时传入 `multiple`,编辑已有值时通过 `initialSelection` 恢复选择。
## 使用选择结果 {#consume-results}
确认回调会收到完整的 `MediaAsset` 记录。如果结果需要在签名 URL 失效后继续使用,应保存 Adapter 提供的稳定引用,而不是临时预览地址。
@@ -0,0 +1,48 @@
---
title: 安装与设置
description: 添加 Media、注册词典并连接存储 Adapter。
order: 10
toc:
- id: add-the-package
title: 添加包
- id: register-styles-and-catalogs
title: 注册样式与词典
- id: provide-media
title: 提供媒体能力
---
## 添加包 {#add-the-package}
在使用方应用中添加 Media
```json
{
"dependencies": {
"@workspace/media": "workspace:*"
}
}
```
## 注册样式与词典 {#register-styles-and-catalogs}
导入一次样式表,让 Tailwind 包含 Media 使用的工具类:
```css
@import "@workspace/media/globals.css";
```
将 `@workspace/media/locales/{locale}` 加入应用的国际化 catalog sources。
## 提供媒体能力 {#provide-media}
在需要共享存储行为和通知方式的媒体界面外统一挂载 Adapter:
```tsx
import { MediaLibrary, MediaProvider } from "@workspace/media"
;<MediaProvider adapter={mediaAdapter} notify={showNotice}>
<MediaLibrary />
</MediaProvider>
```
可选的 `notify` 回调会把操作结果接入应用自己的 Toast 或通知系统。
+31
View File
@@ -0,0 +1,31 @@
---
title: 概览
description: 了解 Media 包、存储边界以及它负责的媒体工作流。
order: 1
toc:
- id: package-role
title: 包的职责
- id: capabilities
title: 功能范围
- id: design-boundary
title: 设计边界
---
## 包的职责 {#package-role}
`@workspace/media` 提供可复用的媒体库和资源选择界面。宿主应用通过 `MediaAdapter` 提供持久化能力;Media 负责浏览、筛选、分页、上传、目录、选择和资源操作。
## 功能范围 {#capabilities}
- 通过搜索、筛选、目录、收藏和分页浏览图片与视频。
- 上传到 Adapter 暴露的本地或云端存储目标。
- 创建、重命名、嵌套和删除分类目录。
- 通过 `MediaPickerDialog` 单选或多选资源。
- 分别解析展示 URL 和稳定引用。
- 提供 `en-US` 与 `zh-Hans` 消息词典。
## 设计边界 {#design-boundary}
Media 不会替应用选择 API、数据库、对象存储、CDN 或签名策略。应用在 `MediaAdapter` 后实现这些决策,替换实现时无需修改界面组件。
> 目录只用于媒体库内部分类,不必对应对象存储路径,也不需要改变公开 URL。
+1 -1
View File
@@ -21,4 +21,4 @@ This tutorial installs the styles, introduces component subpaths, builds a small
## UI or Blocks {#ui-or-blocks}
Use UI components when the application owns the workflow. Use `@workspace/blocks` when the interface also needs a complete feature contract such as navigation, media storage, search adapters, or notification queries.
Use UI components when the application owns the workflow. Use `@workspace/blocks` for complete application-shell features such as navigation and notifications, and focused packages such as `@workspace/media` or `@workspace/search` for domain workflows.
+1 -1
View File
@@ -21,4 +21,4 @@ toc:
## UI 还是 Blocks {#ui-or-blocks}
当应用自己拥有业务流程时使用 UI 组件。当界面还需要导航、媒体存储、搜索适配器或通知查询等完整功能契约时,使用 `@workspace/blocks`
当应用自己拥有业务流程时使用 UI 组件。导航和通知等完整应用外壳功能使用 `@workspace/blocks`,媒体与搜索等领域工作流使用 `@workspace/media` 或 `@workspace/search` 等专用包