A canvas that several people open at once, as a shared design file is: each sees the others' changes as they happen, their cursors, the nodes they selected and those they drag, in their colours.
A shared canvas is a room of Issho, collaboration for Phoenix: Hakushi on its own is a canvas for one person, and Issho makes it a canvas for many, with the people's own permissions and no service besides your app.
<.canvas
id="schema"
graph={@schema.graph}
on_change={&Schemas.change(&1, &2, @schema)}
live={{MyApp.Issho, "schema:#{@schema.id}"}}
user={%{id: @current_user.id, name: @current_user.name}}
>Setting up
Issho in your dependencies:
{:issho, "~> 0.1"}An Issho instance in your supervision tree, after the PubSub, with your policy: who may open which canvas, and change it.
{Issho, name: MyApp.Issho, pubsub: MyApp.PubSub, endpoint: MyAppWeb.Endpoint, policy: MyApp.Collaboration}defmodule MyApp.Collaboration do @behaviour Issho.Policy @impl true def authorize(%Issho.Identity{key: user_id}, "schema:" <> id, action) do if MyApp.Schemas.allowed?(user_id, id, action), do: :ok, else: {:error, :forbidden} end endactionis:join(open the canvas, see who is here),:read(receive the changes),:write(change it) or:lock. The policy is asked as each person opens the canvas, and again for each change, in both directions: a person whose access you take away stops seeing the changes, andIssho.revoke(MyApp.Issho, topic, user_id)takes them out of the room at once.The hook, in the LiveView or its
live_session:live_session :default, on_mount: [Hakushi.Live] do(
Hakushi.Liveis Issho's hook,Issho.Live: a LiveView with either needs no other.)
The room's document
The canvases of a topic share one document, the graph, with one process
in your cluster that puts the changes of everyone in order. The first
canvas to open it gives it its graph, rules and on_change; the
others get the graph as it is then.
Each change of each person goes to that process, which checks the rules,
applies it, asks on_change (once, whoever made it, with the graph
before and after it), and only then sends it to every canvas of the
topic. So on_change runs once for each change, and two people never
save over each other: the second change applies to the graph the first
one made. A change that no longer makes sense (an edge to a node someone
deleted a moment before) is refused, with the reason.
The document lasts while a canvas has it open, and a few seconds more,
so that a reload finds it. Your tables, which on_change writes, are
the truth: the next canvas to open the topic starts from the graph you
give it.
People
user is a map with an :id, and a :name, a :color (any CSS
colour) and an :avatar (an image's URL) if you have them. A person with
no colour gets one of eight, by their id, the same everywhere. A person
with the canvas open in two tabs is one person, and does not see
themselves. Without a user, a canvas joins as someone anonymous.
The avatars of the others show in the corner of the canvas. Draw them
yourself with the :person slot, which gets a map with "id",
"name", "color" and "avatar".
Cursors and drags
Each person's cursor, the nodes they selected and the nodes they drag go to the others as they move, in Issho's awareness frames: one frame of what changed, about twenty times a second, whatever the number of people. A node someone drags moves for everyone while they drag it, with their name; it lands where they drop it when the room has the move.
A cluster
The room's process is found from every node of a cluster (:global,
unless your Issho instance names another registry), and the PubSub must
reach every node, as Phoenix's does.
The app's own changes
In the LiveView of the canvas, Hakushi.Live.change/2. From anywhere (a
job, another LiveView), Hakushi.Issho.change(MyApp.Issho, topic, change), while someone has the canvas open; it answers
{:error, :not_running} when no one has, and then you change your own
tables. Hakushi.Issho.graph/2 reads the graph a room has.