@elah/editor 0.4.1 → 0.6.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/README.md +240 -179
- package/dist/editor/AssetPanel/AssetPanel.js +1 -1
- package/dist/editor/EditorProvider.js +25 -11
- package/dist/editor/Preview/MediaTransformOverlay.d.ts +1 -1
- package/dist/editor/Preview/MediaTransformOverlay.js +245 -44
- package/dist/editor/Preview/Preview.js +99 -18
- package/dist/editor/Preview/PreviewLoadingOverlay.d.ts +1 -0
- package/dist/editor/Preview/PreviewLoadingOverlay.js +18 -0
- package/dist/editor/Preview/ShapeOverlay.d.ts +1 -1
- package/dist/editor/Preview/ShapeOverlay.js +83 -37
- package/dist/editor/Preview/StageBorder.js +2 -2
- package/dist/editor/Preview/TextOverlay.d.ts +1 -1
- package/dist/editor/Preview/TextOverlay.js +143 -34
- package/dist/editor/SourcePanel/SourcePanel.js +1 -1
- package/dist/index.d.ts +25 -12
- package/dist/index.js +15 -9
- package/dist/styles.css +1 -1
- package/package.json +4 -4
- package/src/styles/tokens.css +11 -0
package/README.md
CHANGED
|
@@ -1,179 +1,240 @@
|
|
|
1
|
-
# @elah/editor
|
|
2
|
-
|
|
3
|
-
The full Elah video editor SDK for React. Combines the core engine, timeline UI, WebGL2 renderer, media library, and export pipeline into a single package.
|
|
4
|
-
|
|
5
|
-
Ships `EditorProvider`, `Preview` (WebGL2 canvas + interactive transform overlays), `Timeline`, `AssetPanel`, `SourcePanel`, and `ElementsPanel`, and re-exports the public `@elah/core`, `@elah/react`, and `@elah/timeline` API — so most apps only ever import from `@elah/editor`. (Renderer and debug internals are the exception; import those from `@elah/core` directly.) Supports video, image, text, **shape**, and **freehand** clips, **multi-track audio**, and MP4 export.
|
|
6
|
-
|
|
7
|
-
[](https://www.npmjs.com/package/@elah/editor)
|
|
8
|
-
[](https://github.com/elahlabs/elah/blob/main/LICENSE)
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Install
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm install @elah/editor
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Peer dependencies: `react`, `react-dom` >= 18, `lucide-react` >= 0.400.0 (used by the
|
|
20
|
-
bundled `@elah/timeline` UI for clip icons — install it alongside `react`/`react-dom`
|
|
21
|
-
even though nothing in your own code imports it).
|
|
22
|
-
|
|
23
|
-
**Bundle size:** ~
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Quick start
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
import { EditorProvider, Timeline } from '@elah/editor'
|
|
31
|
-
|
|
32
|
-
function App() {
|
|
33
|
-
return (
|
|
34
|
-
<EditorProvider fps={30}>
|
|
35
|
-
<Timeline style={{ height: 300 }} />
|
|
36
|
-
</EditorProvider>
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## Styling
|
|
44
|
-
|
|
45
|
-
Import the compiled stylesheets once. They are plain CSS — your app does **not**
|
|
46
|
-
need Tailwind, and no utility class names leak into your global scope (preflight
|
|
47
|
-
is disabled, so nothing resets your elements):
|
|
48
|
-
|
|
49
|
-
```ts
|
|
50
|
-
import '@elah/timeline/styles.css'
|
|
51
|
-
import '@elah/editor/styles.css'
|
|
52
|
-
import '@elah/editor/styles/tokens.css' // --elah-* dark defaults (standalone)
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
When embedding inside an app that already defines `.elah-root` (mapping `--elah-*`
|
|
56
|
-
onto its own design system), skip `tokens.css`. Re-theme or white-label by
|
|
57
|
-
overriding `--elah-*` variables in your own `.elah-root` scope — see
|
|
58
|
-
[design-tokens.md](https://github.com/elahlabs/elah/blob/main/docs/design-tokens.md).
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## With preview and asset panel
|
|
63
|
-
|
|
64
|
-
```tsx
|
|
65
|
-
import { EditorProvider, Timeline, Preview, AssetPanel, createDefaultDemuxerFactory } from '@elah/editor'
|
|
66
|
-
|
|
67
|
-
const demuxerFactory = createDefaultDemuxerFactory()
|
|
68
|
-
|
|
69
|
-
function App() {
|
|
70
|
-
return (
|
|
71
|
-
<EditorProvider fps={30}>
|
|
72
|
-
<div style={{ display: 'flex', height: '100vh' }}>
|
|
73
|
-
<AssetPanel style={{ width: 220 }} />
|
|
74
|
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
75
|
-
<Preview demuxerFactory={demuxerFactory} style={{ flex: 1 }} />
|
|
76
|
-
<Timeline style={{ height: 300 }} />
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
</EditorProvider>
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## Import media
|
|
87
|
-
|
|
88
|
-
```ts
|
|
89
|
-
import { importFiles, importUrl, importBlob, useMediaLibrary } from '@elah/editor'
|
|
90
|
-
|
|
91
|
-
await importFiles(Array.from(fileList)) // local files
|
|
92
|
-
await importUrl('https://example.com/clip.mp4') // remote URL
|
|
93
|
-
await
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
`
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
1
|
+
# @elah/editor
|
|
2
|
+
|
|
3
|
+
The full Elah video editor SDK for React. Combines the core engine, timeline UI, WebGL2 renderer, media library, and export pipeline into a single package.
|
|
4
|
+
|
|
5
|
+
Ships `EditorProvider`, `Preview` (WebGL2 canvas + interactive transform overlays), `Timeline`, `AssetPanel`, `SourcePanel`, and `ElementsPanel`, and re-exports the public `@elah/core`, `@elah/react`, and `@elah/timeline` API — so most apps only ever import from `@elah/editor`. (Renderer and debug internals are the exception; import those from `@elah/core` directly.) Supports video, image, text, **shape**, and **freehand** clips, **multiple video tracks**, **multi-track audio**, and MP4 export.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@elah/editor)
|
|
8
|
+
[](../../BUNDLE_STRATEGY.md)
|
|
9
|
+
[](https://github.com/elahlabs/elah/blob/main/LICENSE)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @elah/editor
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Peer dependencies: `react`, `react-dom` >= 18, `lucide-react` >= 0.400.0 (used by the
|
|
20
|
+
bundled `@elah/timeline` UI for clip icons — install it alongside `react`/`react-dom`
|
|
21
|
+
even though nothing in your own code imports it).
|
|
22
|
+
|
|
23
|
+
**Bundle size:** ~19 KiB gzipped for the editor layer; an app using the full SDK (`core` + `react` + `timeline` + `editor`) starts at ~64 KiB gzipped with the demuxer injected or omitted, and ~227 KiB if it calls `createDefaultDemuxerFactory()`, which pulls `mediabunny` (159 KiB) into the startup graph. Measured with code splitting — see [BUNDLE_STRATEGY.md](../../BUNDLE_STRATEGY.md).
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { EditorProvider, Timeline } from '@elah/editor'
|
|
31
|
+
|
|
32
|
+
function App() {
|
|
33
|
+
return (
|
|
34
|
+
<EditorProvider fps={30}>
|
|
35
|
+
<Timeline style={{ height: 300 }} />
|
|
36
|
+
</EditorProvider>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Styling
|
|
44
|
+
|
|
45
|
+
Import the compiled stylesheets once. They are plain CSS — your app does **not**
|
|
46
|
+
need Tailwind, and no utility class names leak into your global scope (preflight
|
|
47
|
+
is disabled, so nothing resets your elements):
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import '@elah/timeline/styles.css'
|
|
51
|
+
import '@elah/editor/styles.css'
|
|
52
|
+
import '@elah/editor/styles/tokens.css' // --elah-* dark defaults (standalone)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
When embedding inside an app that already defines `.elah-root` (mapping `--elah-*`
|
|
56
|
+
onto its own design system), skip `tokens.css`. Re-theme or white-label by
|
|
57
|
+
overriding `--elah-*` variables in your own `.elah-root` scope — see
|
|
58
|
+
[design-tokens.md](https://github.com/elahlabs/elah/blob/main/docs/design-tokens.md).
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## With preview and asset panel
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import { EditorProvider, Timeline, Preview, AssetPanel, createDefaultDemuxerFactory } from '@elah/editor'
|
|
66
|
+
|
|
67
|
+
const demuxerFactory = createDefaultDemuxerFactory()
|
|
68
|
+
|
|
69
|
+
function App() {
|
|
70
|
+
return (
|
|
71
|
+
<EditorProvider fps={30}>
|
|
72
|
+
<div style={{ display: 'flex', height: '100vh' }}>
|
|
73
|
+
<AssetPanel style={{ width: 220 }} />
|
|
74
|
+
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
75
|
+
<Preview demuxerFactory={demuxerFactory} style={{ flex: 1 }} />
|
|
76
|
+
<Timeline style={{ height: 300 }} />
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</EditorProvider>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Import media
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { importFiles, importUrl, importBlob, beginImportUrl, useMediaLibrary } from '@elah/editor'
|
|
90
|
+
|
|
91
|
+
await importFiles(Array.from(fileList)) // local files
|
|
92
|
+
await importUrl('https://example.com/clip.mp4') // remote URL (waits for metadata)
|
|
93
|
+
const pending = await beginImportUrl('https://example.com/clip.mp4') // returns at once, status: 'pending'
|
|
94
|
+
await importBlob(recordedBlob, { name: 'take-1.webm' })
|
|
95
|
+
|
|
96
|
+
// Subscribe in React — useMediaLibrary() takes no arguments and returns
|
|
97
|
+
// { assets, getAsset, removeAsset, updateAsset, importFiles, importUrl, importBlob }
|
|
98
|
+
// with assets in insertion order. `useAssets` is an alias for the same hook.
|
|
99
|
+
const { assets, importFiles: addFiles } = useMediaLibrary()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`beginImportUrl` is for "add to timeline" from a remote gallery: place a clip right away
|
|
103
|
+
and let it resize itself once the real duration arrives. Pair it with
|
|
104
|
+
`growClipToAssetDuration` (see [`@elah/timeline`](https://www.npmjs.com/package/@elah/timeline)).
|
|
105
|
+
`determineAssetHasAudio` answers whether a video has an audio track.
|
|
106
|
+
|
|
107
|
+
### Programmatic insertion (no drag)
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { insertMediaAsset } from '@elah/editor'
|
|
111
|
+
|
|
112
|
+
// Place an imported asset onto the timeline — powers tap-to-add on touch.
|
|
113
|
+
// Returns a typed InsertAssetResult ({ ok, kind, trackId, clipIds } | { ok:false, reason }).
|
|
114
|
+
const result = await insertMediaAsset(engine, assetId, { desiredStartFrame: 0 })
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Multiple video tracks
|
|
120
|
+
|
|
121
|
+
`engine.addTrack('video')` always adds a lane. Video tracks composite in track order
|
|
122
|
+
(topmost lane draws on top) and a new one lands directly below the last video track.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Preview overlays, crop and speed
|
|
127
|
+
|
|
128
|
+
`<Preview>` paints the interactive overlays for you. **Text** clips: drag, resize, a rotate knob
|
|
129
|
+
(Shift snaps to 15°) and inline edit. **Video and image** clips (`MediaTransformOverlay`): drag,
|
|
130
|
+
8 resize handles with non-uniform scale (Shift keeps the aspect ratio), and a Resize/Crop toggle
|
|
131
|
+
that edits `Clip.crop`. There is no rotate handle for video or image clips; the box tilts with
|
|
132
|
+
`transform.rotation`, which you set through the engine. Clip speed (`engine.setClipSpeed`,
|
|
133
|
+
0.25–4) and `Clip.cornerRadius` are engine-level fields.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Text templates and frame sequences
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { BUILT_IN_TEXT_TEMPLATES, applyTextTemplate, createFrameSequence, frameSequenceToProject } from '@elah/editor'
|
|
141
|
+
|
|
142
|
+
engine.updateClip(clip.id, clip.trackId, applyTextTemplate(BUILT_IN_TEXT_TEMPLATES[0], clip)) // one undo entry
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
14 built-in looks with layered entry/exit motion (`MotionSpec`). A template's `stagger` and
|
|
146
|
+
`tracking` are data that `applyTextTemplate` ignores, and rotation motion has no effect on shape
|
|
147
|
+
clips. Transitions are `fade`, `slide` and `wipe` (`slide` goes left only for `direction: 'left'`,
|
|
148
|
+
`wipe` ignores direction). Frame sequences, `snapshotMediaLibrary` / `hydrateMediaLibrary` and the
|
|
149
|
+
rest are documented in [`@elah/core`](https://www.npmjs.com/package/@elah/core); everything is
|
|
150
|
+
re-exported from this package.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Save and restore
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import { serializeProject, deserializeProject } from '@elah/editor'
|
|
158
|
+
|
|
159
|
+
localStorage.setItem('project', serializeProject(engine))
|
|
160
|
+
deserializeProject(engine, localStorage.getItem('project')!) // throws, engine untouched, if unreadable
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
For control over the refusal, read first, then load:
|
|
164
|
+
`engine.loadProject(readProjectDocument(JSON.parse(json)))`. `readProjectDocument` throws
|
|
165
|
+
`ProjectDocumentError` (`'unreadable'` or `'unsupported-version'`). Files imported from
|
|
166
|
+
disk are `blob:` URLs that do not survive a reload; after your media library is rebuilt,
|
|
167
|
+
`relinkProjectMedia(project, assets)` re-points clips at it and lists what is `missing`
|
|
168
|
+
(never dropped). Load its result with `{ transport: 'keep', history: 'keep' }` so the
|
|
169
|
+
playhead and undo stack stay put. See [`@elah/core`](https://www.npmjs.com/package/@elah/core).
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Export to MP4
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { exportVideo } from '@elah/editor'
|
|
177
|
+
|
|
178
|
+
const blob = await exportVideo(engine.getProject(), {
|
|
179
|
+
videoBitrate: 8_000_000,
|
|
180
|
+
outputHeight: 1080, // the stage's short edge: 1920×1080 landscape or 1080×1920 portrait
|
|
181
|
+
onProgress: ({ frame, totalFrames }) => {
|
|
182
|
+
console.log(`${Math.round((frame / totalFrames) * 100)}%`)
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Runs in a web worker. Reuses `resolveTimeline` + the GPU renderer's placement math.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Keyboard shortcuts
|
|
192
|
+
|
|
193
|
+
| Key | Action |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `Space` | Play / pause |
|
|
196
|
+
| `S` | Split clip at playhead |
|
|
197
|
+
| `Delete` / `Backspace` | Delete selected clip(s) |
|
|
198
|
+
| `Ctrl/Cmd + C` | Copy |
|
|
199
|
+
| `Ctrl/Cmd + V` | Paste at playhead |
|
|
200
|
+
| `Ctrl/Cmd + Z` | Undo |
|
|
201
|
+
| `Ctrl/Cmd + Shift + Z` / `Ctrl/Cmd + Y` | Redo |
|
|
202
|
+
| `Ctrl/Cmd + scroll` | Zoom |
|
|
203
|
+
| `← / →` | Step one frame |
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Package layers
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
@elah/core — engine, playback, resolver, vanilla stores, media, export (framework-agnostic)
|
|
211
|
+
@elah/react — React bindings: EditorContext, store hooks, audio hooks
|
|
212
|
+
@elah/timeline — React timeline UI components and hooks (consumes @elah/core + @elah/react)
|
|
213
|
+
@elah/editor — EditorProvider, Preview, AssetPanel + re-exports @elah/core, @elah/react, @elah/timeline
|
|
214
|
+
@elah/cli — headless split/trim/build/export/serve on top of @elah/core
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Use `@elah/editor` for the full in-browser experience. Use `@elah/core` directly
|
|
218
|
+
for headless or custom rendering pipelines, or pair it with `@elah/react` if you
|
|
219
|
+
want the hooks without the timeline UI. Use `@elah/cli` for automation,
|
|
220
|
+
AI-generation pipelines, and server-side rendering.
|
|
221
|
+
|
|
222
|
+
**One active project per page.** `@elah/core`'s stores (`tracksStore`,
|
|
223
|
+
`playbackStore`, `selectionStore`, `transitionsStore`, `mediaLibraryStore`, `textStylePresetsStore`, `clipLoadStore`) are
|
|
224
|
+
module-level singletons, and `<EditorProvider>` wires the `TimelineEngine` /
|
|
225
|
+
`PlaybackEngine` it creates into those same shared stores. Mounting a second
|
|
226
|
+
`<EditorProvider>` on the same page (two independent projects at once) will have
|
|
227
|
+
both instances overwrite each other's state — there is currently no per-instance
|
|
228
|
+
scoping. If you need multiple concurrent editors, isolate each in its own
|
|
229
|
+
tab/window/iframe.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Links
|
|
234
|
+
|
|
235
|
+
- [Website](https://www.elah.dev)
|
|
236
|
+
- [GitHub](https://github.com/elahlabs/elah)
|
|
237
|
+
- [Discord](https://discord.gg/qKJXvc4Pwu)
|
|
238
|
+
- [Headless CLI — @elah/cli](https://www.npmjs.com/package/@elah/cli)
|
|
239
|
+
- [License](https://github.com/elahlabs/elah/blob/main/LICENSE)
|
|
240
|
+
- [Commercial licensing](mailto:paul@elah.dev)
|
|
@@ -239,6 +239,6 @@ export function AssetPanel({ style, className, activateOnTap, onAssetActivate, }
|
|
|
239
239
|
color: toast.tone === 'warn' ? 'var(--elah-danger-text, #f5d0a9)' : 'var(--elah-info-text, #c8d8f0)',
|
|
240
240
|
background: toast.tone === 'warn' ? 'var(--elah-danger-bg, #3a2418)' : 'var(--elah-info-bg, #1a2433)',
|
|
241
241
|
border: `1px solid ${toast.tone === 'warn' ? 'var(--elah-danger-border, #7a4a2a)' : 'var(--elah-info-border, #355070)'}`,
|
|
242
|
-
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.35)',
|
|
242
|
+
boxShadow: 'var(--elah-toast-shadow, 0 4px 12px rgba(0, 0, 0, 0.35))',
|
|
243
243
|
}, children: toast.message })), assets.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center min-h-[120px] p-4 text-center text-ed-text-muted text-[11px] border border-dashed border-ed-border rounded-md", children: [_jsx("span", { className: "mb-2 text-2xl opacity-50", children: "\u2193" }), "Drop files here", _jsx("br", {}), "or click Add"] })) : (_jsx("div", { className: "flex flex-col gap-[6px]", children: assets.map((asset) => (_jsx(AssetThumbnail, { asset: asset, onDelete: handleDeleteAsset, onActivate: activationEnabled ? handleAssetActivate : undefined }, asset.id))) }))] })] }));
|
|
244
244
|
}
|
|
@@ -3,17 +3,31 @@ import { useEffect, useMemo } from 'react';
|
|
|
3
3
|
import { TimelineEngine, PlaybackEngine, installTraceGlobal, trace, } from '@elah/core';
|
|
4
4
|
import { useTracksStore, usePlaybackStore, useTransitionsStore, EditorContext, } from '@elah/react';
|
|
5
5
|
export function EditorProvider({ fps, stage, defaultTrackHeight, maxHistorySize, initialTracks, children, }) {
|
|
6
|
-
const engine = useMemo(() =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
const { engine, playback } = useMemo(() => {
|
|
7
|
+
const engine = new TimelineEngine({
|
|
8
|
+
fps,
|
|
9
|
+
stage,
|
|
10
|
+
defaultTrackHeight,
|
|
11
|
+
maxHistorySize,
|
|
12
|
+
initialTracks,
|
|
13
|
+
});
|
|
14
|
+
const playback = new PlaybackEngine({
|
|
15
|
+
fps,
|
|
16
|
+
getTotalFrames: () => useTracksStore.getState().totalFrames,
|
|
17
|
+
});
|
|
18
|
+
engine.on('project:loaded', ({ transport }) => {
|
|
19
|
+
if (transport !== 'rewind')
|
|
20
|
+
return;
|
|
21
|
+
playback.pause();
|
|
22
|
+
playback.seek(0);
|
|
23
|
+
const pb = usePlaybackStore.getState();
|
|
24
|
+
if (pb.isPlaying)
|
|
25
|
+
pb.pause();
|
|
26
|
+
if (pb.currentFrame !== 0)
|
|
27
|
+
pb.setCurrentFrame(0);
|
|
28
|
+
});
|
|
29
|
+
return { engine, playback };
|
|
30
|
+
}, []);
|
|
17
31
|
useEffect(() => {
|
|
18
32
|
const syncAll = () => {
|
|
19
33
|
const project = engine.getProject();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare
|
|
1
|
+
export declare const MediaTransformOverlay: import("react").NamedExoticComponent<object>;
|