editor-gui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Editor GUI Components

编辑器GUI组件

Build bespoke video editing interfaces by composing discrete GUI components. Each component is a self-contained building block that connects to your composition via targets.
通过组合独立的GUI组件构建定制化视频编辑界面。每个组件都是一个独立的构建块,可通过target属性与你的合成内容关联。

Core Principle

核心原则

GUI components are lego bricks. They don't contain content—they provide controls and views for content defined by Elements (EFTimegroup, EFVideo, EFText, etc.). Connect them using
target
attributes.
GUI组件就像乐高积木。它们不包含内容——而是为Elements(EFTimegroup、EFVideo、EFText等)定义的内容提供控件和视图。使用
target
属性进行关联。

Quick Start: Minimal Player

快速开始:极简播放器

html
<ef-timegroup id="my-video" mode="fixed" duration="5s">
  <ef-video src="/video.mp4" duration="5s"></ef-video>
</ef-timegroup>

<ef-controls target="my-video">
  <ef-toggle-play></ef-toggle-play>
  <ef-scrubber></ef-scrubber>
  <ef-time-display></ef-time-display>
</ef-controls>
html
<ef-timegroup id="my-video" mode="fixed" duration="5s">
  <ef-video src="/video.mp4" duration="5s"></ef-video>
</ef-timegroup>

<ef-controls target="my-video">
  <ef-toggle-play></ef-toggle-play>
  <ef-scrubber></ef-scrubber>
  <ef-time-display></ef-time-display>
</ef-controls>

Component Reference

组件参考

Preview & Canvas

预览与画布

html
<!-- Basic preview (renders composition at current time) -->
<ef-preview target="my-canvas"></ef-preview>

<!-- Canvas with pan/zoom container -->
<ef-pan-zoom>
  <ef-canvas id="my-canvas">
    <ef-timegroup>...</ef-timegroup>
  </ef-canvas>
</ef-pan-zoom>

<!-- Visual transform handles for selected elements -->
<ef-transform-handles></ef-transform-handles>
html
<!-- 基础预览(渲染当前时间点的合成内容) -->
<ef-preview target="my-canvas"></ef-preview>

<!-- 带平移/缩放容器的画布 -->
<ef-pan-zoom>
  <ef-canvas id="my-canvas">
    <ef-timegroup>...</ef-timegroup>
  </ef-canvas>
</ef-pan-zoom>

<!-- 选中元素的可视化变换手柄 -->
<ef-transform-handles></ef-transform-handles>

Playback Controls

播放控件

html
<!-- Controls container (provides context to children) -->
<ef-controls target="my-timegroup">
  <!-- Individual controls -->
  <ef-play></ef-play>
  <ef-pause></ef-pause>
  <ef-toggle-play></ef-toggle-play>
  <ef-toggle-loop></ef-toggle-loop>
  <ef-scrubber></ef-scrubber>
  <ef-time-display></ef-time-display>
</ef-controls>

<!-- Or use controls standalone with explicit targets -->
<ef-toggle-play target="my-timegroup"></ef-toggle-play>
<ef-scrubber target="my-timegroup"></ef-scrubber>
html
<!-- 控件容器(为子元素提供上下文) -->
<ef-controls target="my-timegroup">
  <!-- 单个控件 -->
  <ef-play></ef-play>
  <ef-pause></ef-pause>
  <ef-toggle-play></ef-toggle-play>
  <ef-toggle-loop></ef-toggle-loop>
  <ef-scrubber></ef-scrubber>
  <ef-time-display></ef-time-display>
</ef-controls>

<!-- 或单独使用控件并指定目标 -->
<ef-toggle-play target="my-timegroup"></ef-toggle-play>
<ef-scrubber target="my-timegroup"></ef-scrubber>

Toggle Play with Custom Slots

自定义插槽的播放/暂停切换按钮

html
<ef-toggle-play>
  <button slot="play">▶ Play</button>
  <button slot="pause">⏸ Pause</button>
