# 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`.

> Live preview: try it at https://bettertyped.github.io/react-wrapped/docs/player/scene-editor/
>
> 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`.

`edit-one-scene.tsx`

```tsx
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](https://bettertyped.github.io/react-wrapped/docs/guides/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

- `onSave` gets 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.edits` as `<scene key>.<field name>`. `editsFor(config.edits, key)` reads one scene's edits back out, and `scene.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](https://bettertyped.github.io/react-wrapped/docs/guides/custom-transition/) 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](https://bettertyped.github.io/react-wrapped/docs/scenes/). 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.

## Related

- [Timeline](https://bettertyped.github.io/react-wrapped/docs/player/timeline/)
- [Scenes](https://bettertyped.github.io/react-wrapped/docs/scenes/)
- [Transitions](https://bettertyped.github.io/react-wrapped/docs/transitions/)
- [The config in the URL](https://bettertyped.github.io/react-wrapped/docs/guides/config-in-the-url/)
- [Player](https://bettertyped.github.io/react-wrapped/docs/player/)

Source: https://bettertyped.github.io/react-wrapped/docs/player/scene-editor/
