Loading...
Loading...
Build video editing interfaces using Editframe's GUI web components. Assemble timeline, scrubber, filmstrip, preview, and playback controls like lego bricks. Use when creating video editors, editing tools, or when user mentions timeline, scrubber, preview, playback controls, trim handles, or wants to build editing UIs.
npx skill4agent add editframe/skills editor-guitarget<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><!-- 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><!-- 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><ef-toggle-play>
<button slot="play">▶ Play</button>
<button slot="pause">⏸ Pause</button>
</ef-toggle-play><!-- 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><!-- Thumbnail strip navigation -->
<ef-filmstrip target="my-timegroup"></ef-filmstrip>
<!-- Thumbnail strip visualization (for tracks) -->
<ef-thumbnail-strip></ef-thumbnail-strip>| Prop | Type | Description |
|---|---|---|
| | Direct ref to an EFVideo or EFTimegroup |
| | ID-based target (alternative to targetElement) |
| | Use media's intrinsic duration instead of timeline duration |
| | Thumbnail height in px (default 24) |
<ef-trim-handles
value='{"startMs": 0, "endMs": 0}'
intrinsic-duration-ms="10000"
seek-target="my-video"
></ef-trim-handles>| Prop | Type | Description |
|---|---|---|
| | |
| | Total media duration in ms |
| | ID of element to seek during drag |
| | Show dimmed overlay on trimmed regions (default true) |
handle-starthandle-end--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;onTrimChangeCustomEvent<TrimChangeDetail>TrimChangeDetail = { elementId, type, value: TrimValue }<!-- Element tree panel -->
<ef-hierarchy
target="my-canvas"
show-header
header="LAYERS"
></ef-hierarchy><!-- 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>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>
);
}const videoRef = useRef<EFVideo>(null);
const { intrinsicDurationMs, loading } = useMediaInfo(videoRef);<Video
src="/video.mp4"
trimStartMs={2000} // trim 2s from start
trimEndMs={1500} // trim 1.5s from end
/>trimStartMstrimEndMsimport { 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>
);
}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>
);
}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>
);
}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>
);
}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>
);
}<!-- 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>| Component | Target Type | Purpose |
|---|---|---|
| Timeline | Canvas ID | Shows all tracks from canvas content |
| Hierarchy | Canvas ID | Shows element tree from canvas |
| Controls | Timegroup ID | Play/pause/seek the timegroup |
| Filmstrip | Timegroup ID | Thumbnail navigation for timegroup |
| Preview | Canvas ID | Renders canvas at current time |
className<Scrubber className="h-2 rounded-full" />
<TogglePlay className="w-10 h-10 bg-white rounded-full" />
<Timeline className="h-48 bg-gray-900" />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;
}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 wrappersef-agent-panelef-editbubbles: true, composed: trueef-editagentPanel.addEdit(event.detail)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;
}import { createEditCustomEvent } from './editEvents.js';
element.dispatchEvent(createEditCustomEvent(editEvent));// Bubbles + composed — listen on workbench or any ancestor
workbench.addEventListener('ef-edit', (e: CustomEvent<EditEvent>) => {
console.log(e.detail.description, e.detail.selector);
});buildSelectorPath()ef-canvasef-workbenchef-pan-zoomgetElementHtml()styledata-element-iddata-selectedagentPanel.addEdit(editEvent: EditEvent): void // called by EFWorkbench
agentPanel.clearEdits(): voidelements/packages/elements/src/gui/
├── EFAgentPanel.ts # Lit component for the panel
├── editEvents.ts # EditEvent types, buildSelectorPath, buildAgentPromptelements/scripts/npm run sandboxsrc/gui/EFScrubber.sandbox.tssrc/gui/EFTimeline.sandbox.tssrc/gui/EFFilmstrip.sandbox.tssrc/gui/EFControls.sandbox.ts