</ef-toggle-play>
html
<ef-toggle-play>
  <button slot="play">▶ 播放</button>
  <button slot="pause">⏸ 暂停</button>
</ef-toggle-play>

Timeline

时间轴

html
<!-- Full timeline with tracks, ruler, zoom -->
<ef-timeline 
  target="my-canvas"
  show-ruler
  show-playback-controls
  pixels-per-ms="0.1"
></ef-timeline>

<!-- Just the ruler -->
<ef-timeline-ruler target="my-canvas"></ef-timeline-ruler>

<!-- Trim handles for timeline editing -->
<ef-trim-handles></ef-trim-handles>
html
<!-- 包含轨道、标尺、缩放功能的完整时间轴 -->
<ef-timeline 
  target="my-canvas"
  show-ruler
  show-playback-controls
  pixels-per-ms="0.1"
></ef-timeline>

<!-- 仅显示标尺 -->
<ef-timeline-ruler target="my-canvas"></ef-timeline-ruler>

<!-- 用于时间轴编辑的修剪手柄 -->
<ef-trim-handles></ef-trim-handles>

Filmstrip & Thumbnail Strip

胶片条与缩略图条

html
<!-- Thumbnail strip navigation -->
<ef-filmstrip target="my-timegroup"></ef-filmstrip>

<!-- Thumbnail strip visualization (for tracks) -->
<ef-thumbnail-strip></ef-thumbnail-strip>
ThumbnailStrip standalone props (for use outside timeline):
PropTypeDescription
targetElement
Element | null
Direct ref to an EFVideo or EFTimegroup
target
string
ID-based target (alternative to targetElement)
useIntrinsicDuration
boolean
Use media's intrinsic duration instead of timeline duration
thumbnailHeight
number
Thumbnail height in px (default 24)
html
<!-- 缩略图条导航 -->
<ef-filmstrip target="my-timegroup"></ef-filmstrip>

<!-- (轨道用)缩略图条可视化 -->
<ef-thumbnail-strip></ef-thumbnail-strip>
ThumbnailStrip独立使用属性(用于时间轴外场景):
属性类型描述
targetElement
Element | null
EFVideo或EFTimegroup的直接引用
target
string
基于ID的目标(targetElement的替代方案)
useIntrinsicDuration
boolean
使用媒体的固有时长而非时间轴时长
thumbnailHeight
number
缩略图高度(单位:px,默认24)

Trim

修剪功能

TrimHandles in standalone mode overlays on any container (e.g. a ThumbnailStrip):
html
<ef-trim-handles
  value='{"startMs": 0, "endMs": 0}'
  intrinsic-duration-ms="10000"
  seek-target="my-video"
></ef-trim-handles>
PropTypeDescription
value
TrimValue
{ startMs, endMs }
— ms trimmed from each end
intrinsicDurationMs
number
Total media duration in ms
seekTarget
string
ID of element to seek during drag
showOverlays
boolean
Show dimmed overlay on trimmed regions (default true)
Slots:
handle-start
,
handle-end
— custom handle content (e.g. SVG icons).
CSS custom properties:
css
--trim-handle-width: 16px;
--trim-handle-color: gold;
--trim-handle-active-color: gold;
--trim-handle-border-radius-start: 4px 0 0 4px;
--trim-handle-border-radius-end: 0 4px 4px 0;
--trim-overlay-color: rgba(0, 0, 0, 0.7);
--trim-selected-border-color: gold;
--trim-selected-border-width: 3px;
Events:
onTrimChange
— fires
CustomEvent<TrimChangeDetail>
where
TrimChangeDetail = { elementId, type, value: TrimValue }
.
独立模式的TrimHandles可覆盖在任意容器上(例如ThumbnailStrip):
html
<ef-trim-handles
  value='{"startMs": 0, "endMs": 0}'
  intrinsic-duration-ms="10000"
  seek-target="my-video"
