@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.
@@ -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 {Badge, Button, Empty, Fact, SectionTitle, Separator} from "../components/ui";
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; this list is for browsing them all.
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) ?? filtered[0] ?? videos[0];
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 (!selected || !layout) {
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 (mode === "gallery") {
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
- <aside className="inspector">
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() ?? cue.src}>
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
  };