People and agents seen choosing, with Issho: a picker shows who has it open and the option they are on, and an agent that changes a value goes to the picker, opens it, and moves to the option before the value lands, for the people who watch.
A picker's value is not shared here: it is the value of the document that holds it (a page's property, a row, a form), which your app saves and shares as it does. Erabi carries only who is choosing, and where.
The picker
<.select
field={@form[:status]}
options={@statuses}
issho={[instance: MyApp.Issho, document: "page:42/status"]}
user={@current_user}
/>issho takes the keys of every component of the family
(Issho.Join): the picker's document names it in its room (the part
before the first "/"), and the LiveView joins that room for it. The
user is the person, as Issho.Actor says them.
The people of the room who have the picker open show on its field, each in their color; the option each is on is marked in the list, and when the list is closed on a screen, a peek of it shows the option they move to. Issho's pointers find a picker's places by their options.
An agent
An agent's tool changes the value as the app does, in its own document, and lets the people see it choose first:
def call(%{"status" => status}, ctx) do
{:ok, participant} = Issho.Tool.Context.join(ctx, "page:42")
Erabi.Issho.choose(participant, "page:42/status", status, fn ->
Pages.set_property(page, :status, status)
end)
endchoose/4 moves the agent to the picker (its list opens on the
screens that show it), then to the option, and waits a moment there
(:pace), then runs the function, then leaves the picker. The agent
moves only for someone who sees the picker, within Issho's time budget
(Issho.Motion); else the value lands at once.
Locks
A picker is chosen in by one at a time: the person who opens its list
holds its lock, until they close it or leave. The others see who is
choosing, and their picker does not open; if two open it at once, the
one who opened it first keeps it, and the other's list closes with
the reason. An agent takes the lock as it chooses, and choose/5
gives {:error, {:locked, key}} when someone else holds it.
lock: false in the picker's issho turns the locks off.
The lock is the people's way of not choosing over each other, not a check: a form posts as it does, and the app's document keeps the last value saved.
Places
A place in a picker is %{"open" => true, "option" => value} (the
option a person or an agent is on, its list open), or %{} (the
field), in the room as the "at" of the picker's document:
%{"doc" => "page:42/status", "at" => %{"open" => true, "option" => "done"}}The option is its value as the picker posts it (text).
Summary
Functions
Moves an agent through a picker as it chooses value (or several,
for a picker of several), runs fun (the app's change), and leaves the
picker; gives what fun gives.
A place of a picker, for Issho.Motion or a participant's place:
%{"doc" => document, "at" => at}.
Checks a place in a picker (its "at"): :ok, or an error.
Functions
@spec choose(GenServer.server(), String.t(), term(), (-> result), keyword()) :: result | {:error, {:locked, String.t()} | term()} when result: term()
Moves an agent through a picker as it chooses value (or several,
for a picker of several), runs fun (the app's change), and leaves the
picker; gives what fun gives.
When someone else holds the picker's lock, the agent does not choose:
{:error, {:locked, key}}, with the holder's key. An agent that a
person paused or stopped (Issho.Work) does not choose either: it
gives Issho's {:error, {:interrupted, by}}.
Options: :pace, how long the agent stays on each option before it
takes it (500 ms; 0 for none), and :travel (false: the agent
is there at once, as Issho.Motion says).
A place of a picker, for Issho.Motion or a participant's place:
%{"doc" => document, "at" => at}.
iex> Erabi.Issho.place("page:42/status", %{"open" => true, "option" => "done"})
%{"doc" => "page:42/status", "at" => %{"open" => true, "option" => "done"}}
@spec validate(term()) :: :ok | {:error, :invalid_place}
Checks a place in a picker (its "at"): :ok, or an error.
iex> Erabi.Issho.validate(%{"open" => true, "option" => "done"})
:ok
iex> Erabi.Issho.validate(%{"cell" => [1, "a"]})
{:error, :invalid_place}