></ef-trim-handles>
属性类型描述
value
TrimValue
{ startMs, endMs }
—— 从两端修剪的时长(单位:ms)
intrinsicDurationMs
number
媒体总时长(单位:ms)
seekTarget
string
拖拽期间跳转的元素ID
showOverlays
boolean
显示修剪区域的暗化遮罩(默认true)
插槽
handle-start
handle-end
—— 自定义手柄内容(例如SVG图标)。
CSS自定义属性
css
--trim-handle-width: 16px;
--trim-handle-color: gold;
--trim-handle-active-color: gold;
--trim-handle-border-radius-start: 4px 0 0 4px;
--trim-handle-border-radius-end: 0 4px 4px 0;
--trim-overlay-color: rgba(0, 0, 0, 0.7);
--trim-selected-border-color: gold;
--trim-selected-border-width: 3px;
事件
onTrimChange
—— 触发
CustomEvent<TrimChangeDetail>
,其中
TrimChangeDetail = { elementId, type, value: TrimValue }

Hierarchy Panel

层级面板

html
<!-- Element tree panel -->
<ef-hierarchy 
  target="my-canvas" 
  show-header 
  header="LAYERS"
></ef-hierarchy>
html
<!-- 元素树面板 -->
<ef-hierarchy 
  target="my-canvas" 
  show-header 
  header="LAYERS"
></ef-hierarchy>

Workbench (Full Editor Shell)

工作台(完整编辑器框架)

html
<!-- Complete editing environment with slots -->
<ef-workbench>
  <ef-hierarchy slot="hierarchy" target="my-canvas"></ef-hierarchy>
  <ef-pan-zoom slot="canvas">
    <ef-canvas id="my-canvas">...</ef-canvas>
  </ef-pan-zoom>
  <ef-timeline slot="timeline" target="my-canvas"></ef-timeline>
</ef-workbench>

<!-- Or use shorthand on timegroup -->
<ef-timegroup workbench mode="fixed" duration="10s">
  ...
</ef-timegroup>
html
<!-- 带插槽的完整编辑环境 -->
<ef-workbench>
  <ef-hierarchy slot="hierarchy" target="my-canvas"></ef-hierarchy>
  <ef-pan-zoom slot="canvas">
    <ef-canvas id="my-canvas">...</ef-canvas>
  </ef-pan-zoom>
  <ef-timeline slot="timeline" target="my-canvas"></ef-timeline>
</ef-workbench>

<!-- 或在timegroup上使用简写形式 -->
<ef-timegroup workbench mode="fixed" duration="10s">
  ...
</ef-timegroup>

React Wrappers

React封装

tsx
import { 
  Preview, 
  Timeline, 
  Controls,
  Scrubber, 
  TogglePlay, 
  TimeDisplay,
  Filmstrip,
  ThumbnailStrip,
  TrimHandles,
  Hierarchy,
  Workbench,
  Video,
  useMediaInfo,
  type TrimValue,
} from '@editframe/react';
import type { EFVideo } from '@editframe/elements';

function SimplePlayer({ timegroupId }) {
  return (
    <Controls target={timegroupId}>
      <TogglePlay />
      <Scrubber />
      <TimeDisplay />
    </Controls>
  );
}
tsx
import { 
  Preview, 
  Timeline, 
  Controls,
  Scrubber, 
  TogglePlay, 
  TimeDisplay,
  Filmstrip,
  ThumbnailStrip,
  TrimHandles,
  Hierarchy,
  Workbench,
  Video,
  useMediaInfo,
  type TrimValue,
} from '@editframe/react';
import type { EFVideo } from '@editframe/elements';

function SimplePlayer({ timegroupId }) {
  return (
    <Controls target={timegroupId}>
      <TogglePlay />
      <Scrubber />
      <TimeDisplay />
    </Controls>
  );
}

Hooks

Hooks

useMediaInfo

useMediaInfo

