Erabi.Issho (Erabi v0.1.0)

Copy Markdown View Source

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

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

choose(participant, document, value, fun, opts \\ [])

@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).

place(document, at)

@spec place(String.t(), map()) :: map()

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

validate(at)

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