@cut-workbench/workbench 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_format = require("./format--DhqHMgX.cjs");
2
+ const require_notify = require("./notify-CMNwDFGw.cjs");
3
3
  let react_dom_client = require("react-dom/client");
4
4
  let react = require("react");
5
- react = require_format.__toESM(react, 1);
5
+ react = require_notify.__toESM(react, 1);
6
6
  let _cut_workbench_core_engine = require("@cut-workbench/core/engine");
7
7
  let _cut_workbench_core = require("@cut-workbench/core");
8
8
  let _cut_workbench_react = require("@cut-workbench/react");
@@ -36,7 +36,7 @@ let zustand_vanilla = require("zustand/vanilla");
36
36
  */
37
37
  function AppShell({ topBar, left, center, right, timeline, leftWidth, rightWidth, timelineHeight, leftResizer, rightResizer, timelineResizer, className }) {
38
38
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
39
- className: require_format.cn("flex h-full min-h-0 flex-col bg-background", className),
39
+ className: require_notify.cn("flex h-full min-h-0 flex-col bg-background", className),
40
40
  children: [
41
41
  topBar,
42
42
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -106,7 +106,7 @@ function Button({ className, variant = "default", size = "default", asChild = fa
106
106
  "data-slot": "button",
107
107
  "data-variant": variant,
108
108
  "data-size": size,
109
- className: require_format.cn(buttonVariants({
109
+ className: require_notify.cn(buttonVariants({
110
110
  variant,
111
111
  size,
112
112
  className
@@ -120,7 +120,7 @@ function Separator({ className, orientation = "horizontal", decorative = true, .
120
120
  "data-slot": "separator",
121
121
  decorative,
122
122
  orientation,
123
- className: require_format.cn("shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", className),
123
+ className: require_notify.cn("shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", className),
124
124
  ...props
125
125
  });
126
126
  }
@@ -199,7 +199,7 @@ function TooltipContent({ className, sideOffset = 0, children, ...props }) {
199
199
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(radix_ui.Tooltip.Content, {
200
200
  "data-slot": "tooltip-content",
201
201
  sideOffset,
202
- className: require_format.cn("z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", className),
202
+ className: require_notify.cn("z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", className),
203
203
  ...props,
204
204
  children: [children, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Tooltip.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" })]
205
205
  })
@@ -226,7 +226,7 @@ function PopoverContent({ className, align = "center", sideOffset = 4, ...props
226
226
  "data-slot": "popover-content",
227
227
  align,
228
228
  sideOffset,
229
- className: require_format.cn("z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", className),
229
+ className: require_notify.cn("z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", className),
230
230
  ...props
231
231
  })
232
232
  });
@@ -237,7 +237,7 @@ function CapabilityBadge({ info }) {
237
237
  const { status, report, summary } = info;
238
238
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Popover, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PopoverTrigger, {
239
239
  asChild: true,
240
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_format.Badge, {
240
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_notify.Badge, {
241
241
  variant: "outline",
242
242
  className: "cursor-pointer gap-1 text-[11px] font-normal select-none",
243
243
  children: [status === "checking" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "size-3 animate-spin" }) : status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CheckCircle2, { className: "size-3 text-success" }) : status === "degraded" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlertTriangle, { className: "size-3 text-warning" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.XCircle, { className: "size-3 text-destructive" }), summary]
@@ -306,44 +306,6 @@ function Row$1({ label, ok, optional }) {
306
306
  })]
307
307
  });
308
308
  }
309
- // src/context.tsx
310
- /**
311
- * 工作台自身的 Context:UI 状态(面板尺寸 / 缩放 / 对话框)+ 宿主桥。
312
- *
313
- * 与 `@cut-workbench/react` 的编辑器 Context 分开:编辑器是「数据与引擎」,
314
- * 这里是「某一次挂载的布局与交互」。两者生命周期可以不同(例如宿主切换项目时
315
- * 复用同一个编辑器、只重建 UI)。
316
- */
317
- var WorkbenchContext = (0, react.createContext)(null);
318
- function useWorkbench() {
319
- const value = (0, react.useContext)(WorkbenchContext);
320
- if (value == null) throw new Error("[cut-workbench] 缺少 <WorkbenchProvider>:组件必须挂在工作台内部");
321
- return value;
322
- }
323
- /** 订阅 UI 状态(带 selector) */
324
- function useUi(selector) {
325
- const { ui } = useWorkbench();
326
- return (0, react.useSyncExternalStore)(ui.subscribe, () => selector(ui.getState()), () => selector(ui.getState()));
327
- }
328
- /** 取 UI store 本体(事件回调里读写用) */
329
- function useUiStoreApi() {
330
- return useWorkbench().ui;
331
- }
332
- /** 默认功能开关 */
333
- var DEFAULT_FEATURES = {
334
- mediaLibrary: true,
335
- preview: true,
336
- timeline: true,
337
- inspector: true,
338
- export: true,
339
- canvasSettings: true,
340
- lab: false
341
- };
342
- var DEFAULT_CONFIG = {
343
- features: DEFAULT_FEATURES,
344
- keyboard: "global",
345
- resizable: true
346
- };
347
309
  // src/components/layout/WorkbenchToolbar.tsx
348
310
  /**
349
311
  * 内置顶栏(WorkbenchToolbar)。
@@ -368,9 +330,9 @@ function WorkbenchToolbar({ capability, onExport, onSaveProject, onOpenProject,
368
330
  const redo = (0, _cut_workbench_react.useEditor)((s) => s.redo);
369
331
  const canUndo = (0, _cut_workbench_react.useHistory)((s) => s.past.length > 0);
370
332
  const canRedo = (0, _cut_workbench_react.useHistory)((s) => s.future.length > 0);
371
- const route = useUi((s) => s.route);
372
- const setRoute = useUi((s) => s.setRoute);
373
- const openDialog = useUi((s) => s.openDialog);
333
+ const route = require_notify.useUi((s) => s.route);
334
+ const setRoute = require_notify.useUi((s) => s.setRoute);
335
+ const openDialog = require_notify.useUi((s) => s.openDialog);
374
336
  const clipCount = (0, _cut_workbench_react.useEditor)((s) => s.project.clips.length);
375
337
  const projectWidth = (0, _cut_workbench_react.useEditor)((s) => s.project.width);
376
338
  const projectHeight = (0, _cut_workbench_react.useEditor)((s) => s.project.height);
@@ -530,7 +492,7 @@ function ProjectTitle({ name, dirty, placeholder, editable, onRename }) {
530
492
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
531
493
  className: "flex min-w-0 items-center gap-1.5",
532
494
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
533
- className: require_format.cn("truncate text-sm font-medium", editable && "cursor-text"),
495
+ className: require_notify.cn("truncate text-sm font-medium", editable && "cursor-text"),
534
496
  title: editable ? "双击重命名" : name.length > 0 ? name : placeholder,
535
497
  onDoubleClick: editable ? () => {
536
498
  setDraft(name.length > 0 ? name : "");
@@ -621,8 +583,8 @@ function PanelResizer({ orientation, value, min, max, invert, onChange, classNam
621
583
  "aria-orientation": isVertical ? "vertical" : "horizontal",
622
584
  "aria-label": label,
623
585
  onPointerDown,
624
- className: require_format.cn("group relative z-20 shrink-0 touch-none transition-colors", isVertical ? "w-1 cursor-col-resize" : "h-1 cursor-row-resize", dragging ? "bg-primary/60" : "bg-border hover:bg-primary/40", className),
625
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: require_format.cn("absolute", isVertical ? "-left-1 -right-1 inset-y-0" : "-top-1 -bottom-1 inset-x-0") })
586
+ className: require_notify.cn("group relative z-20 shrink-0 touch-none transition-colors", isVertical ? "w-1 cursor-col-resize" : "h-1 cursor-row-resize", dragging ? "bg-primary/60" : "bg-border hover:bg-primary/40", className),
587
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: require_notify.cn("absolute", isVertical ? "-left-1 -right-1 inset-y-0" : "-top-1 -bottom-1 inset-x-0") })
626
588
  });
627
589
  }
628
590
  // src/components/common/LoadingOverlay.tsx
@@ -630,7 +592,7 @@ function PanelResizer({ orientation, value, min, max, invert, onChange, classNam
630
592
  function LoadingOverlay({ visible, title = "处理中…", description, progress, className, fullscreen, children }) {
631
593
  if (!visible) return null;
632
594
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
633
- className: require_format.cn("z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm", fullscreen ? "fixed inset-0" : "absolute inset-0", className),
595
+ className: require_notify.cn("z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm", fullscreen ? "fixed inset-0" : "absolute inset-0", className),
634
596
  role: "status",
635
597
  "aria-live": "polite",
636
598
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -711,18 +673,18 @@ function UnsupportedBrowser({ report, loading, error }) {
711
673
  const dependenciesMissing = missing.length > 0;
712
674
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
713
675
  className: "flex min-h-screen items-center justify-center bg-background p-6",
714
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_format.Card, {
676
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_notify.Card, {
715
677
  className: "w-full max-w-2xl",
716
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_format.CardHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
678
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_notify.CardHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
717
679
  className: "flex items-center gap-2 text-destructive",
718
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlertOctagon, { className: "size-5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_format.CardTitle, {
680
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlertOctagon, { className: "size-5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_notify.CardTitle, {
719
681
  className: "text-base",
720
682
  children: dependenciesMissing ? "缺少引擎依赖" : "当前浏览器无法运行本应用"
721
683
  })]
722
684
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
723
685
  className: "text-sm text-muted-foreground",
724
686
  children: dependenciesMissing ? "工作台的预览与导出依赖 WebAV(@webav/av-cliper、@webav/av-canvas)。当前项目里没有解析到它们。" : "WebAV 依赖 WebCodecs 与 OPFS,仅支持 Chromium 内核浏览器。请使用最新版 Chrome 或 Edge(非无痕模式)重新打开。"
725
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_format.CardContent, {
687
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_notify.CardContent, {
726
688
  className: "space-y-4",
727
689
  children: [
728
690
  dependenciesMissing && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -816,7 +778,7 @@ var Toaster = (props) => {
816
778
  function Textarea({ className, ...props }) {
817
779
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
818
780
  "data-slot": "textarea",
819
- className: require_format.cn("flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40", className),
781
+ className: require_notify.cn("flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40", className),
820
782
  ...props
821
783
  });
822
784
  }
@@ -824,7 +786,7 @@ function Textarea({ className, ...props }) {
824
786
  function Label({ className, ...props }) {
825
787
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Label.Root, {
826
788
  "data-slot": "label",
827
- className: require_format.cn("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", className),
789
+ className: require_notify.cn("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", className),
828
790
  ...props
829
791
  });
830
792
  }
@@ -832,7 +794,7 @@ function Label({ className, ...props }) {
832
794
  function ScrollArea({ className, children, ...props }) {
833
795
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(radix_ui.ScrollArea.Root, {
834
796
  "data-slot": "scroll-area",
835
- className: require_format.cn("relative", className),
797
+ className: require_notify.cn("relative", className),
836
798
  ...props,
837
799
  children: [
838
800
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.ScrollArea.Viewport, {
@@ -849,7 +811,7 @@ function ScrollBar({ className, orientation = "vertical", ...props }) {
849
811
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.ScrollArea.ScrollAreaScrollbar, {
850
812
  "data-slot": "scroll-area-scrollbar",
851
813
  orientation,
852
- className: require_format.cn("flex touch-none p-px transition-colors select-none", orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent", orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent", className),
814
+ className: require_notify.cn("flex touch-none p-px transition-colors select-none", orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent", orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent", className),
853
815
  ...props,
854
816
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.ScrollArea.ScrollAreaThumb, {
855
817
  "data-slot": "scroll-area-thumb",
@@ -888,7 +850,7 @@ function DialogPortal({ ...props }) {
888
850
  function DialogOverlay({ className, ...props }) {
889
851
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Dialog.Overlay, {
890
852
  "data-slot": "dialog-overlay",
891
- className: require_format.cn("fixed inset-0 z-50 bg-overlay data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0", className),
853
+ className: require_notify.cn("fixed inset-0 z-50 bg-overlay data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0", className),
892
854
  ...props
893
855
  });
894
856
  }
@@ -897,7 +859,7 @@ function DialogContent({ className, children, showCloseButton = true, ...props }
897
859
  "data-slot": "dialog-portal",
898
860
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogOverlay, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(radix_ui.Dialog.Content, {
899
861
  "data-slot": "dialog-content",
900
- className: require_format.cn("fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg", className),
862
+ className: require_notify.cn("fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg", className),
901
863
  ...props,
902
864
  children: [children, showCloseButton && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(radix_ui.Dialog.Close, {
903
865
  "data-slot": "dialog-close",
@@ -913,14 +875,14 @@ function DialogContent({ className, children, showCloseButton = true, ...props }
913
875
  function DialogHeader({ className, ...props }) {
914
876
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
915
877
  "data-slot": "dialog-header",
916
- className: require_format.cn("flex flex-col gap-2 text-center sm:text-left", className),
878
+ className: require_notify.cn("flex flex-col gap-2 text-center sm:text-left", className),
917
879
  ...props
918
880
  });
919
881
  }
920
882
  function DialogFooter({ className, showCloseButton = false, children, ...props }) {
921
883
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
922
884
  "data-slot": "dialog-footer",
923
- className: require_format.cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
885
+ className: require_notify.cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
924
886
  ...props,
925
887
  children: [children, showCloseButton && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Dialog.Close, {
926
888
  asChild: true,
@@ -934,14 +896,14 @@ function DialogFooter({ className, showCloseButton = false, children, ...props }
934
896
  function DialogTitle({ className, ...props }) {
935
897
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Dialog.Title, {
936
898
  "data-slot": "dialog-title",
937
- className: require_format.cn("text-lg leading-none font-semibold", className),
899
+ className: require_notify.cn("text-lg leading-none font-semibold", className),
938
900
  ...props
939
901
  });
940
902
  }
941
903
  function DialogDescription({ className, ...props }) {
942
904
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Dialog.Description, {
943
905
  "data-slot": "dialog-description",
944
- className: require_format.cn("text-sm text-muted-foreground", className),
906
+ className: require_notify.cn("text-sm text-muted-foreground", className),
945
907
  ...props
946
908
  });
947
909
  }
@@ -970,7 +932,7 @@ function DropdownMenuContent({ className, sideOffset = 4, ...props }) {
970
932
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownMenuPortal, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.DropdownMenu.Content, {
971
933
  "data-slot": "dropdown-menu-content",
972
934
  sideOffset,
973
- className: require_format.cn("z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", className),
935
+ className: require_notify.cn("z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", className),
974
936
  ...props
975
937
  }) });
976
938
  }
@@ -979,7 +941,7 @@ function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
979
941
  "data-slot": "dropdown-menu-item",
980
942
  "data-inset": inset,
981
943
  "data-variant": variant,
982
- className: require_format.cn("relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!", className),
944
+ className: require_notify.cn("relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!", className),
983
945
  ...props
984
946
  });
985
947
  }
@@ -987,14 +949,14 @@ function DropdownMenuLabel({ className, inset, ...props }) {
987
949
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.DropdownMenu.Label, {
988
950
  "data-slot": "dropdown-menu-label",
989
951
  "data-inset": inset,
990
- className: require_format.cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className),
952
+ className: require_notify.cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className),
991
953
  ...props
992
954
  });
993
955
  }
994
956
  function DropdownMenuSeparator({ className, ...props }) {
995
957
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.DropdownMenu.Separator, {
996
958
  "data-slot": "dropdown-menu-separator",
997
- className: require_format.cn("-mx-1 my-1 h-px bg-border", className),
959
+ className: require_notify.cn("-mx-1 my-1 h-px bg-border", className),
998
960
  ...props
999
961
  });
1000
962
  }
@@ -1002,7 +964,7 @@ function DropdownMenuSeparator({ className, ...props }) {
1002
964
  /** 统一空态:图标 + 标题 + 说明 + 可选操作 */
1003
965
  function EmptyState({ icon: Icon, title, description, action, className, compact }) {
1004
966
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1005
- className: require_format.cn("flex flex-col items-center justify-center gap-2 text-center", compact ? "px-4 py-6" : "px-6 py-12", className),
967
+ className: require_notify.cn("flex flex-col items-center justify-center gap-2 text-center", compact ? "px-4 py-6" : "px-6 py-12", className),
1006
968
  children: [
1007
969
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1008
970
  className: "flex size-10 items-center justify-center rounded-full bg-muted text-muted-foreground",
@@ -1020,8 +982,164 @@ function EmptyState({ icon: Icon, title, description, action, className, compact
1020
982
  ]
1021
983
  });
1022
984
  }
985
+ // src/features/media-library/lib/pick-files.ts
986
+ var INPUT_ID = "cut-workbench-file-input";
987
+ /** 内置回退:隐藏 input,选完即弃 */
988
+ function pickWithInput(accept, multiple) {
989
+ return new Promise((resolve) => {
990
+ const input = document.createElement("input");
991
+ input.type = "file";
992
+ input.id = INPUT_ID;
993
+ input.multiple = multiple;
994
+ if (accept.length > 0) input.accept = accept.join(",");
995
+ input.style.display = "none";
996
+ document.body.appendChild(input);
997
+ const finish = (files) => {
998
+ input.remove();
999
+ resolve(files);
1000
+ };
1001
+ input.addEventListener("change", () => finish(Array.from(input.files ?? [])), { once: true });
1002
+ window.addEventListener("focus", () => {
1003
+ window.setTimeout(() => {
1004
+ if (document.body.contains(input)) finish([]);
1005
+ }, 300);
1006
+ }, { once: true });
1007
+ input.click();
1008
+ });
1009
+ }
1010
+ /**
1011
+ * @returns 选中的文件;用户取消时为空数组
1012
+ */
1013
+ async function pickMediaFiles(bridge, options) {
1014
+ const multiple = options.multiple ?? true;
1015
+ if (bridge?.files?.pickFiles != null) {
1016
+ const result = await bridge.files.pickFiles({
1017
+ accept: options.accept,
1018
+ multiple,
1019
+ description: options.description
1020
+ });
1021
+ if (result != null) return result;
1022
+ }
1023
+ return pickWithInput(options.accept, multiple);
1024
+ }
1025
+ // src/features/media-library/hooks/useMediaImport.ts
1026
+ var ACCEPT = [
1027
+ "video/mp4",
1028
+ "audio/*",
1029
+ "image/*",
1030
+ ".srt",
1031
+ "text/plain"
1032
+ ];
1033
+ /**
1034
+ * 找出可以「重连」的素材。
1035
+ *
1036
+ * 场景:刷新后工程从 IndexedDB 恢复,但素材二进制消失(File 无法持久化)。
1037
+ * 用户重新导入同一个文件时,复用原 assetId,时间轴上已有片段立即恢复。
1038
+ * 匹配条件:同名 + 同类型 + 当前确实缺少二进制。
1039
+ */
1040
+ function findReconnectableAssetId(state, file, registry) {
1041
+ if (state.missingAssetIds.length === 0) return null;
1042
+ return state.assets.find((asset) => state.missingAssetIds.includes(asset.id) && asset.name === file.name && registry.getFile(asset.id) == null)?.id ?? null;
1043
+ }
1044
+ /**
1045
+ * 批量导入素材。
1046
+ *
1047
+ * - 串行处理,避免同时开辟多个解码器把内存打爆;
1048
+ * - 单个文件失败只影响自身,最后汇总 toast;
1049
+ * - 进度写在本地 state(不放进 store,避免高频更新引起全局重渲染);
1050
+ * - 若与「缺失源文件」的素材同名同类型,则重连而不是新增。
1051
+ */
1052
+ function useMediaImport() {
1053
+ const editor = (0, _cut_workbench_react.useCutEditor)();
1054
+ const clips = editor.runtime.clips;
1055
+ const store = editor.store;
1056
+ const [progress, setProgress] = (0, react.useState)([]);
1057
+ const [importing, setImporting] = (0, react.useState)(false);
1058
+ const addAsset = (0, _cut_workbench_react.useEditor)((s) => s.addAsset);
1059
+ const replaceAsset = (0, _cut_workbench_react.useEditor)((s) => s.replaceAsset);
1060
+ const clearMissing = (0, _cut_workbench_react.useEditor)((s) => s.clearMissing);
1061
+ const selectAsset = (0, _cut_workbench_react.useEditor)((s) => s.selectAsset);
1062
+ const beginImport = require_notify.useUi((s) => s.beginImport);
1063
+ const endImport = require_notify.useUi((s) => s.endImport);
1064
+ const { bridge } = require_notify.useWorkbench();
1065
+ return {
1066
+ importing,
1067
+ progress,
1068
+ importFiles: (0, react.useCallback)(async (files) => {
1069
+ if (files.length === 0) return [];
1070
+ setImporting(true);
1071
+ beginImport();
1072
+ setProgress(files.map((f) => ({
1073
+ name: f.name,
1074
+ progress: 0
1075
+ })));
1076
+ const imported = [];
1077
+ const failures = [];
1078
+ let reconnected = 0;
1079
+ for (let i = 0; i < files.length; i += 1) {
1080
+ const file = files[i];
1081
+ if (!file) continue;
1082
+ try {
1083
+ const reconnectId = findReconnectableAssetId(store.getState(), file, clips);
1084
+ const result = await (0, _cut_workbench_core_engine.importFile)(clips, file, (p) => {
1085
+ setProgress((prev) => prev.map((item, index) => index === i ? {
1086
+ ...item,
1087
+ progress: p
1088
+ } : item));
1089
+ });
1090
+ if (reconnectId != null) {
1091
+ clips.replaceAssetSource(reconnectId, file, result.master ?? null);
1092
+ replaceAsset(reconnectId, result.asset);
1093
+ clearMissing(reconnectId);
1094
+ imported.push({
1095
+ ...result.asset,
1096
+ id: reconnectId
1097
+ });
1098
+ reconnected += 1;
1099
+ } else {
1100
+ addAsset(result.asset);
1101
+ imported.push(result.asset);
1102
+ }
1103
+ } catch (err) {
1104
+ const message = err instanceof _cut_workbench_core_engine.UnsupportedFileError ? err.message : err instanceof Error ? err.message : String(err);
1105
+ failures.push(`${file.name}:${message}`);
1106
+ setProgress((prev) => prev.map((item, index) => index === i ? {
1107
+ ...item,
1108
+ error: message
1109
+ } : item));
1110
+ }
1111
+ }
1112
+ setImporting(false);
1113
+ endImport();
1114
+ if (imported.length > 0) {
1115
+ selectAsset(imported[0]?.id ?? null);
1116
+ require_notify.notify(bridge, {
1117
+ level: "success",
1118
+ title: reconnected > 0 ? `已导入 ${imported.length} 个素材(其中 ${reconnected} 个重连到已有片段)` : `已导入 ${imported.length} 个素材`
1119
+ });
1120
+ }
1121
+ if (failures.length > 0) require_notify.notify(bridge, {
1122
+ level: "error",
1123
+ title: `${failures.length} 个文件导入失败`,
1124
+ description: failures.slice(0, 3).join("\n"),
1125
+ duration: 8e3
1126
+ });
1127
+ window.setTimeout(() => setProgress([]), 3e3);
1128
+ return imported;
1129
+ }, [
1130
+ addAsset,
1131
+ beginImport,
1132
+ bridge,
1133
+ clearMissing,
1134
+ endImport,
1135
+ replaceAsset,
1136
+ selectAsset
1137
+ ])
1138
+ };
1139
+ }
1023
1140
  // src/features/media-library/components/DropZone.tsx
1024
- var ACCEPT = {
1141
+ /** react-dropzone 用的 MIME → 扩展名映射 */
1142
+ var DROPZONE_ACCEPT = {
1025
1143
  "video/mp4": [".mp4", ".m4v"],
1026
1144
  "audio/*": [
1027
1145
  ".mp3",
@@ -1044,28 +1162,51 @@ var ACCEPT = {
1044
1162
  /**
1045
1163
  * 拖拽 / 点击导入区域。
1046
1164
  *
1165
+ * 两条入口:
1166
+ * - **拖拽**:走 react-dropzone(宿主无法介入,这是浏览器原生行为);
1167
+ * - **点击**:优先走 `HostBridge.files.pickFiles` —— 宿主可能不用系统文件
1168
+ * 对话框,而是从自己的素材库 / 云端 / URL 导入。宿主返回 `null`(不接管)
1169
+ * 时才回退到内置 `<input type=file>`。
1170
+ *
1047
1171
  * 注意:react-dropzone 的 accept 只做初筛,真正按 MIME + 扩展名校验在
1048
1172
  * core/media/file-reader,从而保证「拖入 .webm 得到明确错误」而不是被静默拒绝。
1049
1173
  */
1050
1174
  function DropZone({ onFiles, disabled, className, compact }) {
1051
- const { getRootProps, getInputProps, isDragActive, open } = (0, react_dropzone.useDropzone)({
1175
+ const { bridge } = require_notify.useWorkbench();
1176
+ const { getRootProps, getInputProps, isDragActive } = (0, react_dropzone.useDropzone)({
1052
1177
  onDrop: (accepted) => {
1053
1178
  if (accepted.length > 0) onFiles(accepted);
1054
1179
  },
1055
- noClick: compact,
1056
- noKeyboard: compact,
1180
+ noClick: true,
1181
+ noKeyboard: true,
1057
1182
  multiple: true,
1058
1183
  disabled,
1059
- accept: ACCEPT
1060
- });
1184
+ accept: DROPZONE_ACCEPT
1185
+ });
1186
+ const handleClick = (0, react.useCallback)(() => {
1187
+ if (disabled) return;
1188
+ pickMediaFiles(bridge, {
1189
+ accept: ACCEPT,
1190
+ multiple: true,
1191
+ description: "选择要导入的素材"
1192
+ }).then((files) => {
1193
+ if (files.length > 0) onFiles(files);
1194
+ });
1195
+ }, [
1196
+ bridge,
1197
+ disabled,
1198
+ onFiles
1199
+ ]);
1061
1200
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1062
1201
  ...getRootProps(),
1063
- className: require_format.cn("group relative flex cursor-pointer flex-col items-center justify-center gap-1.5 rounded-lg border border-dashed text-center transition-colors", compact ? "px-3 py-2.5" : "px-4 py-8", isDragActive ? "border-primary bg-primary/10" : "border-border hover:border-primary/60 hover:bg-muted/40", disabled && "pointer-events-none opacity-50", className),
1202
+ onClick: handleClick,
1203
+ "data-drop-zone": "",
1204
+ className: require_notify.cn("group relative flex cursor-pointer flex-col items-center justify-center gap-1.5 rounded-lg border border-dashed text-center transition-colors", compact ? "px-3 py-2.5" : "px-4 py-8", isDragActive ? "border-primary bg-primary/10" : "border-border hover:border-primary/60 hover:bg-muted/40", disabled && "pointer-events-none opacity-50", className),
1064
1205
  children: [
1065
1206
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { ...getInputProps() }),
1066
1207
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1067
1208
  className: "flex items-center gap-2 text-sm font-medium",
1068
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Upload, { className: require_format.cn("text-muted-foreground", compact ? "size-3.5" : "size-4") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: compact ? "导入素材" : isDragActive ? "松开以导入" : "拖入文件,或点击选择" })]
1209
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Upload, { className: require_notify.cn("text-muted-foreground", compact ? "size-3.5" : "size-4") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: compact ? "导入素材" : isDragActive ? "松开以导入" : "拖入文件,或点击选择" })]
1069
1210
  }),
1070
1211
  !compact && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1071
1212
  className: "text-xs text-muted-foreground",
@@ -1076,7 +1217,7 @@ function DropZone({ onFiles, disabled, className, compact }) {
1076
1217
  className: "sr-only",
1077
1218
  onClick: (e) => {
1078
1219
  e.stopPropagation();
1079
- open();
1220
+ handleClick();
1080
1221
  },
1081
1222
  children: "选择文件"
1082
1223
  })
@@ -1087,7 +1228,7 @@ function DropZone({ onFiles, disabled, className, compact }) {
1087
1228
  function Skeleton({ className, ...props }) {
1088
1229
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1089
1230
  "data-slot": "skeleton",
1090
- className: require_format.cn("animate-pulse rounded-md bg-accent", className),
1231
+ className: require_notify.cn("animate-pulse rounded-md bg-accent", className),
1091
1232
  ...props
1092
1233
  });
1093
1234
  }
@@ -1136,7 +1277,7 @@ function MediaCard({ asset, selected, missing, onSelect, onAddToTimeline, onShow
1136
1277
  onKeyDown: (e) => {
1137
1278
  if (e.key === "Enter") onAddToTimeline();
1138
1279
  },
1139
- className: require_format.cn("group relative flex cursor-pointer flex-col gap-1.5 rounded-lg border p-1.5 text-left transition-colors outline-none", missing ? "border-warning/50 bg-warning/5" : selected ? "border-primary bg-primary/10 ring-1 ring-primary/40" : "border-border bg-card hover:border-primary/40 hover:bg-muted/40 focus-visible:border-primary"),
1280
+ className: require_notify.cn("group relative flex cursor-pointer flex-col gap-1.5 rounded-lg border p-1.5 text-left transition-colors outline-none", missing ? "border-warning/50 bg-warning/5" : selected ? "border-primary bg-primary/10 ring-1 ring-primary/40" : "border-border bg-card hover:border-primary/40 hover:bg-muted/40 focus-visible:border-primary"),
1140
1281
  children: [
1141
1282
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1142
1283
  className: "relative aspect-video w-full overflow-hidden rounded-md bg-muted",
@@ -1157,7 +1298,7 @@ function MediaCard({ asset, selected, missing, onSelect, onAddToTimeline, onShow
1157
1298
  opacity: .9
1158
1299
  })
1159
1300
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1160
- className: require_format.cn("flex size-full items-center justify-center", kind.tone),
1301
+ className: require_notify.cn("flex size-full items-center justify-center", kind.tone),
1161
1302
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "size-7" })
1162
1303
  }),
1163
1304
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -1204,7 +1345,7 @@ function MediaCard({ asset, selected, missing, onSelect, onAddToTimeline, onShow
1204
1345
  children: asset.name
1205
1346
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1206
1347
  className: "truncate text-[10px] text-muted-foreground",
1207
- children: [asset.kind === "audio" ? `${require_format.formatBytes(asset.size)} · 48kHz 双声道` : asset.kind === "text" || asset.kind === "subtitle" ? `${require_format.formatResolution(asset.width, asset.height)}` : `${require_format.formatResolution(asset.width, asset.height)} · ${require_format.formatBytes(asset.size)}`, asset.hasAudio ? " · 含音轨" : ""]
1348
+ children: [asset.kind === "audio" ? `${require_notify.formatBytes(asset.size)} · 48kHz 双声道` : asset.kind === "text" || asset.kind === "subtitle" ? `${require_notify.formatResolution(asset.width, asset.height)}` : `${require_notify.formatResolution(asset.width, asset.height)} · ${require_notify.formatBytes(asset.size)}`, asset.hasAudio ? " · 含音轨" : ""]
1208
1349
  })]