Returns intrinsic duration and loading state for a media element ref.
tsx
const videoRef = useRef<EFVideo>(null);
const { intrinsicDurationMs, loading } = useMediaInfo(videoRef);
返回媒体元素引用的固有时长和加载状态。
tsx
const videoRef = useRef<EFVideo>(null);
const { intrinsicDurationMs, loading } = useMediaInfo(videoRef);

Video Trim Props

视频修剪属性

Video (and all temporal elements) support trim props that clip playback:
tsx
<Video
  src="/video.mp4"
  trimStartMs={2000}  // trim 2s from start
  trimEndMs={1500}    // trim 1.5s from end
/>
trimStartMs
and
trimEndMs
are in milliseconds, trimmed from each end of the intrinsic duration.
Video(及所有时间相关元素)支持修剪属性以裁剪播放内容:
tsx
<Video
  src="/video.mp4"
  trimStartMs={2000}  // 从开头修剪2秒
  trimEndMs={1500}    // 从结尾修剪1.5秒
/>
trimStartMs
trimEndMs
单位为毫秒,从媒体固有时长的两端进行修剪。

Composition Patterns

合成模式

Pattern 1: Minimal Preview + Controls

模式1:极简预览+控件

tsx
import { Timegroup, Video, Controls, TogglePlay, Scrubber, TimeDisplay } from '@editframe/react';

export function VideoPlayer({ src }) {
  return (
    <div className="flex flex-col gap-2">
      <Timegroup id="player" mode="fixed" duration="10s">
        <Video src={src} duration="10s" style={{ width: '100%' }} />
      </Timegroup>
      
      <Controls target="player" className="flex items-center gap-2">
        <TogglePlay />
        <Scrubber className="flex-1" />
        <TimeDisplay />
      </Controls>
    </div>
  );
}
tsx
import { Timegroup, Video, Controls, TogglePlay, Scrubber, TimeDisplay } from '@editframe/react';

export function VideoPlayer({ src }) {
  return (
    <div className="flex flex-col gap-2">
      <Timegroup id="player" mode="fixed" duration="10s">
        <Video src={src} duration="10s" style={{ width: '100%' }} />
      </Timegroup>
      
      <Controls target="player" className="flex items-center gap-2">
        <TogglePlay />
        <Scrubber className="flex-1" />
        <TimeDisplay />
      </Controls>
    </div>
  );
}

Pattern 2: Timeline-Only Editor

模式2:仅时间轴编辑器

tsx
import { Canvas, Timegroup, Video, Timeline } from '@editframe/react';

export function TimelineEditor({ clips }) {
  return (
    <div className="flex flex-col h-screen">
      <Canvas id="editor" className="flex-1 bg-black">
        <Timegroup id="root" mode="sequence">
          {clips.map(clip => (
            <Video key={clip.id} src={clip.src} duration={clip.duration} />
          ))}
        </Timegroup>
      </Canvas>
      
      <Timeline 
        target="editor" 
        showRuler 
        showPlaybackControls 
        className="h-48 border-t"
      />
    </div>
  );
}
tsx
import { Canvas, Timegroup, Video, Timeline } from '@editframe/react';

export function TimelineEditor({ clips }) {
  return (
    <div className="flex flex-col h-screen">
      <Canvas id="editor" className="flex-1 bg-black">
        <Timegroup id="root" mode="sequence">
          {clips.map(clip => (
            <Video key={clip.id} src={clip.src} duration={clip.duration} />
          ))}
        </Timegroup>
      </Canvas>
      
      <Timeline 
        target="editor" 
        showRuler 
        showPlaybackControls 
        className="h-48 border-t"
      />
    </div>
  );
}

Pattern 3: Filmstrip Navigator

模式3:胶片条导航器

tsx
import { Timegroup, Video, Filmstrip, Controls, TogglePlay } from '@editframe/react';

