Skip to content
react-wrapped

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

0

contributions.

OctNovDecJanFebMarAprMayJunJulAugSepOct

That's 4.6 a day, on 257 active days out of 365.

edits: {} cuts: {}

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
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

PropTypeWhat it does
sceneSceneInstance | nullThe scene as it plays now, edits applied. null keeps the dialog closed.
originalSceneInstance | nullThe same scene with no edits. "Original scene" restores from it.
cutSceneCut | nullThe way out of the scene. null for the last scene playing, or one that is switched off.
configWrappedConfigSo the transition preview plays in the video's own palette and format.
onSave(values: Record<string, string>, style: WrappedTransition | null) => voidThe texts that differ from the original, by field name, and the picked transition.
onClose() => voidThe dialog was closed, by saving or by leaving.
className?stringA class for the dialog itself.
classNames?SlotClassNames<SceneEditorSlot>A class per part, by slot name: "editor-input", "editor-save". See styling.

The cut

KeyTypeWhat it is
styleWrappedTransitionThe transition the scene plays now.
originalWrappedTransitionThe one the seed picked, to go back to.
nextSceneInstanceThe 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 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.

Last updated . Read this page as Markdown.