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))
endFude.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
@spec assign( Phoenix.LiveView.Socket.t(), atom(), String.t(), String.t(), Fude.Chart.t() | Fude.Spec.t() ) :: Phoenix.LiveView.Socket.t()
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.
@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.
@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.
@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).
@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"}]
The place module of this binding, or nil.
@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.
@spec tools() :: [module()]
Fude's tools for agents: read_chart and change_chart.