1209
1350
  }),
1210
1351
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownMenuTrigger, {
@@ -1246,7 +1387,7 @@ function MediaGrid({ assets, selectedAssetId, missingAssetIds = [], progress, on
1246
1387
  className: "grid grid-cols-2 gap-2.5 p-2.5",
1247
1388
  children: [
1248
1389
  progress.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1249
- className: require_format.cn("flex flex-col gap-1.5 rounded-lg border border-dashed p-1.5", item.error != null && "border-destructive/50 bg-destructive/5"),
1390
+ className: require_notify.cn("flex flex-col gap-1.5 rounded-lg border border-dashed p-1.5", item.error != null && "border-destructive/50 bg-destructive/5"),
1250
1391
  children: [
1251
1392
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, { className: "aspect-video w-full rounded-md" }),
1252
1393
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -1297,107 +1438,6 @@ function MediaGrid({ assets, selectedAssetId, missingAssetIds = [], progress, on
1297
1438
  ]
1298
1439
  });
1299
1440
  }
1300
- // src/features/media-library/hooks/useMediaImport.ts
1301
- /**
1302
- * 找出可以「重连」的素材。
1303
- *
1304
- * 场景:刷新后工程从 IndexedDB 恢复,但素材二进制消失(File 无法持久化)。
1305
- * 用户重新导入同一个文件时,复用原 assetId,时间轴上已有片段立即恢复。
1306
- * 匹配条件:同名 + 同类型 + 当前确实缺少二进制。
1307
- */
1308
- function findReconnectableAssetId(state, file, registry) {
1309
- if (state.missingAssetIds.length === 0) return null;
1310
- return state.assets.find((asset) => state.missingAssetIds.includes(asset.id) && asset.name === file.name && registry.getFile(asset.id) == null)?.id ?? null;
1311
- }
1312
- /**
1313
- * 批量导入素材。
1314
- *
1315
- * - 串行处理,避免同时开辟多个解码器把内存打爆;
1316
- * - 单个文件失败只影响自身,最后汇总 toast;
1317
- * - 进度写在本地 state(不放进 store,避免高频更新引起全局重渲染);
1318
- * - 若与「缺失源文件」的素材同名同类型,则重连而不是新增。
1319
- */
1320
- function useMediaImport() {
1321
- const editor = (0, _cut_workbench_react.useCutEditor)();
1322
- const clips = editor.runtime.clips;
1323
- const store = editor.store;
1324
- const [progress, setProgress] = (0, react.useState)([]);
1325
- const [importing, setImporting] = (0, react.useState)(false);
1326
- const addAsset = (0, _cut_workbench_react.useEditor)((s) => s.addAsset);
1327
- const replaceAsset = (0, _cut_workbench_react.useEditor)((s) => s.replaceAsset);
1328
- const clearMissing = (0, _cut_workbench_react.useEditor)((s) => s.clearMissing);
1329
- const selectAsset = (0, _cut_workbench_react.useEditor)((s) => s.selectAsset);
1330
- const beginImport = useUi((s) => s.beginImport);
1331
- const endImport = useUi((s) => s.endImport);
1332
- return {
1333
- importing,
1334
- progress,
1335
- importFiles: (0, react.useCallback)(async (files) => {
1336
- if (files.length === 0) return [];
1337
- setImporting(true);
1338
- beginImport();
1339
- setProgress(files.map((f) => ({
1340
- name: f.name,
1341
- progress: 0
1342
- })));
1343
- const imported = [];
1344
- const failures = [];
1345
- let reconnected = 0;
1346
- for (let i = 0; i < files.length; i += 1) {
1347
- const file = files[i];
1348
- if (!file) continue;
1349
- try {
1350
- const reconnectId = findReconnectableAssetId(store.getState(), file, clips);
1351
- const result = await (0, _cut_workbench_core_engine.importFile)(clips, file, (p) => {
1352
- setProgress((prev) => prev.map((item, index) => index === i ? {
1353
- ...item,
1354
- progress: p
1355
- } : item));
1356
- });
1357
- if (reconnectId != null) {
1358
- clips.replaceAssetSource(reconnectId, file, result.master ?? null);
1359
- replaceAsset(reconnectId, result.asset);
1360
- clearMissing(reconnectId);
1361
- imported.push({
1362
- ...result.asset,
1363
- id: reconnectId
1364
- });
1365
- reconnected += 1;
1366
- } else {
1367
- addAsset(result.asset);
1368
- imported.push(result.asset);
1369
- }
1370
- } catch (err) {
1371
- const message = err instanceof _cut_workbench_core_engine.UnsupportedFileError ? err.message : err instanceof Error ? err.message : String(err);
1372
- failures.push(`${file.name}:${message}`);
1373
- setProgress((prev) => prev.map((item, index) => index === i ? {
1374
- ...item,
1375
- error: message
1376
- } : item));
1377
- }
1378
- }
1379
- setImporting(false);
1380
- endImport();
1381
- if (imported.length > 0) {
1382
- selectAsset(imported[0]?.id ?? null);
1383
- sonner.toast.success(reconnected > 0 ? `已导入 ${imported.length} 个素材(其中 ${reconnected} 个重连到已有片段)` : `已导入 ${imported.length} 个素材`);
1384
- }
1385
- if (failures.length > 0) sonner.toast.error(`${failures.length} 个文件导入失败`, {
1386
- description: failures.slice(0, 3).join("\n"),
1387
- duration: 8e3
1388
- });
1389
- window.setTimeout(() => setProgress([]), 3e3);
1390
- return imported;
1391
- }, [
1392
- addAsset,
1393
- beginImport,
1394
- clearMissing,
1395
- endImport,
1396
- replaceAsset,
1397
- selectAsset
1398
- ])
1399
- };
1400
- }
1401
1441
  // src/features/media-library/components/MediaLibraryPanel.tsx
1402
1442
  var KIND_LABEL$1 = {
1403
1443
  video: "视频",
@@ -1485,11 +1525,11 @@ function MediaLibraryPanel({ onAddToTimeline, onRemoveAsset }) {
1485
1525
  }),
1486
1526
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
1487
1527
  label: "分辨率",
1488
- value: require_format.formatResolution(metaAsset.width, metaAsset.height)
1528
+ value: require_notify.formatResolution(metaAsset.width, metaAsset.height)
1489
1529
  }),