export function FilmstripViewer({ src }) {
  return (
    <div className="flex flex-col gap-4">
      <Timegroup id="viewer" mode="fixed" duration="30s">
        <Video src={src} duration="30s" style={{ width: '100%' }} />
      </Timegroup>
      
      <Filmstrip target="viewer" className="h-16" />
      
      <Controls target="viewer" className="flex justify-center">
        <TogglePlay />
      </Controls>
    </div>
  );
}
tsx
import { Timegroup, Video, Filmstrip, Controls, TogglePlay } from '@editframe/react';

export function FilmstripViewer({ src }) {
  return (
    <div className="flex flex-col gap-4">
      <Timegroup id="viewer" mode="fixed" duration="30s">
        <Video src={src} duration="30s" style={{ width: '100%' }} />
      </Timegroup>
      
      <Filmstrip target="viewer" className="h-16" />
      
      <Controls target="viewer" className="flex justify-center">
        <TogglePlay />
      </Controls>
    </div>
  );
}

Pattern 4: Trim Tool

模式4:修剪工具

tsx
import { useId, useState, useRef } from "react";
import {
  Video,
  ThumbnailStrip,
  TrimHandles,
  TogglePlay,
  useMediaInfo,
  type TrimValue,
} from "@editframe/react";
import type { EFVideo } from "@editframe/elements";

export function TrimTool({ src }: { src: string }) {
  const videoId = useId();
  const videoRef = useRef<EFVideo>(null);
  const { intrinsicDurationMs } = useMediaInfo(videoRef);
  const [trim, setTrim] = useState<TrimValue>({ startMs: 0, endMs: 0 });
  const totalDuration = intrinsicDurationMs ?? 0;

  return (
    <div>
      <Video
        id={videoId}
        ref={videoRef}
        src={src}
        loop
        trimStartMs={trim.startMs}
        trimEndMs={trim.endMs}
        className="aspect-video w-full object-contain"
      />

      <div className="relative" style={{ height: 64 }}>
        <ThumbnailStrip
          targetElement={videoRef.current}
          useIntrinsicDuration
          thumbnailHeight={64}
          className="pointer-events-none absolute inset-0"
        />
        <TrimHandles
          value={trim}
          intrinsicDurationMs={totalDuration}
          seekTarget={videoId}
          onTrimChange={(e) => setTrim(e.detail.value)}
          className="absolute inset-0"
        >
          <span slot="handle-start"></span>
          <span slot="handle-end"></span>
        </TrimHandles>
      </div>

      <TogglePlay target={videoId}>
        <button slot="play"></button>
        <button slot="pause"></button>
      </TogglePlay>
    </div>
  );
}
tsx
import { useId, useState, useRef } from "react";
import {
  Video,
  ThumbnailStrip,
  TrimHandles,
  TogglePlay,
  useMediaInfo,
  type TrimValue,
} from "@editframe/react";
import type { EFVideo } from "@editframe/elements";

export function TrimTool({ src }: { src: string }) {
  const videoId = useId();
  const videoRef = useRef<EFVideo>(null);
  const { intrinsicDurationMs } = useMediaInfo(videoRef);
  const [trim, setTrim] = useState<TrimValue>({ startMs: 0, endMs: 0 });
  const totalDuration = intrinsicDurationMs ?? 0;

  return (
    <div>
      <Video
        id={videoId}
        ref={videoRef}
        src={src}
        loop
        trimStartMs={trim.startMs}
        trimEndMs={trim.endMs}
        className="aspect-video w-full object-contain"
      />

      <div className="relative" style={{ height: 64 }}>
        <ThumbnailStrip
          targetElement={videoRef.current}
          useIntrinsicDuration
          thumbnailHeight={64}
          className="pointer-events-none absolute inset-0"
        />
        <TrimHandles
          value={trim}
          intrinsicDurationMs={totalDuration}
          seekTarget={videoId}
          onTrimChange={(e) => setTrim(e.detail.value)}
          className="absolute inset-0"
        >
          <span slot="handle-start"></span>
          <span slot="handle-end"></span>
        </TrimHandles>
      </div>

      <TogglePlay target={videoId}>
        <button slot="play"></button>
        <button slot="pause"></button>
      </TogglePlay>
    </div>
  );
}

