neiam
/diogramos
diogramos
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
Let the embedding page choose the embed's theme
4aa121d · Gabriel Morell · 2026-10-13 17:17
Message
{commit_body(@commit)}
Files changed
modified
README.md
+4
−1
@@ -18,7 +18,10 @@ site. Phoenix LiveView on top of PostgreSQL, with live multi-user editing.
| 18 | 18 | - **Embeds** — publish a canvas read-only via `/embed/:token` (and the |
| 19 | 19 | `/c-embed/:slug` redirect for in-canvas links). Add `?mode=frame` for a |
| 20 | 20 | static framing — no live cursors or identity chip, diagram scaled to fill |
| 21 | − the iframe — suited to slides and docs. | |
| 21 | + the iframe — suited to slides and docs. `?theme=<name>` picks the palette; | |
| 22 | + the embedding page can also switch it live by posting | |
| 23 | + `{type: "diogramos:set-theme", theme}` to the iframe, which announces | |
| 24 | + `{type: "diogramos:ready"}` to its parent once it's listening. | |
| 22 | 25 | - **JSON export** — `/c/:slug/export.json` for any canvas you can see. |
| 23 | 26 | - **Accounts** — magic-link login by default, password optional, with |
| 24 | 27 | invite-gated registration (`/users/invites`). |
modified
lib/diogramos_web/live/canvas_live/embed.ex
+31
−1
@@ -31,6 +31,13 @@ defmodule DiogramosWeb.CanvasLive.Embed do
| 31 | 31 | frame = params["mode"] == "frame" |
| 32 | 32 | show_cursors = canvas.embed_show_cursors and not frame |
| 33 | 33 | |
| 34 | + # `?theme=` lets the embedding page pick the palette up front; it can | |
| 35 | + # also switch later over postMessage (see the EmbedViewport hook). | |
| 36 | + theme = | |
| 37 | + if params["theme"] in Diogramos.Themes.all(), | |
| 38 | + do: params["theme"], | |
| 39 | + else: canvas.theme | |
| 40 | + | |
| 34 | 41 | identity = %{ |
| 35 | 42 | key: "embed-" <> Diogramos.ULID.generate(), |
| 36 | 43 | name: CanvasPresence.random_animal_name(), |
@@ -56,6 +63,7 @@ defmodule DiogramosWeb.CanvasLive.Embed do
| 56 | 63 | |> assign(:topic, topic) |
| 57 | 64 | |> assign(:frame, frame) |
| 58 | 65 | |> assign(:show_cursors, show_cursors) |
| 66 | + |> assign(:theme, theme) | |
| 59 | 67 | |> assign(:peers, []) |
| 60 | 68 | |> assign(:identity, identity)} |
| 61 | 69 | end |
@@ -147,9 +155,13 @@ defmodule DiogramosWeb.CanvasLive.Embed do
| 147 | 155 | |> assign(:palette, DiogramosWeb.CanvasPresence.palette()) |
| 148 | 156 | |
| 149 | 157 | ~H""" |
| 158 | + <%!-- The parent page may switch data-theme from the client, so | |
| 159 | + patches leave it alone. --%> | |
| 150 | 160 | <div |
| 151 | 161 | id="embed-root" |
| 152 | − data-theme={@canvas.theme} | |
| 162 | + data-theme={@theme} | |
| 163 | + data-themes={Enum.join(Diogramos.Themes.all(), " ")} | |
| 164 | + phx-mounted={JS.ignore_attributes(["data-theme"])} | |
| 153 | 165 | class="min-h-screen w-screen bg-base-100 text-base-content relative" |
| 154 | 166 | > |
| 155 | 167 | <svg |
@@ -333,6 +345,23 @@ defmodule DiogramosWeb.CanvasLive.Embed do
| 333 | 345 | this.viewport = this.el.querySelector("#embed-viewport") |
| 334 | 346 | this.content = this.el.querySelector("#embed-content") |
| 335 | 347 | this.lastFitKey = null |
| 348 | + this.root = document.getElementById("embed-root") | |
| 349 | + this.themes = (this.root.dataset.themes || "").split(" ") | |
| 350 | + | |
| 351 | + // Theme handshake with the embedding page: | |
| 352 | + // parent → embed {type: "diogramos:set-theme", theme: "forest"} | |
| 353 | + // embed → parent {type: "diogramos:ready"} once listening | |
| 354 | + // Any origin may ask; the worst it can do is repaint the embed | |
| 355 | + // with another of our own themes. | |
| 356 | + this.onMessage = (e) => { | |
| 357 | + const msg = e.data | |
| 358 | + if (!msg || msg.type !== "diogramos:set-theme") return | |
| 359 | + if (this.themes.includes(msg.theme)) this.root.setAttribute("data-theme", msg.theme) | |
| 360 | + } | |
| 361 | + window.addEventListener("message", this.onMessage) | |
| 362 | + if (window.parent !== window) { | |
| 363 | + window.parent.postMessage({ type: "diogramos:ready" }, "*") | |
| 364 | + } | |
| 336 | 365 | this.lastCursorAt = 0 |
| 337 | 366 | this.scheduleFit() |
| 338 | 367 |
@@ -371,6 +400,7 @@ defmodule DiogramosWeb.CanvasLive.Embed do
| 371 | 400 | }, |
| 372 | 401 | destroyed() { |
| 373 | 402 | window.removeEventListener("resize", this.onResize) |
| 403 | + window.removeEventListener("message", this.onMessage) | |
| 374 | 404 | this.el.removeEventListener("pointermove", this.onPointerMove) |
| 375 | 405 | this.resizeObserver && this.resizeObserver.disconnect() |
| 376 | 406 | }, |
modified
test/diogramos_web/live/canvas_embed_test.exs
+11
−0
@@ -172,6 +172,17 @@ defmodule DiogramosWeb.CanvasEmbedTest do
| 172 | 172 | assert after_content =~ ~s(id="embed-cursors") |
| 173 | 173 | end |
| 174 | 174 | |
| 175 | + test "?theme= overrides the canvas theme when it names a known theme", %{ | |
| 176 | + conn: conn, | |
| 177 | + canvas: canvas | |
| 178 | + } do | |
| 179 | + {:ok, _lv, html} = live(conn, ~p"/embed/#{canvas.embed_token}?theme=forest") | |
| 180 | + assert html =~ ~s(id="embed-root" data-theme="forest") | |
| 181 | + | |
| 182 | + {:ok, _lv, html} = live(conn, ~p"/embed/#{canvas.embed_token}?theme=nope") | |
| 183 | + assert html =~ ~s(id="embed-root" data-theme="#{canvas.theme}") | |
| 184 | + end | |
| 185 | + | |
| 175 | 186 | test "embed receives live updates from the editor", %{conn: conn, canvas: canvas, owner: owner} do |
| 176 | 187 | {:ok, embed_lv, _} = live(conn, ~p"/embed/#{canvas.embed_token}") |
| 177 | 188 |
Parents: 2f6152a