1490
1530
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
1491
1531
  label: "大小",
1492
- value: metaAsset.kind === "text" ? "—" : require_format.formatBytes(metaAsset.size)
1532
+ value: metaAsset.kind === "text" ? "—" : require_notify.formatBytes(metaAsset.size)
1493
1533
  }),
1494
1534
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
1495
1535
  label: "音轨",
@@ -1565,6 +1605,7 @@ function CreateTextDialog({ open, onOpenChange, onCreated }) {
1565
1605
  const projectWidth = (0, _cut_workbench_react.useEditor)((s) => s.project.width);
1566
1606
  const projectHeight = (0, _cut_workbench_react.useEditor)((s) => s.project.height);
1567
1607
  const addAsset = (0, _cut_workbench_react.useEditor)((s) => s.addAsset);
1608
+ const { bridge } = require_notify.useWorkbench();
1568
1609
  const handleCreate = () => {
1569
1610
  const { asset } = (0, _cut_workbench_core_engine.createTextAsset)({
1570
1611
  registry: editor.runtime.clips,
@@ -1575,7 +1616,10 @@ function CreateTextDialog({ open, onOpenChange, onCreated }) {
1575
1616
  addAsset(asset);
1576
1617
  onCreated(asset.id);
1577
1618
  onOpenChange(false);
1578
- sonner.toast.success("已创建文字素材");
1619
+ require_notify.notify(bridge, {
1620
+ level: "success",
1621
+ title: "已创建文字素材"
1622
+ });
1579
1623
  };
1580
1624
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
1581
1625
  open,
@@ -1609,10 +1653,15 @@ function CreateSubtitleDialog({ open, onOpenChange, onCreated }) {
1609
1653
  const projectWidth = (0, _cut_workbench_react.useEditor)((s) => s.project.width);
1610
1654
  const projectHeight = (0, _cut_workbench_react.useEditor)((s) => s.project.height);
1611
1655
  const addAsset = (0, _cut_workbench_react.useEditor)((s) => s.addAsset);
1656
+ const { bridge } = require_notify.useWorkbench();
1612
1657
  const handleCreate = () => {
1613
1658
  const durationUs = (0, _cut_workbench_core.parseSrtDurationUs)(srt);
1614
1659
  if (durationUs <= 0) {
1615
- sonner.toast.error("SRT 解析失败", { description: "请检查时间轴格式:00:00:00,000 --> 00:00:02,000" });
1660
+ require_notify.notify(bridge, {
1661
+ level: "error",
1662
+ title: "SRT 解析失败",
1663
+ description: "请检查时间轴格式:00:00:00,000 --> 00:00:02,000"
1664
+ });
1616
1665
  return;
1617
1666
  }
1618
1667
  const { asset } = (0, _cut_workbench_core_engine.createSubtitleAsset)({
@@ -1625,7 +1674,10 @@ function CreateSubtitleDialog({ open, onOpenChange, onCreated }) {
1625
1674
  addAsset(asset);
1626
1675
  onCreated(asset.id);
1627
1676
  onOpenChange(false);
1628
- sonner.toast.success("已创建字幕素材");
1677
+ require_notify.notify(bridge, {
1678
+ level: "success",
1679
+ title: "已创建字幕素材"
1680
+ });
1629
1681
  };
1630
1682
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
1631
1683
  open,
@@ -1651,11 +1703,22 @@ function CreateSubtitleDialog({ open, onOpenChange, onCreated }) {
1651
1703
  });
1652
1704
  }
1653
1705
  // src/hooks/use-toast-safe.ts
1654
- /** 统一把异常转成可见提示,禁止静默吞掉 */
1706
+ /**
1707
+ * 把异常转成可见提示,禁止静默吞掉。
1708
+ *
1709
+ * 这是**非 hook** 版本,供事件回调 / 非组件代码调用(例如 `useAvCanvas` 的
1710
+ * 初始化失败)。它拿不到 React context,因此走 `notify` 注册的默认桥:
1711
+ * `WorkbenchProvider` 挂载时会把 `bridge` 注册进来,宿主实现了 `notify`
1712
+ * 就交给宿主,否则退回库内 toast。
1713
+ */
1655
1714
  function reportError(error, fallback = "操作失败") {
1656
1715
  const message = error instanceof Error ? error.message : String(error);
1657
1716
  console.error(error);
1658
- sonner.toast.error(fallback, { description: message });
1717
+ require_notify.notify(void 0, {
1718
+ level: "error",
1719
+ title: fallback,
1720
+ description: message
1721
+ });
1659
1722
  }
1660
1723
  // src/features/preview/hooks/useAvCanvas.ts
1661
1724
  /**
@@ -2285,7 +2348,7 @@ function PreviewStage({ attachRef, width, height, emptyHint, onCapture, onFullsc
2285
2348
  const scaleY = height > 0 ? box.h / height : 1;
2286
2349
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2287
2350
  ref: outerRef,
2288
- className: require_format.cn("relative flex min-h-0 flex-1 items-center justify-center overflow-hidden bg-stage-bg", className),
2351
+ className: require_notify.cn("relative flex min-h-0 flex-1 items-center justify-center overflow-hidden bg-stage-bg", className),
2289
2352
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2290
2353
  className: "relative shrink-0 overflow-hidden rounded-md bg-black shadow-2xl ring-1 ring-border",
2291
2354
  style: {
@@ -2346,14 +2409,14 @@ function Slider({ className, defaultValue, value, min = 0, max = 100, ...props }
2346
2409
  value,
2347
2410
  min,
2348
2411
  max,
2349
- className: require_format.cn("relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col", className),
2412
+ className: require_notify.cn("relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col", className),
2350
2413
  ...props,
2351
2414
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Slider.Track, {
2352
2415
  "data-slot": "slider-track",
2353
- className: require_format.cn("relative grow overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"),
2416
+ className: require_notify.cn("relative grow overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"),
2354
2417
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Slider.Range, {
2355
2418
  "data-slot": "slider-range",
2356
- className: require_format.cn("absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full")
2419
+ className: require_notify.cn("absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full")
2357
2420
  })
2358
2421
  }), Array.from({ length: _values.length }, (_, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Slider.Thumb, {
2359
2422
  "data-slot": "slider-thumb",
@@ -2368,7 +2431,7 @@ function Slider({ className, defaultValue, value, min = 0, max = 100, ...props }
2368
2431
  */
2369
2432
  function Timecode({ us, showMs = true, className, muted, title }) {
2370
2433
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2371
- className: require_format.cn("font-mono text-xs tabular-nums", muted && "text-muted-foreground", className),
2434
+ className: require_notify.cn("font-mono text-xs tabular-nums", muted && "text-muted-foreground", className),
2372
2435
  title: title ?? `${us} µs`,
2373
2436
  children: (0, _cut_workbench_core.formatTimecode)(us, showMs)
2374
2437
  });
@@ -2419,7 +2482,7 @@ function PlaybackControls({ playing, currentUs, durationUs, masterVolume, playba
2419
2482
  const full = width === 0 || width >= FULL_PX;
2420
2483
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2421
2484
  ref: rootRef,
2422
- className: require_format.cn("flex h-11 shrink-0 items-center gap-1.5 overflow-hidden border-t bg-card/60", ultraCompact ? "px-1.5" : "px-3", className),
2485
+ className: require_notify.cn("flex h-11 shrink-0 items-center gap-1.5 overflow-hidden border-t bg-card/60", ultraCompact ? "px-1.5" : "px-3", className),
2423
2486
  children: [
2424
2487
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2425
2488
  className: "flex shrink-0 items-center gap-0.5",
@@ -2480,7 +2543,7 @@ function PlaybackControls({ playing, currentUs, durationUs, masterVolume, playba
2480
2543
  ]
2481
2544
  }),
2482
2545
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2483
- className: require_format.cn("mx-2 flex shrink-0 items-baseline gap-1 whitespace-nowrap", ultraCompact && "mx-1", micro && "hidden"),
2546
+ className: require_notify.cn("mx-2 flex shrink-0 items-baseline gap-1 whitespace-nowrap", ultraCompact && "mx-1", micro && "hidden"),
2484
2547
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timecode, {
2485
2548
  us: currentUs,
2486
2549
  className: "text-[13px] font-medium"
@@ -2504,14 +2567,14 @@ function PlaybackControls({ playing, currentUs, durationUs, masterVolume, playba
2504
2567
  "aria-label": "播放进度"
2505
2568
  }),
2506
2569
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2507
- className: require_format.cn("flex shrink-0 items-center gap-1", compact && "ml-auto", micro && "hidden"),
2570
+ className: require_notify.cn("flex shrink-0 items-center gap-1", compact && "ml-auto", micro && "hidden"),
2508
2571
  children: [
2509
2572
  !compact && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2510
2573
  className: "flex items-center rounded-md border p-0.5",
2511
2574
  children: RATES.map((rate) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2512
2575
  type: "button",
2513
2576
  onClick: () => onRateChange(rate),
2514
- className: require_format.cn("rounded px-1.5 py-0.5 text-[11px] tabular-nums transition-colors", playbackRate === rate ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"),
2577
+ className: require_notify.cn("rounded px-1.5 py-0.5 text-[11px] tabular-nums transition-colors", playbackRate === rate ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"),
2515
2578
  title: rate !== 1 ? "变速预览(不会改变导出结果)" : "正常速度",
2516
2579
  children: [rate, "×"]
2517
2580
  }, rate))
@@ -2575,6 +2638,7 @@ function PreviewPanel() {
2575
2638
  const setMasterVolume = (0, _cut_workbench_react.useEditor)((s) => s.setMasterVolume);
2576
2639
  const playheadUs = (0, _cut_workbench_react.useEditor)((s) => s.playheadUs);
2577
2640
  const selectedClipIds = (0, _cut_workbench_react.useEditor)((s) => s.selectedClipIds);
2641
+ const { bridge } = require_notify.useWorkbench();
2578
2642
  const canvas = useAvCanvas({
2579
2643
  width: project.width,
2580
2644
  height: project.height
@@ -2585,12 +2649,18 @@ function PreviewPanel() {
2585
2649
  if (errors.length === 0) return;
2586
2650
  const missing = errors.filter((e) => e.message.startsWith("素材文件未载入"));
2587
2651
  const other = errors.filter((e) => !e.message.startsWith("素材文件未载入"));
2588
- if (missing.length > 0) sonner.toast.warning(`${missing.length} 个片段缺少源文件,已跳过预览`, {
2652
+ if (missing.length > 0) require_notify.notify(bridge, {
2653
+ level: "warning",
2654
+ title: `${missing.length} 个片段缺少源文件,已跳过预览`,
2589
2655
  description: "刷新后素材二进制不会保留,请重新导入相同文件",
2590
2656
  duration: 8e3
2591
2657
  });
2592
- if (other.length > 0) sonner.toast.error("部分片段无法载入预览", { description: Array.from(new Set(other.map((e) => e.message))).slice(0, 3).join("\n") });
2593
- }, []);
2658
+ if (other.length > 0) require_notify.notify(bridge, {
2659
+ level: "error",
2660
+ title: "部分片段无法载入预览",
2661
+ description: Array.from(new Set(other.map((e) => e.message))).slice(0, 3).join("\n")
2662
+ });
2663
+ }, [bridge]);
2594
2664
  const clock = usePlaybackClock({
2595
2665
  project,
2596
2666
  assets,
@@ -2726,12 +2796,18 @@ function PreviewPanel() {
2726
2796
  const handleCapture = (0, react.useCallback)(() => {
2727
2797
  const dataUrl = canvas.captureImage();
2728
2798
  if (dataUrl == null) {
2729
- sonner.toast.error("截图失败:画布尚未就绪");
2799
+ require_notify.notify(bridge, {
2800
+ level: "error",
2801
+ title: "截图失败:画布尚未就绪"
2802
+ });
2730
2803
  return;
2731
2804
  }
2732
2805
  downloadDataUrl(dataUrl, `frame-${Date.now()}.png`);
2733
- sonner.toast.success("已导出当前帧");
2734
- }, [canvas]);
2806
+ require_notify.notify(bridge, {
2807
+ level: "success",
2808
+ title: "已导出当前帧"
2809
+ });
2810
+ }, [bridge, canvas]);
2735
2811
  const handleFullscreen = () => {
2736
2812
  const el = canvas.containerRef.current?.parentElement;
2737
2813
  if (el == null) return;
@@ -2968,14 +3044,14 @@ function Ruler({ pxPerSecond, widthPx, playheadPx, scrollLeftPx, className }) {
2968
3044
  widthPx
2969
3045
  ]);
2970
3046
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2971
- className: require_format.cn("relative shrink-0 cursor-ew-resize border-b bg-timeline-bg select-none", className),
3047
+ className: require_notify.cn("relative shrink-0 cursor-ew-resize border-b bg-timeline-bg select-none", className),
2972
3048
  style: {
2973
3049
  height: 28,
2974
3050
  width: widthPx
2975
3051
  },
2976
3052
  children: [
2977
3053
  ticks.map((tick) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2978
- className: require_format.cn("absolute bottom-0 w-px", tick.major ? "h-2 bg-muted-foreground/50" : "h-1 bg-muted-foreground/25"),
3054
+ className: require_notify.cn("absolute bottom-0 w-px", tick.major ? "h-2 bg-muted-foreground/50" : "h-1 bg-muted-foreground/25"),
2979
3055
  style: { left: tick.px }
2980
3056
  }, `${tick.us}-${tick.major ? "M" : "m"}`)),
2981
3057
  ticks.filter((t) => t.major).map((tick) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -3003,7 +3079,7 @@ function Ruler({ pxPerSecond, widthPx, playheadPx, scrollLeftPx, className }) {
3003
3079
  */
3004
3080
  var TrackHeader = (0, react.memo)(function TrackHeader({ track, onToggleMute, onToggleLock, onToggleHidden, onRemove }) {
3005
3081
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3006
- className: require_format.cn("flex items-center gap-1 border-r border-b border-timeline-grid px-2", track.kind === "audio" ? "bg-clip-audio/10" : "bg-clip-video/10", track.hidden && "opacity-60"),
3082
+ className: require_notify.cn("flex items-center gap-1 border-r border-b border-timeline-grid px-2", track.kind === "audio" ? "bg-clip-audio/10" : "bg-clip-video/10", track.hidden && "opacity-60"),
3007
3083
  style: { height: 56 },
3008
3084
  "data-track-header": true,
3009
3085
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3058,7 +3134,7 @@ function IconToggle({ active, onClick, label, icon: Icon }) {
3058
3134
  onClick,
3059
3135
  "aria-label": label,
3060
3136
  "aria-pressed": active,
3061
- className: require_format.cn("flex size-[18px] items-center justify-center rounded transition-colors", active ? "bg-primary/20 text-primary" : "text-muted-foreground hover:text-foreground"),
3137
+ className: require_notify.cn("flex size-[18px] items-center justify-center rounded transition-colors", active ? "bg-primary/20 text-primary" : "text-muted-foreground hover:text-foreground"),
3062
3138
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "size-3" })
3063
3139
  })
3064
3140
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContent, { children: label })] });
@@ -3149,7 +3225,7 @@ var ClipBlock = (0, react.memo)(function ClipBlock({ clip, asset, pxPerSecond, s
3149
3225
  handlers.beginMove(event, clip);
3150
3226
  },
3151
3227
  onDoubleClick: () => handlers.seekTo(clip),
3152
- className: require_format.cn("group absolute top-1 bottom-1 overflow-hidden rounded-md border text-left select-none transition-shadow", KIND_BG[kind], selected ? "ring-2 ring-primary ring-offset-1 ring-offset-timeline-bg" : "hover:brightness-110", locked ? "cursor-not-allowed opacity-70" : "cursor-grab active:cursor-grabbing"),
3228
+ className: require_notify.cn("group absolute top-1 bottom-1 overflow-hidden rounded-md border text-left select-none transition-shadow", KIND_BG[kind], selected ? "ring-2 ring-primary ring-offset-1 ring-offset-timeline-bg" : "hover:brightness-110", locked ? "cursor-not-allowed opacity-70" : "cursor-grab active:cursor-grabbing"),
3153
3229
  style: {
3154
3230
  left: leftPx,
3155
3231
  width: widthPx,
@@ -3228,7 +3304,7 @@ function ContextMenuPortal({ ...props }) {
3228
3304
  function ContextMenuContent({ className, ...props }) {
3229
3305
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextMenuPortal, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.ContextMenu.Content, {
3230
3306
  "data-slot": "context-menu-content",
3231
- className: require_format.cn("z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", className),
3307
+ className: require_notify.cn("z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", className),
3232
3308
  ...props
3233
3309
  }) });
3234
3310
  }
@@ -3237,21 +3313,21 @@ function ContextMenuItem({ className, inset, variant = "default", ...props }) {
3237
3313
  "data-slot": "context-menu-item",
3238
3314
  "data-inset": inset,
3239
3315
  "data-variant": variant,
3240
- className: require_format.cn("relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!", className),
3316
+ className: require_notify.cn("relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!", className),
3241
3317
  ...props
3242
3318
  });
3243
3319
  }
3244
3320
  function ContextMenuSeparator({ className, ...props }) {
3245
3321
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.ContextMenu.Separator, {
3246
3322
  "data-slot": "context-menu-separator",
3247
- className: require_format.cn("-mx-1 my-1 h-px bg-border", className),
3323
+ className: require_notify.cn("-mx-1 my-1 h-px bg-border", className),
3248
3324
  ...props
3249
3325
  });
3250
3326
  }
3251
3327
  function ContextMenuShortcut({ className, ...props }) {
3252
3328
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3253
3329
  "data-slot": "context-menu-shortcut",
3254
- className: require_format.cn("ml-auto text-xs tracking-widest text-muted-foreground", className),
3330
+ className: require_notify.cn("ml-auto text-xs tracking-widest text-muted-foreground", className),
3255
3331
  ...props
3256
3332
  });
3257
3333
  }
@@ -3308,14 +3384,14 @@ function ClipContextMenu({ children, onSplit, onDuplicate, onRemove, onShowInfo,
3308
3384
  /** 播放头:一条竖线 + 顶部把手,跨过所有轨道 */
3309
3385
  function Playhead({ px, playing, className }) {
3310
3386
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3311
- className: require_format.cn("pointer-events-none absolute top-0 bottom-0 z-20 w-px", className),
3387
+ className: require_notify.cn("pointer-events-none absolute top-0 bottom-0 z-20 w-px", className),
3312
3388
  style: { left: px },
3313
3389
  "aria-hidden": true,
3314
3390
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3315
- className: require_format.cn("absolute inset-y-0 -left-px w-0.5 bg-playhead", playing && "shadow-[0_0_6px_var(--cw-playhead)]"),
3391
+ className: require_notify.cn("absolute inset-y-0 -left-px w-0.5 bg-playhead", playing && "shadow-[0_0_6px_var(--cw-playhead)]"),
3316
3392
  style: { top: 24 }
3317
3393
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3318
- className: require_format.cn("absolute -translate-x-1/2 rounded-sm bg-playhead", playing ? "size-3 rotate-45" : "h-2.5 w-2.5 rotate-45"),
3394
+ className: require_notify.cn("absolute -translate-x-1/2 rounded-sm bg-playhead", playing ? "size-3 rotate-45" : "h-2.5 w-2.5 rotate-45"),
3319
3395
  style: { top: 14 }
3320
3396
  })]
3321
3397
  });
@@ -3324,7 +3400,7 @@ function Playhead({ px, playing, className }) {
3324
3400
  /** 框选矩形 */
3325
3401
  function Marquee({ x1, y1, x2, y2, className }) {
3326
3402
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3327
- className: require_format.cn("pointer-events-none absolute z-40 rounded-sm border border-primary/70 bg-primary/15", className),
3403
+ className: require_notify.cn("pointer-events-none absolute z-40 rounded-sm border border-primary/70 bg-primary/15", className),
3328
3404
  style: {
3329
3405
  left: Math.min(x1, x2),
3330
3406
  top: Math.min(y1, y2),
@@ -3342,10 +3418,10 @@ function Marquee({ x1, y1, x2, y2, className }) {
3342
3418
  * 调整回去,视觉上内容不会乱跳。
3343
3419
  */
3344
3420
  function useTimelineZoom(containerRef) {
3345
- const uiApi = useUiStoreApi();
3346
- const pxPerSecond = useUi((s) => s.pxPerSecond);
3347
- const setPxPerSecond = useUi((s) => s.setPxPerSecond);
3348
- const setScrollLeftPx = useUi((s) => s.setScrollLeftPx);
3421
+ const uiApi = require_notify.useUiStoreApi();
3422
+ const pxPerSecond = require_notify.useUi((s) => s.pxPerSecond);
3423
+ const setPxPerSecond = require_notify.useUi((s) => s.setPxPerSecond);
3424
+ const setScrollLeftPx = require_notify.useUi((s) => s.setScrollLeftPx);
3349
3425
  const applyZoom = (0, react.useCallback)((nextPps, anchorClientX) => {
3350
3426
  const container = containerRef.current;
3351
3427
  const prevPps = uiApi.getState().pxPerSecond;
@@ -3670,7 +3746,7 @@ function useTimelineDrag({ pxPerSecond, contentLeftPx, resolveTrackAt, snapEnabl
3670
3746
  */
3671
3747
  function Timeline() {
3672
3748
  const editor = (0, _cut_workbench_react.useCutEditor)();
3673
- const uiApi = useUiStoreApi();
3749
+ const uiApi = require_notify.useUiStoreApi();
3674
3750
  const project = (0, _cut_workbench_react.useEditor)((s) => s.project);
3675
3751
  const assets = (0, _cut_workbench_react.useEditor)((s) => s.assets);
3676
3752
  const selectedClipIds = (0, _cut_workbench_react.useEditor)((s) => s.selectedClipIds);
@@ -3999,7 +4075,7 @@ function Timeline() {
3999
4075
  "data-timeline-tracks": true,
4000
4076
  children: [
4001
4077
  project.tracks.map((track, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4002
- className: require_format.cn("relative border-b border-timeline-grid", track.kind === "audio" ? "bg-timeline-track/30" : "bg-timeline-track/50", track.hidden && "opacity-40"),
4078
+ className: require_notify.cn("relative border-b border-timeline-grid", track.kind === "audio" ? "bg-timeline-track/30" : "bg-timeline-track/50", track.hidden && "opacity-40"),
4003
4079
  style: { height: 56 },
4004
4080
  "data-track-row": track.id,
4005
4081
  children: [
@@ -4070,7 +4146,7 @@ function Tabs({ className, orientation = "horizontal", ...props }) {
4070
4146
  "data-slot": "tabs",
4071
4147
  "data-orientation": orientation,
4072
4148
  orientation,
4073
- className: require_format.cn("group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className),
4149
+ className: require_notify.cn("group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className),
4074
4150
  ...props
4075
4151
  });
4076
4152
  }
@@ -4085,21 +4161,21 @@ function TabsList({ className, variant = "default", ...props }) {
4085
4161
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Tabs.List, {
4086
4162
  "data-slot": "tabs-list",
4087
4163
  "data-variant": variant,
4088
- className: require_format.cn(tabsListVariants({ variant }), className),
4164
+ className: require_notify.cn(tabsListVariants({ variant }), className),
4089
4165
  ...props
4090
4166
  });
4091
4167
  }
4092
4168
  function TabsTrigger({ className, ...props }) {
4093
4169
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Tabs.Trigger, {
4094
4170
  "data-slot": "tabs-trigger",
4095
- className: require_format.cn("relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent", "data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100", className),
4171
+ className: require_notify.cn("relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent", "data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100", className),
4096
4172
  ...props
4097
4173
  });
4098
4174
  }
4099
4175
  function TabsContent({ className, ...props }) {
4100
4176
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Tabs.Content, {
4101
4177
  "data-slot": "tabs-content",
4102
- className: require_format.cn("flex-1 outline-none", className),
4178
+ className: require_notify.cn("flex-1 outline-none", className),
4103
4179
  ...props
4104
4180
  });
4105
4181
  }
@@ -4108,7 +4184,7 @@ function Input({ className, type, ...props }) {
4108
4184
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4109
4185
  type,
4110
4186
  "data-slot": "input",
4111
- className: require_format.cn("h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30", "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50", "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40", className),
4187
+ className: require_notify.cn("h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30", "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50", "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40", className),
4112
4188
  ...props
4113
4189
  });
4114
4190
  }
@@ -4129,7 +4205,7 @@ function SelectTrigger({ className, size = "default", children, ...props }) {
4129
4205
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(radix_ui.Select.Trigger, {
4130
4206
  "data-slot": "select-trigger",
4131
4207
  "data-size": size,
4132
- className: require_format.cn("flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", className),
4208
+ className: require_notify.cn("flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", className),
4133
4209
  ...props,
4134
4210
  children: [children, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Select.Icon, {
4135
4211
  asChild: true,
@@ -4143,14 +4219,14 @@ function SelectContent({ className, children, position = "item-aligned", align =
4143
4219
  container: container ?? void 0,
4144
4220
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(radix_ui.Select.Content, {
4145
4221
  "data-slot": "select-content",
4146
- className: require_format.cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
4222
+ className: require_notify.cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
4147
4223
  position,
4148
4224
  align,
4149
4225
  ...props,
4150
4226
  children: [
4151
4227
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectScrollUpButton, {}),
4152
4228
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Select.Viewport, {
4153
- className: require_format.cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"),
4229
+ className: require_notify.cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"),
4154
4230
  children
4155
4231
  }),
4156
4232
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectScrollDownButton, {})
@@ -4161,7 +4237,7 @@ function SelectContent({ className, children, position = "item-aligned", align =
4161
4237
  function SelectItem({ className, children, ...props }) {
4162
4238
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(radix_ui.Select.Item, {
4163
4239
  "data-slot": "select-item",
4164
- className: require_format.cn("relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className),
4240
+ className: require_notify.cn("relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className),
4165
4241
  ...props,
4166
4242
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4167
4243
  "data-slot": "select-item-indicator",
@@ -4173,7 +4249,7 @@ function SelectItem({ className, children, ...props }) {
4173
4249
  function SelectScrollUpButton({ className, ...props }) {
4174
4250
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Select.ScrollUpButton, {
4175
4251
  "data-slot": "select-scroll-up-button",
4176
- className: require_format.cn("flex cursor-default items-center justify-center py-1", className),
4252
+ className: require_notify.cn("flex cursor-default items-center justify-center py-1", className),
4177
4253
  ...props,
4178
4254
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronUpIcon, { className: "size-4" })
4179
4255
  });
@@ -4181,7 +4257,7 @@ function SelectScrollUpButton({ className, ...props }) {
4181
4257
  function SelectScrollDownButton({ className, ...props }) {
4182
4258
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Select.ScrollDownButton, {
4183
4259
  "data-slot": "select-scroll-down-button",
4184
- className: require_format.cn("flex cursor-default items-center justify-center py-1", className),
4260
+ className: require_notify.cn("flex cursor-default items-center justify-center py-1", className),
4185
4261
  ...props,
4186
4262
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDownIcon, { className: "size-4" })
4187
4263
  });
@@ -4190,7 +4266,7 @@ function SelectScrollDownButton({ className, ...props }) {
4190
4266
  /** 属性面板的一行:标签 + 控件(标签在左,控件占满剩余宽度) */
4191
4267
  function Field({ label, hint, children, className, htmlFor }) {
4192
4268
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4193
- className: require_format.cn("flex items-center gap-2", className),
4269
+ className: require_notify.cn("flex items-center gap-2", className),
4194
4270
  children: [
4195
4271
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
4196
4272
  htmlFor,
@@ -4211,7 +4287,7 @@ function Field({ label, hint, children, className, htmlFor }) {
4211
4287
  /** 属性分组 */
4212
4288
  function Section({ title, children, action, className }) {
4213
4289
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4214
- className: require_format.cn("space-y-2", className),
4290
+ className: require_notify.cn("space-y-2", className),
4215
4291
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4216
4292
  className: "flex items-center justify-between",
4217
4293
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
@@ -4406,7 +4482,7 @@ function VideoProps({ clip, onPatch, onPatchTransform, onPatchSize }) {
4406
4482
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
4407
4483
  type: "number",
4408
4484
  className: "h-7 text-xs tabular-nums",
4409
- value: require_format.formatSeconds(clip.startUs),
4485
+ value: require_notify.formatSeconds(clip.startUs),
4410
4486
  step: .1,
4411
4487
  min: 0,
4412
4488
  onChange: (e) => {
@@ -4420,7 +4496,7 @@ function VideoProps({ clip, onPatch, onPatchTransform, onPatchSize }) {
4420
4496
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
4421
4497
  type: "number",
4422
4498
  className: "h-7 text-xs tabular-nums",
4423
- value: require_format.formatSeconds(clip.durationUs),
4499
+ value: require_notify.formatSeconds(clip.durationUs),
4424
4500
  step: .1,
4425
4501
  min: .1,
4426
4502
  onChange: (e) => {
@@ -4527,7 +4603,7 @@ function AudioProps({ clip, onPatch }) {
4527
4603
  className: "text-[10px] text-muted-foreground",
4528
4604
  children: [
4529
4605
  "片段时长 ",
4530
- require_format.formatSeconds(clip.durationUs),
4606
+ require_notify.formatSeconds(clip.durationUs),
4531
4607
  "s,淡入使用 setAnimation 线性插值。"
4532
4608
  ]
4533
4609
  })
@@ -4782,7 +4858,7 @@ function SubtitleProps({ clip, onPatch, onRebuild }) {
4782
4858
  className: "text-[10px] text-muted-foreground",
4783
4859
  children: [
4784
4860
  "片段时长 ",
4785
- require_format.formatSeconds(clip.durationUs),
4861
+ require_notify.formatSeconds(clip.durationUs),
4786
4862
  "s",
4787
4863
  cues.length > 0 && ` · 最后一条结束于 ${(cues[cues.length - 1].endMs / 1e3).toFixed(2)}s`
4788
4864
  ]
@@ -5054,7 +5130,7 @@ function InspectorPanel() {
5054
5130
  title: asset.name,
5055
5131
  children: asset.name
5056
5132
  }),
5057
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_format.Badge, {
5133
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_notify.Badge, {
5058
5134
  variant: "outline",
5059
5135
  className: "text-[10px]",
5060
5136
  children: KIND_LABEL[asset.kind]
@@ -5077,7 +5153,7 @@ function InspectorPanel() {
5077
5153
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
5078
5154
  (0, _cut_workbench_core.formatDuration)(clip.durationUs),
5079
5155
  " · ",
5080
- require_format.formatResolution(asset.width, asset.height)
5156
+ require_notify.formatResolution(asset.width, asset.height)
5081
5157
  ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
5082
5158
  "入点 ",
5083
5159
  (clip.inPointUs / 1e6).toFixed(2),
@@ -5182,11 +5258,11 @@ function Switch({ className, size = "default", ...props }) {
5182
5258
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Switch.Root, {
5183
5259
  "data-slot": "switch",
5184
5260
  "data-size": size,
5185
- className: require_format.cn("peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80", className),
5261
+ className: require_notify.cn("peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80", className),
5186
5262
  ...props,
5187
5263
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Switch.Thumb, {
5188
5264
  "data-slot": "switch-thumb",
5189
- className: require_format.cn("pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground")
5265
+ className: require_notify.cn("pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground")
5190
5266
  })
5191
5267
  });
5192
5268
  }
@@ -5194,7 +5270,7 @@ function Switch({ className, size = "default", ...props }) {
5194
5270
  function Progress({ className, value, ...props }) {
5195
5271
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Progress.Root, {
5196
5272
  "data-slot": "progress",
5197
- className: require_format.cn("relative h-2 w-full overflow-hidden rounded-full bg-primary/20", className),
5273
+ className: require_notify.cn("relative h-2 w-full overflow-hidden rounded-full bg-primary/20", className),
5198
5274
  ...props,
5199
5275
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(radix_ui.Progress.Indicator, {
5200
5276
  "data-slot": "progress-indicator",
@@ -5725,8 +5801,8 @@ var FPS_OPTIONS = [
5725
5801
  * 所有重活都在 Worker 里;这里只负责 UI 与状态机。
5726
5802
  */
5727
5803
  function ExportDialog() {
5728
- const open = useUi((s) => s.dialog === "export");
5729
- const closeDialog = useUi((s) => s.closeDialog);
5804
+ const open = require_notify.useUi((s) => s.dialog === "export");
5805
+ const closeDialog = require_notify.useUi((s) => s.closeDialog);
5730
5806
  const project = (0, _cut_workbench_react.useEditor)((s) => s.project);
5731
5807
  const assets = (0, _cut_workbench_react.useEditor)((s) => s.assets);
5732
5808
  const [preset, setPreset] = (0, react.useState)("1080p");
@@ -5872,7 +5948,7 @@ function ExportDialog() {
5872
5948
  className: "flex justify-between",
5873
5949
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "预估大小" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5874
5950
  className: "font-mono tabular-nums",
5875
- children: require_format.formatBytes(estimatedBytes)
5951
+ children: require_notify.formatBytes(estimatedBytes)
5876
5952
  })]
5877
5953
  }),
5878
5954
  hasTextClips && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -5909,10 +5985,10 @@ function ExportDialog() {
5909
5985
  children: [
5910
5986
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CheckCircle2, { className: "size-3.5" }),
5911
5987
  "导出完成 · 耗时 ",
5912
- require_format.formatElapsed(state.result.elapsedMs / 1e3),
5988
+ require_notify.formatElapsed(state.result.elapsedMs / 1e3),
5913
5989
  " ·",
5914
5990
  " ",
5915
- require_format.formatBytes(state.result.blob.size),
5991
+ require_notify.formatBytes(state.result.blob.size),
5916
5992
  state.result.mode === "main" && "(主线程)"
5917
5993
  ]
5918
5994
  }),
@@ -5975,7 +6051,7 @@ function ExportProgress({ progress, label = "正在合成…", className }) {
5975
6051
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "size-3 animate-spin" }), label]
5976
6052
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5977
6053
  className: "font-mono tabular-nums",
5978
- children: require_format.formatPercent(progress)
6054
+ children: require_notify.formatPercent(progress)
5979
6055
  })]
5980
6056
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Progress, { value: progress * 100 })]
5981
6057
  });
@@ -6051,7 +6127,7 @@ function CanvasSettingsBody({ onDone }) {
6051
6127
  setDraftW(String(preset.width));
6052
6128
  setDraftH(String(preset.height));
6053
6129
  },
6054
- className: require_format.cn("flex flex-col items-center gap-1 rounded-md border px-2 py-2 text-[11px] transition-colors", activePreset?.id === preset.id ? "border-primary bg-primary/10 text-foreground" : "text-muted-foreground hover:border-primary/50 hover:text-foreground"),
6130
+ className: require_notify.cn("flex flex-col items-center gap-1 rounded-md border px-2 py-2 text-[11px] transition-colors", activePreset?.id === preset.id ? "border-primary bg-primary/10 text-foreground" : "text-muted-foreground hover:border-primary/50 hover:text-foreground"),
6055
6131
  title: preset.hint,
6056
6132
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6057
6133
  className: "block rounded-[2px] border border-current opacity-70",
@@ -6112,7 +6188,7 @@ function CanvasSettingsBody({ onDone }) {
6112
6188
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6113
6189
  type: "button",
6114
6190
  onClick: () => setMode("scale"),
6115
- className: require_format.cn("rounded-md border px-2.5 py-2 text-left text-[11px] transition-colors", mode === "scale" ? "border-primary bg-primary/10" : "text-muted-foreground hover:border-primary/50"),
6191
+ className: require_notify.cn("rounded-md border px-2.5 py-2 text-left text-[11px] transition-colors", mode === "scale" ? "border-primary bg-primary/10" : "text-muted-foreground hover:border-primary/50"),
6116
6192
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6117
6193
  className: "block font-medium text-foreground",
6118
6194
  children: "随画布缩放"
@@ -6123,7 +6199,7 @@ function CanvasSettingsBody({ onDone }) {
6123
6199
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6124
6200
  type: "button",
6125
6201
  onClick: () => setMode("keep"),
6126
- className: require_format.cn("rounded-md border px-2.5 py-2 text-left text-[11px] transition-colors", mode === "keep" ? "border-primary bg-primary/10" : "text-muted-foreground hover:border-primary/50"),
6202
+ className: require_notify.cn("rounded-md border px-2.5 py-2 text-left text-[11px] transition-colors", mode === "keep" ? "border-primary bg-primary/10" : "text-muted-foreground hover:border-primary/50"),
6127
6203
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6128
6204
  className: "block font-medium text-foreground",
6129
6205
  children: "保持原位置"
@@ -6185,16 +6261,23 @@ function useAddToTimeline() {
6185
6261
  const assets = (0, _cut_workbench_react.useEditor)((s) => s.assets);
6186
6262
  const playheadUs = (0, _cut_workbench_react.useEditor)((s) => s.playheadUs);
6187
6263
  const addClip = (0, _cut_workbench_react.useEditor)((s) => s.addClip);
6264
+ const { bridge } = require_notify.useWorkbench();
6188
6265
  return (0, react.useCallback)((assetId) => {
6189
6266
  const asset = assets.find((a) => a.id === assetId);
6190
6267
  if (!asset) {
6191
- sonner.toast.error("素材不存在,可能已被删除");
6268
+ require_notify.notify(bridge, {
6269
+ level: "error",
6270
+ title: "素材不存在,可能已被删除"
6271
+ });
6192
6272
  return;
6193
6273
  }
6194
6274
  const kindIsAudio = asset.kind === "audio";
6195
6275
  const track = project.tracks.find((t) => t.kind === (kindIsAudio ? "audio" : "visual")) ?? project.tracks[0];
6196
6276
  if (!track) {
6197
- sonner.toast.error("没有可用轨道");
6277
+ require_notify.notify(bridge, {
6278
+ level: "error",
6279
+ title: "没有可用轨道"
6280
+ });
6198
6281
  return;
6199
6282
  }
6200
6283
  const trackEnd = (0, _cut_workbench_core.getTrackEnd)(project, track.id);
@@ -6227,6 +6310,7 @@ function useAddToTimeline() {
6227
6310
  }, [
6228
6311
  addClip,
6229
6312
  assets,
6313
+ bridge,
6230
6314
  playheadUs,
6231
6315
  project
6232
6316
  ]);
@@ -6280,7 +6364,7 @@ var FRAME_US = 1e6 / 30;
6280
6364
  */
6281
6365
  function useKeyboardShortcuts() {
6282
6366
  const editor = (0, _cut_workbench_react.useCutEditor)();
6283
- const uiApi = useUiStoreApi();
6367
+ const uiApi = require_notify.useUiStoreApi();
6284
6368
  const handler = (0, react.useCallback)((event) => {
6285
6369
  const target = event.target;
6286
6370
  const isEditable = target != null && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable || target.getAttribute("role") === "textbox" || target.closest("[role=menu]") != null);
@@ -6460,17 +6544,17 @@ var LabPage = (0, react.lazy)(() => import("@cut-workbench/workbench/lab").then(
6460
6544
  function CutWorkbench({ slots, toolbar: toolbarOptions, className, onSaveProject, onOpenProject, enforceCapability = true }) {
6461
6545
  const editor = (0, _cut_workbench_react.useCutEditor)();
6462
6546
  const capability = useCapabilityInfo();
6463
- const leftWidth = useUi((s) => s.leftWidth);
6464
- const rightWidth = useUi((s) => s.rightWidth);
6465
- const timelineHeight = useUi((s) => s.timelineHeight);
6466
- const setLeftWidth = useUi((s) => s.setLeftWidth);
6467
- const setRightWidth = useUi((s) => s.setRightWidth);
6468
- const setTimelineHeight = useUi((s) => s.setTimelineHeight);
6469
- const openDialog = useUi((s) => s.openDialog);
6470
- const closeDialog = useUi((s) => s.closeDialog);
6471
- const dialog = useUi((s) => s.dialog);
6472
- const { config, theme } = useWorkbench();
6473
- const route = useUi((s) => s.route);
6547
+ const leftWidth = require_notify.useUi((s) => s.leftWidth);
6548
+ const rightWidth = require_notify.useUi((s) => s.rightWidth);
6549
+ const timelineHeight = require_notify.useUi((s) => s.timelineHeight);
6550
+ const setLeftWidth = require_notify.useUi((s) => s.setLeftWidth);
6551
+ const setRightWidth = require_notify.useUi((s) => s.setRightWidth);
6552
+ const setTimelineHeight = require_notify.useUi((s) => s.setTimelineHeight);
6553
+ const openDialog = require_notify.useUi((s) => s.openDialog);
6554
+ const closeDialog = require_notify.useUi((s) => s.closeDialog);
6555
+ const dialog = require_notify.useUi((s) => s.dialog);
6556
+ const { config, theme } = require_notify.useWorkbench();
6557
+ const route = require_notify.useUi((s) => s.route);
6474
6558
  const addToTimeline = useAddToTimeline();
6475
6559
  const removeAsset = (0, _cut_workbench_react.useEditor)((s) => s.removeAsset);
6476
6560
  const [busy, setBusy] = (0, react.useState)(null);
@@ -7517,11 +7601,15 @@ function CutWorkbenchProvider({ children, bridge, theme = "dark", tokens, config
7517
7601
  (0, _cut_workbench_react.useCutEditor)();
7518
7602
  const [ui] = (0, react.useState)(() => createUiStore());
7519
7603
  const resolvedBridge = (0, react.useMemo)(() => bridge ?? createDefaultBridge(), [bridge]);
7604
+ (0, react.useEffect)(() => {
7605
+ require_notify.setDefaultBridge(resolvedBridge);
7606
+ return () => require_notify.setDefaultBridge(null);
7607
+ }, [resolvedBridge]);
7520
7608
  const mergedConfig = (0, react.useMemo)(() => ({
7521
- ...DEFAULT_CONFIG,
7609
+ ...require_notify.DEFAULT_CONFIG,
7522
7610
  ...config,
7523
7611
  features: {
7524
- ...DEFAULT_CONFIG.features,
7612
+ ...require_notify.DEFAULT_CONFIG.features,
7525
7613
  ...config?.features ?? {}
7526
7614
  }
7527
7615
  }), [config]);
@@ -7550,7 +7638,7 @@ function CutWorkbenchProvider({ children, bridge, theme = "dark", tokens, config
7550
7638
  resolvedTheme,
7551
7639
  portalContainer
7552
7640
  ]);
7553
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkbenchContext.Provider, {
7641
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_notify.WorkbenchContext.Provider, {
7554
7642
  value,
7555
7643
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PortalContainerProvider, {
7556
7644
  container: portalContainer,
@@ -7803,8 +7891,8 @@ exports.ClipBlock = ClipBlock;
7803
7891
  exports.ClipContextMenu = ClipContextMenu;
7804
7892
  exports.CutWorkbench = CutWorkbench;
7805
7893
  exports.CutWorkbenchProvider = CutWorkbenchProvider;
7806
- exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
7807
- exports.DEFAULT_FEATURES = DEFAULT_FEATURES;
7894
+ exports.DEFAULT_CONFIG = require_notify.DEFAULT_CONFIG;
7895
+ exports.DEFAULT_FEATURES = require_notify.DEFAULT_FEATURES;
7808
7896
  exports.DropZone = DropZone;
7809
7897
  exports.EmptyState = EmptyState;
7810
7898
  exports.ExportDialog = ExportDialog;
@@ -7852,8 +7940,8 @@ exports.useExport = useExport;
7852
7940
  exports.useHostDarkClass = useHostDarkClass;
7853
7941
  exports.useMediaImport = useMediaImport;
7854
7942
  exports.usePortalContainer = usePortalContainer;
7855
- exports.useUi = useUi;
7856
- exports.useUiStoreApi = useUiStoreApi;
7857
- exports.useWorkbench = useWorkbench;
7943
+ exports.useUi = require_notify.useUi;
7944
+ exports.useUiStoreApi = require_notify.useUiStoreApi;
7945
+ exports.useWorkbench = require_notify.useWorkbench;
7858
7946
 
7859
7947
  //# sourceMappingURL=index.cjs.map