Pattern 5: Multi-Layer Editor with Hierarchy

模式5:带层级面板的多图层编辑器

tsx
import { Workbench, Hierarchy, PanZoom, Canvas, Timegroup, Timeline } from '@editframe/react';

export function LayeredEditor({ children }) {
  return (
    <Workbench className="h-screen">
      <Hierarchy slot="hierarchy" target="main-canvas" showHeader header="LAYERS" />
      
      <PanZoom slot="canvas">
        <Canvas id="main-canvas">
          <Timegroup id="root" mode="contain" duration="10s">
            {children}
          </Timegroup>
        </Canvas>
      </PanZoom>
      
      <Timeline slot="timeline" target="main-canvas" showRuler showPlaybackControls />
    </Workbench>
  );
}
tsx
import { Workbench, Hierarchy, PanZoom, Canvas, Timegroup, Timeline } from '@editframe/react';

export function LayeredEditor({ children }) {
  return (
    <Workbench className="h-screen">
      <Hierarchy slot="hierarchy" target="main-canvas" showHeader header="LAYERS" />
      
      <PanZoom slot="canvas">
        <Canvas id="main-canvas">
          <Timegroup id="root" mode="contain" duration="10s">
            {children}
          </Timegroup>
        </Canvas>
      </PanZoom>
      
      <Timeline slot="timeline" target="main-canvas" showRuler showPlaybackControls />
    </Workbench>
  );
}

Target Linking

目标关联

GUI components connect to content using IDs:
html
<!-- Content with ID -->
<ef-canvas id="my-canvas">
  <ef-timegroup id="my-root">...</ef-timegroup>
</ef-canvas>

<!-- GUI components target by ID -->
<ef-timeline target="my-canvas"></ef-timeline>
<ef-hierarchy target="my-canvas"></ef-hierarchy>
<ef-controls target="my-root"></ef-controls>
<ef-filmstrip target="my-root"></ef-filmstrip>
ComponentTarget TypePurpose
TimelineCanvas IDShows all tracks from canvas content
HierarchyCanvas IDShows element tree from canvas
ControlsTimegroup IDPlay/pause/seek the timegroup
FilmstripTimegroup IDThumbnail navigation for timegroup
PreviewCanvas IDRenders canvas at current time
GUI组件通过ID与内容关联:
html
<!-- 带ID的内容 -->
<ef-canvas id="my-canvas">
  <ef-timegroup id="my-root">...</ef-timegroup>
</ef-canvas>

<!-- 按ID指定目标的GUI组件 -->
<ef-timeline target="my-canvas"></ef-timeline>
<ef-hierarchy target="my-canvas"></ef-hierarchy>
<ef-controls target="my-root"></ef-controls>
<ef-filmstrip target="my-root"></ef-filmstrip>
组件目标类型用途
TimelineCanvas ID显示画布内容中的所有轨道
HierarchyCanvas ID显示画布的元素树
ControlsTimegroup ID播放/暂停/跳转时间轴
FilmstripTimegroup ID时间轴的缩略图导航
PreviewCanvas ID渲染当前时间点的画布内容

Styling

样式定制

All components accept
className
for styling:
tsx
<Scrubber className="h-2 rounded-full" />
<TogglePlay className="w-10 h-10 bg-white rounded-full" />
<Timeline className="h-48 bg-gray-900" />
Components use CSS custom properties for theming:
css
ef-timeline {
  --ef-timeline-bg: #1a1a1a;
  --ef-timeline-track-bg: #2a2a2a;
  --ef-timeline-playhead: #ff0000;
}

