Fude.Issho (Fude v0.1.0)

Copy Markdown View Source

Collaboration on charts, for people and agents, in Issho's rooms: a chart's spec is a shared state of the room (Issho.State), that everyone changes with the same functions, and that is a valid chart after every change.

Fude draws charts on its own; this module is only for a chart that others change too: a report two people build at once, a chart an agent makes beside you. It is compiled when Issho is in your deps.

The document

A shared chart is a document of a room. Define it in your policy, from the chart your app keeps, and save it in :on_change:

def document(_who, "report:" <> id = doc) do
  report = Reports.get!(id)
  Fude.Issho.document(report.chart, on_change: &Reports.save_chart(report, &1, &2))
end

Fude.Issho.document/2 gives Issho the chart's spec, with an id for each layer and annotation (so that two changes to two layers at once both stay), and a hook that refuses a change that would make the chart invalid: the person or the agent who made it gets every error, with where it is. :on_change gets the chart after each change and who made it, and answers :ok or {:error, reason} to refuse it.

In a LiveView

def mount(%{"id" => id}, _session, socket) do
  room = "report:" <> id
  report = Reports.get!(id)

  if connected?(socket),
    do: {:ok, _} = Issho.Live.join(MyApp.Issho, socket.assigns.current_user, room)

  {:ok, Fude.Issho.assign(socket, :chart, room, room, report.chart)}
end

def handle_event("title", %{"title" => title}, socket) do
  :ok = Fude.Issho.change(socket, :chart, &Fude.title(&1, title))
  {:noreply, socket}
end
<.chart id="revenue" spec={@chart} data={@sales} issho="report:7" />

@chart is the chart's spec, and changes as anyone changes it. change/3 takes a function of the chart, made with Fude's own functions (Fude.title/3, Fude.annotate/3, Fude.layers/3), and sends only what it changed. The issho attribute of <.chart> is the chart's document: the people in the room see each other's pointers on its marks, at the same data on every screen (Fude.Issho.Place).

Agents

An agent uses the same chart: tools/0 gives read_chart, which says what a chart shows (its spec, and the table of its data when your app gives it), and change_chart, which takes the spec as the agent wants it and sends what changed. Issho's point tool points at a mark, in Fude.Issho.Place's terms, and its update_state changes a spec by paths too. Every change passes the same hook as a person's.

The data of a chart is your app's, read as the agent:

config :fude, Fude.Issho, data: {MyApp.Reports, :chart_data}

def chart_data("report:" <> id, ctx), do: {:ok, Reports.sales(id, ctx.opts[:on_behalf_of])}

Summary

Functions

Assigns a shared chart's spec as key, kept up to date as anyone changes it (Issho.State.assign/5). Before the LiveView is in the room (the static render), it is the spec of chart.

Changes the shared chart assigned as key with a function of the chart (Fude's functions), and sends what changed, as one change. Gives :ok, or {:error, reason}: the chart it makes is not valid, or the room refused it.

The chart of a shared state (or of a chart, as it is), or the errors of it in words.

A shared chart's document, for your policy's document/2: the chart (a Fude.Chart, or a spec) as the room's shared state, refused when a change makes it invalid.

The operations (Issho.State) that make one shared state into another: a set or a delete for each key that changed, and, for the layers and annotations, an insert, a move or a remove of each by its id, and a set of each key of one that changed. Two people who change two keys, or two layers, at once both have their change.

The place module of this binding, or nil.

The chart as a shared state: its spec (Fude.Spec.dump/2), with its layers in "layers" and an id for each layer and annotation that has none.

Fude's tools for agents: read_chart and change_chart.

Functions

assign(socket, key, room, document, chart)

Assigns a shared chart's spec as key, kept up to date as anyone changes it (Issho.State.assign/5). Before the LiveView is in the room (the static render), it is the spec of chart.

change(socket, key, fun)

@spec change(Phoenix.LiveView.Socket.t(), atom(), (Fude.Chart.t() -> Fude.Chart.t())) ::
  :ok | {:error, term()}

Changes the shared chart assigned as key with a function of the chart (Fude's functions), and sends what changed, as one change. Gives :ok, or {:error, reason}: the chart it makes is not valid, or the room refused it.

chart(chart)

@spec chart(Fude.Chart.t() | Fude.Spec.t()) ::
  {:ok, Fude.Chart.t()} | {:error, String.t()}

The chart of a shared state (or of a chart, as it is), or the errors of it in words.

document(chart_or_spec, opts \\ [])

@spec document(Fude.Chart.t() | Fude.Spec.t(), keyword()) ::
  {:ok, map()} | {:error, term()}

A shared chart's document, for your policy's document/2: the chart (a Fude.Chart, or a spec) as the room's shared state, refused when a change makes it invalid.

Options: :on_change, a function of the chart after a change and the transaction's context (:by, :document, :revision), answering :ok or {:error, reason}; :store, Issho's store of the document (the instance's unless given).

ops(old, new)

@spec ops(Fude.Spec.t(), Fude.Spec.t()) :: [map()]

The operations (Issho.State) that make one shared state into another: a set or a delete for each key that changed, and, for the layers and annotations, an insert, a move or a remove of each by its id, and a set of each key of one that changed. Two people who change two keys, or two layers, at once both have their change.

iex> Fude.Issho.ops(%{"title" => "Q3"}, %{"title" => "Q4"})
[%{"op" => "set", "path" => ["title"], "value" => "Q4"}]

place()

The place module of this binding, or nil.

state(chart)

@spec state(Fude.Chart.t()) :: Fude.Spec.t()

The chart as a shared state: its spec (Fude.Spec.dump/2), with its layers in "layers" and an id for each layer and annotation that has none.

tools()

@spec tools() :: [module()]

Fude's tools for agents: read_chart and change_chart.