@cut-workbench/workbench
v0.5.0
Published
Cut Workbench 的 UI 组件库:素材库、预览、时间轴、属性面板与导出对话框。
Maintainers
Readme
@cut-workbench/workbench
可嵌入的浏览器端剪辑台 UI:素材库、预览、时间轴、属性面板与导出对话框。 基于 React 19 + Tailwind CSS v4 + Radix,样式已作用域化,嵌进任何宿主都不污染。
安装
只需要装这一个包:
npm i @cut-workbench/workbench@cut-workbench/core、@cut-workbench/react 以及引擎依赖
@webav/av-cliper、@webav/av-canvas、@webav/av-recorder 都是它的
dependencies,会一起装上 —— 不需要你再显式安装,也不需要知道有这些东西。
选装(按需):
| 想要什么 | 装什么 |
|---|---|
| 只取纯逻辑,不渲染 UI | npm i @cut-workbench/core |
| 只要 React hooks,自己写 UI | npm i @cut-workbench/core @cut-workbench/react |
| Electron 宿主桥(「下载」→「保存到指定路径」) | npm i @cut-workbench/host-electron |
| 需要控制 React 实例 | 宿主自己提供 react / react-dom / zustand(见下) |
关于 react / react-dom / zustand
这三个是 peer 依赖,由宿主提供,库不会替你装 —— 一旦出现两份 React,
宿主会碰到 Cannot read properties of null (reading 'useContext')。项目已有
React 时无需任何额外操作。
zustand 同理:@cut-workbench/react 直接 import 它,两份实例会让订阅错乱,
所以必须由宿主提供(React 项目通常已有)。
为什么 WebAV 是 dependencies 而不是 peer
@webav/* 是运行时必需的:没有 av-cliper / av-canvas 就没有预览和
导出,功能直接不可用。所以这个包选择把它们带上,让「装一个包就能用」成立。
@cut-workbench/core 则相反 —— 它的定位是「只取纯逻辑」,有人只要 timeline
模型和工程序列化,不该被迫装 WebAV,所以 core 那边是 optional peer。
⚠️ 0.1.0 的 WebAV 是 peer,只靠 npm 7+ 自动安装 peer 的隐式行为碰巧能跑; pnpm / yarn 不会自动装 peer,在它们下面构建会报
[MISSING_EXPORT] "Combinator" is not exported by "__vite-optional-peer-dep:…"。 0.2.0 起改为 dependencies,与包管理器无关。
React 项目
推荐:用 /auto 子入口,一行自带样式:
import { createEditor } from '@cut-workbench/core/engine'
import { CutEditorProvider } from '@cut-workbench/react'
import { CutWorkbench, CutWorkbenchProvider } from '@cut-workbench/workbench/auto'
const editor = createEditor()
createRoot(el).render(
<CutEditorProvider editor={editor}>
<CutWorkbenchProvider theme="dark">
<CutWorkbench className="h-screen" onSaveProject={save} onOpenProject={open} />
</CutWorkbenchProvider>
</CutEditorProvider>,
)根入口不自带样式 —— 给已经有自己的构建流程、需要控制 CSS 顺序 (例如要压过某条全局重置)的场景:
import { CutWorkbench, CutWorkbenchProvider } from '@cut-workbench/workbench'
import '@cut-workbench/workbench/style.css' // ← 必须自己引,忘了就是无样式两者的 API 完全一致,/auto 只是多了一行 import './cut-workbench.css'。
不要同时引两个入口 —— 样式会被引入两次(作用域化之后是幂等的,但没必要)。
布局
┌───────────────────────────────────────────┐
│ 顶栏 │
├──────────┬──────────────┬─────────────────┤
│ 素材库 │ 预览 │ 属性 │ ← 上半区,可纵向滚动
├──────────┴──────────────┴─────────────────┤
│ 时间轴(横跨整行) │ ← 下栏
└───────────────────────────────────────────┘时间轴通栏占满底部一行,不随左右面板宽度变化。左上「素材」、中「预览」、 右「属性」三栏可拖拽调整,面板内的内容各自滚动。
0.2.0 之前时间轴被塞在中间列里,左右面板一拉宽就把它挤窄,工具条按钮会 溢出到属性面板上;窄列下播放控制条的时间码也会被裁掉。现在布局与控件都有 容器级自适应(按容器宽度分档收起次要控件,而不是按视口宽度),
scripts/layout-test.mjs守着这两条。
顶栏定制
内置顶栏分三层可定制,由浅到深:
1. 改文案 —— labels:
<CutWorkbench toolbar={{ labels: { export: '导出成片', untitledProject: '新建项目' } }} />2. 改局部 —— brand / title / status / actions:
<CutWorkbench
toolbar={{
brand: <MyLogo />, // 默认**没有**品牌区,避免露出本库名字
title: null, // 不要工程名区
status: <AutoSaveBadge />, // 默认「空工程 · 16:9」
actions: <MyShareButton />, // 追加在右侧按钮组末尾
editableTitle: true, // 双击工程名重命名(走撤销栈)
showCapabilityBadge: false, // 隐藏引擎能力徽标
}}
/>3. 整条替换 —— slots.topBar(优先级最高):
<CutWorkbench slots={{ topBar: <MyHeader /> }} /> // 传 null 表示完全不要顶栏
slots.topBar会替换整条顶栏,撤销/导出等内置动作一并由宿主负责;toolbar只换其中一块,其余保留。可运行示例见 playground 的/toolbar-demo.html。
非 React 项目
import { initCutWorkbench } from '@cut-workbench/workbench/auto'
const wb = initCutWorkbench('#app', { theme: 'dark', namespace: 'my-project' })
await wb.whenReady()
wb.on('export:done', ({ blob, filename }) => upload(blob, filename))script 标签 / 微前端
UMD 单文件已把 React、WebAV、Worker 与样式全部内联,暴露 window.CutWorkbench:
<script src="https://unpkg.com/@cut-workbench/workbench/dist/umd/cut-workbench.umd.js"></script>
<script>
const wb = CutWorkbench.initCutWorkbench('#app', { theme: 'dark' })
</script>⚠️ React 19 不再发布 UMD 产物,因此 UMD 版本内置了一份 React。有任何构建工具时 都优先选 ESM。
类型与依赖缺失
如果引擎依赖没解析到(例如被 npm 的 hoisting 拔掉、或在 pnpm 下版本冲突), 工作台不会白屏:能力探测会把「缺依赖」和「浏览器不支持」分开报告, 阻断页直接给出可执行的安装命令:
缺少 @webav/av-cliper,请执行 npm i @webav/av-cliper @webav/av-canvas @webav/av-recorder注意缺失依赖只在构建期解析 —— 装完必须重新跑一次构建,刷新浏览器没用。
关掉能力实验室(可选)
config.features.lab 是运行时开关,默认 false。lab 本体是独立子入口
@cut-workbench/workbench/lab,主包对它做动态 import —— 所以默认配置下
lab 的代码(含 @webav/av-recorder)不会加载,但打包器仍会产出一个约 33 KB 的
lab chunk。
要在产物里彻底删掉它,在宿主的构建配置里把子入口指到空实现:
// vite.config.ts
export default {
resolve: {
alias: [
{
find: /^@cut-workbench\/workbench\/lab$/,
replacement: '@cut-workbench/workbench/lab-disabled',
},
],
},
}验证方式:
grep -c "FrameCounterClip" dist/assets/*.js # 应为 0为什么不是
define: { __CUT_WORKBENCH_LAB__: 'false' }:实测(Vite 8 / Rolldown)动态 import 只要出现在模块图里,chunk 就会被写进 dist,即便所在 分支已被折成死代码。只有把模块解析本身换掉才真正生效。
宿主桥(Electron / 自定义文件来源)
宿主想接管「导出保存到哪」和「素材从哪来」时,用 @cut-workbench/host-electron:
const editor = createEditor({
bridge: createElectronBridge(window.cutHost), // ← 传给 createEditor 即可
})
<CutEditorProvider editor={editor}>
<CutWorkbenchProvider theme="dark">
<CutWorkbench className="h-full w-full" />
</CutWorkbenchProvider>
</CutEditorProvider>CutWorkbenchProvider 不传 bridge 时会回落到编辑器自己的 bridge,
所以不用传两遍。若想给 UI 层单独换一套桥,再显式传 bridge 覆盖。
⚠️ 只把桥传给
createEditor却期待 UI 也生效,在 0.2.0 之前是不成立的: Provider 会另建一个浏览器内置桥,于是「导出走宿主、素材导入走内置<input>」的半接管状态静默存在 —— 界面完全正常,宿主自己的素材库永远 弹不出来。0.2.0 起已改为回落。scripts/bridge-host-test.mjs守着这条, 可运行验收页见 playground 的/bridge-host.html。
主题定制
<CutWorkbenchProvider theme="inherit" /> // 跟随宿主 <html class="dark">
<CutWorkbenchProvider tokens={{ primary: '#4d6bfe', radius: '8px' }} />token 通过根节点 inline style 应用,优先级高于样式表,不依赖引入顺序;变量都带
--cw- 前缀且挂在 .cw-root 上,不污染宿主 :root。
样式隔离
- 不引 preflight,宿主
h1/button默认样式不变; - 每条 utility 前置
.cw-root,宿主的.flex与工作台的互不干扰; - 不使用
@layer,避免被宿主未分层样式压过。
导出与 Worker
导出默认在 Web Worker 中执行,产物里的 assets/export.worker-*.js 由
@cut-workbench/workbench 自身携带。若宿主有严格 CSP,可用
HostBridge.createExportWorker 完全接管 Worker 的创建。
含文字片段的工程会自动回退主线程渲染(文字依赖 DOM)。
浏览器要求
仅支持 Chromium 内核(Chrome / Edge):WebAV 依赖 WebCodecs 与 OPFS。
License
MIT
