SceneEditor
SceneEditor is the dialog behind the pencil on the timeline. It shows the texts one scene lets a viewer rewrite and, when the scene hands over to another one, a chip per transition with the cut playing next to them. Use it as the answer to the timeline's onEdit.
In the last 12 months you made
contributions.
That's 4.6 a day, on 257 active days out of 365.
Rewrite the headline, pick another transition, save.
Usage
The editor keeps nothing. It opens on the scene you hand it and gives back what changed. You store that in the config: texts under edits, the transition under cuts.
import { useMemo, useState } from "react";
import {
editsFor,
luckyCut,
type SceneInstance,
SceneThumb,
type WrappedConfig,
type WrappedTransition,
} from "@react-wrapped/core";
import { SceneEditor } from "@react-wrapped/player";
export function EditOneScene({
original,
next,
seed,
config,
onConfigChange,
}: {
/** The scene as the director wrote it. */
original: SceneInstance;
/** The scene that plays after it, for the transition's preview. */
next: SceneInstance;
seed: string;
config: WrappedConfig;
onConfigChange: (next: WrappedConfig) => void;
}) {
const [open, setOpen] = useState(false);
const { key } = original;
// The scene as it plays now: the director's words with the viewer's edits over them.
const scene = useMemo(() => original.withEdits(editsFor(config.edits, key)), [original, config.edits, key]);
// The transition the seed picked for this scene. A pick of the viewer's own is kept in `config.cuts`.
const lucky = luckyCut(seed, key);
const save = (values: Record<string, string>, style: WrappedTransition | null) => {
// Edits are stored flat, as "<scene key>.<field name>", so they fit in one URL parameter.
const others = Object.entries(config.edits).filter(([name]) => !name.startsWith(`${key}.`));
const mine = Object.entries(values).map(([field, value]): [string, string] => [`${key}.${field}`, value]);
const cuts = Object.fromEntries(Object.entries(config.cuts).filter(([sceneKey]) => sceneKey !== key));
// Only a pick that differs from the seed's own is worth keeping.
if (style && style !== lucky) cuts[key] = style;
onConfigChange({ ...config, edits: Object.fromEntries([...others, ...mine]), cuts });
};
return (
<div>
<SceneThumb scene={scene} config={config} width={280} />
<div role="group" aria-label="Scene">
<button type="button" onClick={() => setOpen(true)}>
Edit this scene
</button>
</div>
<output>{`edits: ${JSON.stringify(config.edits)} cuts: ${JSON.stringify(config.cuts)}`}</output>
<SceneEditor
scene={open ? scene : null}
original={original}
cut={{ style: config.cuts[key] ?? lucky, original: lucky, next }}
config={config}
onSave={save}
onClose={() => setOpen(false)}
/>
</div>
);
}Props
| Prop | Type | What it does |
|---|---|---|
scene | SceneInstance | null | The scene as it plays now, edits applied. null keeps the dialog closed. |
original | SceneInstance | null | The same scene with no edits. "Original scene" restores from it. |
cut | SceneCut | null | The way out of the scene. null for the last scene playing, or one that is switched off. |
config | WrappedConfig | So the transition preview plays in the video's own palette and format. |
onSave | (values: Record<string, string>, style: WrappedTransition | null) => void | The texts that differ from the original, by field name, and the picked transition. |
onClose | () => void | The dialog was closed, by saving or by leaving. |
className? | string | A class for the dialog itself. |
classNames? | SlotClassNames<SceneEditorSlot> | A class per part, by slot name: "editor-input", "editor-save". See styling. |
The cut
| Key | Type | What it is |
|---|---|---|
style | WrappedTransition | The transition the scene plays now. |
original | WrappedTransition | The one the seed picked, to go back to. |
next | SceneInstance | The scene it hands over to, for the preview. |
How edits are stored
onSavegets only the texts that differ from the original, so the config and the link made from it stay short.- A field left empty gives the original words back. There is no way to save an empty headline.
- Edits live in
config.editsas<scene key>.<field name>.editsFor(config.edits, key)reads one scene's edits back out, andscene.withEdits(edits)returns that scene with them applied. - A headline is edited as lines: a new line in the box is a new line on screen. Single stars colour words and double stars make them bold, as in any scene text.
- The transition list comes from
transitionList(), so a transition of your own is offered next to the built-in ones.
Which scenes can be edited
A scene offers the fields its kind declares: see "What a viewer can rewrite" on each scene's page. A scene placed with locked: true offers none, and a scene with no fields still opens for its transition. The last scene playing has no way out, so pass cut: null for it.