ef-scrubber {
  --ef-scrubber-track: #333;
  --ef-scrubber-fill: #fff;
  --ef-scrubber-thumb: #fff;
}
所有组件都支持
className
用于样式定制:
tsx
<Scrubber className="h-2 rounded-full" />
<TogglePlay className="w-10 h-10 bg-white rounded-full" />
<Timeline className="h-48 bg-gray-900" />
组件支持通过CSS自定义属性进行主题定制:
css
ef-timeline {
  --ef-timeline-bg: #1a1a1a;
  --ef-timeline-track-bg: #2a2a2a;
  --ef-timeline-playhead: #ff0000;
}

ef-scrubber {
  --ef-scrubber-track: #333;
  --ef-scrubber-fill: #fff;
  --ef-scrubber-thumb: #fff;
}

File Locations

文件位置

elements/packages/elements/src/gui/
├── EFConfiguration.ts    # Root config wrapper
├── EFWorkbench.ts        # Full editor shell
├── EFControls.ts         # Control container
├── EFPlay.ts             # Play button
├── EFPause.ts            # Pause button  
├── EFTogglePlay.ts       # Play/pause toggle
├── EFToggleLoop.ts       # Loop toggle
├── EFScrubber.ts         # Time scrubber
├── EFTimeDisplay.ts      # Time display
├── EFFilmstrip.ts        # Thumbnail strip
├── EFPreview.ts          # Preview renderer
├── hierarchy/
│   └── EFHierarchy.ts    # Layer panel
└── timeline/
    ├── EFTimeline.ts     # Main timeline
    ├── EFTimelineRuler.ts # Time ruler
    └── EFTrimHandles.ts  # Trim handles

elements/packages/react/src/gui/
├── Workbench.ts          # React wrapper
├── Timeline.ts           # React wrapper
├── Controls.ts           # React wrapper
├── Scrubber.ts           # React wrapper
└── ...                   # All components have React wrappers
elements/packages/elements/src/gui/
├── EFConfiguration.ts    # 根配置包装器
├── EFWorkbench.ts        # 完整编辑器框架
├── EFControls.ts         # 控件容器
├── EFPlay.ts             # 播放按钮
├── EFPause.ts            # 暂停按钮  
├── EFTogglePlay.ts       # 播放/暂停切换按钮
├── EFToggleLoop.ts       # 循环切换按钮
├── EFScrubber.ts         # 时间进度条
├── EFTimeDisplay.ts      # 时间显示组件
├── EFFilmstrip.ts        # 胶片条组件
├── EFPreview.ts          # 预览渲染组件
├── hierarchy/
│   └── EFHierarchy.ts    # 图层面板
└── timeline/
    ├── EFTimeline.ts     # 主时间轴
    ├── EFTimelineRuler.ts # 时间标尺
    └── EFTrimHandles.ts  # 修剪手柄

elements/packages/react/src/gui/
├── Workbench.ts          # React封装
├── Timeline.ts           # React封装
├── Controls.ts           # React封装
├── Scrubber.ts           # React封装
└── ...                   # 所有组件均有React封装

Agent Panel & ef-edit Event System

Agent面板与ef-edit事件系统

EFWorkbench includes a built-in right-column Agent Sync panel (
ef-agent-panel
) that accumulates GUI edits and generates a copyable prompt for coding agents.
EFWorkbench包含内置的右侧栏Agent Sync面板
ef-agent-panel
),可累积GUI编辑操作并生成可供复制的提示语,用于AI编码助手。

How it works

工作原理

  1. When a user drags, resizes, or rotates an element in EFCanvas, the canvas dispatches an
    ef-edit
    CustomEvent with
    bubbles: true, composed: true
    .
  2. EFWorkbench listens for
    ef-edit
    on itself (event bubbles up), then calls
    agentPanel.addEdit(event.detail)
    .
  3. The panel accumulates edits, renders a list, and exposes a "Copy Prompt" button.
  1. 当用户在EFCanvas中拖拽、调整大小或旋转元素时,画布会触发
    ef-edit
    自定义事件,设置
    bubbles: true, composed: true
  2. EFWorkbench监听自身上的
    ef-edit
    事件(事件会冒泡),然后调用
    agentPanel.addEdit(event.detail)
  3. 面板会累积编辑操作,渲染列表,并提供“复制提示语”按钮。

