@odori/cli 0.0.2 → 0.0.3
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/{chunk-7XJL2BYO.js → chunk-RXLB2CXH.js} +367 -171
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +29 -3
- package/dist/index.js +1 -1
- package/dist/{registry-snapshot-NIH2JMQ6.js → registry-snapshot-BDP6PVYB.js} +2 -2
- package/package.json +3 -3
- package/src/chunk-cache.ts +6 -0
- package/src/cli.ts +10 -3
- package/src/commands/add.ts +8 -8
- package/src/commands/dev.ts +86 -11
- package/src/commands/exportVideo.ts +39 -5
- package/src/commands/init.ts +1 -1
- package/src/commands/new.ts +1 -1
- package/src/cues.ts +34 -21
- package/src/discovery.ts +22 -3
- package/src/formats.ts +67 -8
- package/src/jobs.ts +6 -2
- package/src/registry-snapshot.json +2 -2
- package/src/registry-source.ts +50 -3
- package/src/render.ts +48 -13
- package/src/server.ts +48 -12
- package/studio/src/Studio.tsx +19 -22
- package/studio/src/components/ExportPanel.tsx +124 -90
- package/studio/src/components/Inspector.tsx +121 -0
- package/studio/src/components/Thumbnail.tsx +65 -23
- package/studio/src/components/ui.tsx +9 -2
- package/studio/src/studio.css +194 -26
- package/studio/src/views/AssetsView.tsx +14 -1
- package/studio/src/views/BrandsView.tsx +58 -27
- package/studio/src/views/ComponentsView.tsx +23 -37
- package/studio/src/views/HomeView.tsx +39 -17
- package/studio/src/views/VideosView.tsx +34 -49
|
@@ -19,22 +19,33 @@ import {Thumbnail} from "../components/Thumbnail";
|
|
|
19
19
|
import {InputControls} from "../components/InputControls";
|
|
20
20
|
import {ExportPanel} from "../components/ExportPanel";
|
|
21
21
|
import {Diagnostics} from "../components/Diagnostics";
|
|
22
|
-
import {
|
|
22
|
+
import {Inspector} from "../components/Inspector";
|
|
23
|
+
import {Badge, Empty, Fact, SectionTitle, Separator} from "../components/ui";
|
|
23
24
|
import {measureTrack} from "../lib/mix-loudness";
|
|
24
25
|
import {useShortcuts} from "../shortcuts";
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* What a cue is called in the mix, not what its file is called on disk. A
|
|
29
|
+
* generated cue's filename carries a 16-character content hash that means
|
|
30
|
+
* nothing while mixing and is wide enough to collide with the column beside
|
|
31
|
+
* it; the full filename stays one hover away.
|
|
32
|
+
*/
|
|
33
|
+
const cueLabel = (src: string): string => {
|
|
34
|
+
const file = src.split("/").pop() ?? src;
|
|
35
|
+
return file.replace(/-[0-9a-f]{16}\.wav$/, "");
|
|
36
|
+
};
|
|
37
|
+
|
|
26
38
|
export const VideosView = ({
|
|
27
39
|
selection,
|
|
28
40
|
onSelect,
|
|
29
|
-
mode,
|
|
30
41
|
}: {
|
|
31
42
|
selection: string | null;
|
|
32
43
|
onSelect: (id: string) => void;
|
|
33
|
-
mode: "player" | "gallery";
|
|
34
44
|
}) => {
|
|
35
|
-
// Finding one by name is ⌘K;
|
|
45
|
+
// Finding one by name is ⌘K; the gallery is for browsing them all. A route
|
|
46
|
+
// with no selection is the gallery; a selection is that video's player.
|
|
36
47
|
const filtered = videos;
|
|
37
|
-
const selected = filtered.find((video) => video.metadata.id === selection) ??
|
|
48
|
+
const selected = selection ? (filtered.find((video) => video.metadata.id === selection) ?? null) : null;
|
|
38
49
|
const [input, setInput] = useState<Record<string, unknown>>({});
|
|
39
50
|
const [timeline, setTimeline] = useState<CompiledTimeline | null>(null);
|
|
40
51
|
const [track, setTrack] = useState<AudioTrack | null>(null);
|
|
@@ -150,7 +161,7 @@ export const VideosView = ({
|
|
|
150
161
|
s: () => setShowSafeArea((value) => !value),
|
|
151
162
|
});
|
|
152
163
|
|
|
153
|
-
if (
|
|
164
|
+
if (videos.length === 0) {
|
|
154
165
|
return (
|
|
155
166
|
<Empty title="No videos yet">
|
|
156
167
|
Create <code>{project.videosDir}/launch/video.tsx</code> or run <code>odori new launch</code>. A video exists
|
|
@@ -159,7 +170,7 @@ export const VideosView = ({
|
|
|
159
170
|
);
|
|
160
171
|
}
|
|
161
172
|
|
|
162
|
-
if (
|
|
173
|
+
if (!selected || !layout) {
|
|
163
174
|
return (
|
|
164
175
|
<div className="gallery">
|
|
165
176
|
{filtered.map((video) => {
|
|
@@ -169,19 +180,17 @@ export const VideosView = ({
|
|
|
169
180
|
key={video.metadata.id}
|
|
170
181
|
type="button"
|
|
171
182
|
className="card"
|
|
172
|
-
data-active={video.metadata.id === selected.metadata.id ? "true" : undefined}
|
|
173
183
|
onClick={() => onSelect(video.metadata.id)}
|
|
174
184
|
>
|
|
175
185
|
<Thumbnail
|
|
176
186
|
entry={video}
|
|
177
187
|
frame={video.metadata.thumbnailFrame ?? Math.round(entryDurationInFrames(video, videoLayout) * 0.4)}
|
|
178
188
|
durationInFrames={entryDurationInFrames(video, videoLayout)}
|
|
189
|
+
audioBadge
|
|
179
190
|
/>
|
|
180
191
|
<div className="card-meta">
|
|
181
192
|
<strong>{video.metadata.title}</strong>
|
|
182
|
-
<span>
|
|
183
|
-
{videoLayout.format.width}x{videoLayout.format.height} · {String(video.metadata.duration ?? "auto")}
|
|
184
|
-
</span>
|
|
193
|
+
<span>{String(video.metadata.duration ?? "auto")}</span>
|
|
185
194
|
</div>
|
|
186
195
|
</button>
|
|
187
196
|
);
|
|
@@ -196,28 +205,6 @@ export const VideosView = ({
|
|
|
196
205
|
|
|
197
206
|
return (
|
|
198
207
|
<>
|
|
199
|
-
<aside className="sidebar">
|
|
200
|
-
<div className="sidebar-scroll">
|
|
201
|
-
<ul className="list">
|
|
202
|
-
{filtered.map((video) => (
|
|
203
|
-
<li key={video.metadata.id}>
|
|
204
|
-
<button
|
|
205
|
-
type="button"
|
|
206
|
-
className="list-item"
|
|
207
|
-
data-active={video.metadata.id === selected.metadata.id ? "true" : undefined}
|
|
208
|
-
onClick={() => onSelect(video.metadata.id)}
|
|
209
|
-
>
|
|
210
|
-
{/* Format and duration are facts about the selection, and the
|
|
211
|
-
inspector states both. The list is for choosing. */}
|
|
212
|
-
<strong>{video.metadata.title}</strong>
|
|
213
|
-
</button>
|
|
214
|
-
</li>
|
|
215
|
-
))}
|
|
216
|
-
</ul>
|
|
217
|
-
|
|
218
|
-
</div>
|
|
219
|
-
</aside>
|
|
220
|
-
|
|
221
208
|
<section className="stage">
|
|
222
209
|
<CanvasStage
|
|
223
210
|
width={layout.format.width}
|
|
@@ -259,7 +246,18 @@ export const VideosView = ({
|
|
|
259
246
|
/>
|
|
260
247
|
</section>
|
|
261
248
|
|
|
262
|
-
<
|
|
249
|
+
<Inspector title={selected.metadata.title} hint={file}>
|
|
250
|
+
{/* Export sits first: it is the pane's one action, and the header
|
|
251
|
+
names what is being worked on right above it. */}
|
|
252
|
+
<ExportPanel
|
|
253
|
+
videoId={selected.metadata.id}
|
|
254
|
+
input={input}
|
|
255
|
+
frame={playback.frame}
|
|
256
|
+
width={layout.format.width}
|
|
257
|
+
height={layout.format.height}
|
|
258
|
+
/>
|
|
259
|
+
<Separator />
|
|
260
|
+
|
|
263
261
|
<SectionTitle>Video</SectionTitle>
|
|
264
262
|
<dl className="facts">
|
|
265
263
|
<Fact label="id">{selected.metadata.id}</Fact>
|
|
@@ -282,11 +280,6 @@ export const VideosView = ({
|
|
|
282
280
|
) : null}
|
|
283
281
|
</dl>
|
|
284
282
|
{selected.metadata.description ? <p className="hint">{selected.metadata.description}</p> : null}
|
|
285
|
-
<div style={{marginTop: 8}}>
|
|
286
|
-
<Button variant="outline" active={showSafeArea} onClick={() => setShowSafeArea((value) => !value)}>
|
|
287
|
-
Safe area
|
|
288
|
-
</Button>
|
|
289
|
-
</div>
|
|
290
283
|
|
|
291
284
|
{Object.keys(fields).length > 0 ? (
|
|
292
285
|
<>
|
|
@@ -304,7 +297,7 @@ export const VideosView = ({
|
|
|
304
297
|
<SectionTitle>Audio</SectionTitle>
|
|
305
298
|
<dl className="facts">
|
|
306
299
|
{track.cues.map((cue) => (
|
|
307
|
-
<Fact key={cue.id} label={cue.src.split("/").pop()
|
|
300
|
+
<Fact key={cue.id} label={cueLabel(cue.src)} title={cue.src.split("/").pop()}>
|
|
308
301
|
{cue.fromFrame}-{cue.fromFrame + cue.durationInFrames - 1} · gain {cue.gain}
|
|
309
302
|
{cue.duckUnder ? ` · ducked to ${DUCK_GAIN}` : ""}
|
|
310
303
|
{soloCue === cue.id ? " · solo" : ""}
|
|
@@ -329,15 +322,7 @@ export const VideosView = ({
|
|
|
329
322
|
) : null}
|
|
330
323
|
|
|
331
324
|
<Diagnostics entry={selected} timeline={timeline} track={track} />
|
|
332
|
-
|
|
333
|
-
<Separator />
|
|
334
|
-
<ExportPanel
|
|
335
|
-
videoId={selected.metadata.id}
|
|
336
|
-
input={input}
|
|
337
|
-
frame={playback.frame}
|
|
338
|
-
exportDir={project.exportDir}
|
|
339
|
-
/>
|
|
340
|
-
</aside>
|
|
325
|
+
</Inspector>
|
|
341
326
|
</>
|
|
342
327
|
);
|
|
343
328
|
};
|