OpenFresco.Editor (open_fresco v0.2.0)

Copy Markdown View Source

The browser editor stage — a Phoenix.LiveComponent that renders a scene as a server-authoritative SVG preview and lets the user select (single, shift-click, marquee), drag, resize, reorder, and delete elements on a Fresco-style artboard.

Design: there is one layout engine — the server. The JS hook (priv/static/open_fresco.js) drives gestures: it hit-tests locally at pointerdown for instant selection/drag seeding, previews the gesture with a client-side transform, and commits one operation on release; the server applies it through OpenFresco.Editor.Ops against the resolved layout (the same geometry the paint pass used) and re-renders. So "what you edit is what renders" holds by construction.

The SVG is rendered as separate defs / body / selection dynamics (OpenFresco.Svg.render_parts/3 with split_images: true), so LiveView patches after a commit don't retransmit inlined image data-URLs, and selection changes don't retransmit geometry.

Embedding

<.live_component
  module={OpenFresco.Editor} id="og-editor"
  scene={@scene} values={@values} globals={@globals} />

Assigns:

  • :scene (required) — the OpenFresco.Scene.
  • :values — slot values, as OpenFresco.render_svg/3.
  • :globals[[global]] values; the stage resolves them exactly like the PNG path does (one shared substitution pass).
  • :resolver — the resource-resolver fun (see OpenFresco.render_svg/3); lets the stage display media that's stored as opaque refs instead of the stand-in.
  • :selected — pass to drive selection programmatically (e.g. click a slot chip in the host → highlight its element). Omit to leave selection editor-driven.
  • :fittrue (default) scales the stage to its container (pointer math is transform-safe); false renders 1:1.
  • :notify — where change messages go: a pid (default: the root LiveView via self()), or {module, id} to send_update/3 into a host LiveComponent (which otherwise could never receive them).

Ownership contract

The scene is host-owned, editor-driven: every committed edit is notified to the host (persist it; echo it back via the :scene assign). Echoed scenes that equal the editor's current scene are accepted without re-rendering, and gestures preview client-side — a parent re-render can no longer clobber an in-flight gesture. A different scene from the host always wins (that's programmatic editing); hosts wanting stricter arbitration can withhold the assign.

On every committed edit the component notifies {:open_fresco_editor, id, {:scene_changed, scene}} (every message is a persist-safe commit — previews never reach the server); on selection {:open_fresco_editor, id, {:selected, id_or_nil}} and — when a selection set is active — {:selected_ids, [ids]}.

Undo material: apply edits through OpenFresco.Editor.Ops.command/2 in the host when you need an undo stack — each command returns its inverse.

Keyboard

With the stage focused: Tab/Shift+Tab cycle element selection, arrows nudge by 1px (Shift = 10px), Delete/Backspace delete the selection (set-aware), Escape deselects, ]/[ bring to front / send to back (Alt steps one position).

Requires the optional :phoenix_live_view dependency — without it this module is not compiled.