ef-edit event payload (EditEvent)

ef-edit事件负载(EditEvent)

ts
interface EditEvent {
  operation: ElementMovedOperation | ElementResizedOperation | ElementRotatedOperation;
  description: string;   // human-readable sentence
  selector: string;      // CSS path stopping at infrastructure tags
  elementHtml: string;   // cleaned outerHTML (runtime attrs stripped)
  timestamp: number;
}
ts
interface EditEvent {
  operation: ElementMovedOperation | ElementResizedOperation | ElementRotatedOperation;
  description: string;   # 人类可读的描述语句
  selector: string;      # 到基础设施标签为止的CSS路径
  elementHtml: string;   # 清理后的outerHTML(移除运行时属性)
  timestamp: number;
}

Dispatching ef-edit

触发ef-edit事件

ts
import { createEditCustomEvent } from './editEvents.js';
element.dispatchEvent(createEditCustomEvent(editEvent));
ts
import { createEditCustomEvent } from './editEvents.js';
element.dispatchEvent(createEditCustomEvent(editEvent));

Listening from outside

外部监听

ts
// Bubbles + composed — listen on workbench or any ancestor
workbench.addEventListener('ef-edit', (e: CustomEvent<EditEvent>) => {
  console.log(e.detail.description, e.detail.selector);
});
ts
// 事件会冒泡且可跨DOM树——在工作台或任何祖先元素上监听
workbench.addEventListener('ef-edit', (e: CustomEvent<EditEvent>) => {
  console.log(e.detail.description, e.detail.selector);
});

Selector path rules

选择器路径规则

  • buildSelectorPath()
    walks up the DOM stopping at INFRASTRUCTURE_TAGS (
    ef-canvas
    ,
    ef-workbench
    ,
    ef-pan-zoom
    , etc.)
  • Auto-generated IDs (containing 8+ digits) are omitted; nth-of-type is used instead
  • getElementHtml()
    strips
    style
    ,
    data-element-id
    ,
    data-selected
    , and auto-generated ids from outerHTML
  • buildSelectorPath()
    遍历DOM树,遇到INFRASTRUCTURE_TAGS(
    ef-canvas
    ef-workbench
    ef-pan-zoom
    等)时停止
  • 自动生成的ID(包含8位以上数字)会被省略;改用nth-of-type
  • getElementHtml()
    会从outerHTML中移除
    style
    data-element-id
    data-selected
    和自动生成的ID

Agent panel API

Agent面板API

ts
agentPanel.addEdit(editEvent: EditEvent): void   // called by EFWorkbench
agentPanel.clearEdits(): void
ts
agentPanel.addEdit(editEvent: EditEvent): void   // 由EFWorkbench调用
agentPanel.clearEdits(): void

File locations

文件位置

elements/packages/elements/src/gui/
├── EFAgentPanel.ts    # Lit component for the panel
├── editEvents.ts      # EditEvent types, buildSelectorPath, buildAgentPrompt
elements/packages/elements/src/gui/
├── EFAgentPanel.ts    # 面板的Lit组件
├── editEvents.ts      # EditEvent类型、buildSelectorPath、buildAgentPrompt

Sandboxes

沙箱环境

Test individual components:
bash
elements/scripts/npm run sandbox
Browse to component sandboxes:
  • src/gui/EFScrubber.sandbox.ts
  • src/gui/EFTimeline.sandbox.ts
  • src/gui/EFFilmstrip.sandbox.ts
  • src/gui/EFControls.sandbox.ts
测试单个组件:
bash
elements/scripts/npm run sandbox
浏览组件沙箱:
  • src/gui/EFScrubber.sandbox.ts
  • src/gui/EFTimeline.sandbox.ts
  • src/gui/EFFilmstrip.sandbox.ts
  • src/gui/EFControls.sandbox.ts