Masu.Presence (Masu v0.1.0)

Copy Markdown View Source

Who else has a grid open, and where they are: the people of a grid, with Phoenix.Presence.

<.data_grid
  id="orders"
  source={Order}
  repo={Repo}
  changeset={&Order.changeset/2}
  live={{MyApp.PubSub, "orders"}}
  presence={MyAppWeb.Presence}
  user={%{id: @current_user.id, name: @current_user.name}}
>

The grid shows the other people who have it open (their avatars above it), the cell each of them is on (a line in their color, with their name), the cell each of them is editing (which the others cannot edit meanwhile), and the rows they save, which flash in their color.

Setting up

presence is your app's Phoenix.Presence module, on the same PubSub as the grid's live topic (mix phx.gen.presence makes one):

defmodule MyAppWeb.Presence do
  use Phoenix.Presence, otp_app: :my_app, pubsub_server: MyApp.PubSub
end

and it must be started in your supervision tree, after the PubSub. The grid needs live (the people of a grid are those of its topic) and the Masu.Live hook.

The user

user is a map: id (required), name, and, if you have them, color (a CSS color) and avatar (an image's URL). A person with no color gets one of a palette of eight, by their id, the same in every browser.

The people are those of other users: a person who has the grid open in two tabs does not see themselves.

Places, not positions

Each person has their own order, filters and search, so a place is a row's id and a column's key, not a row's index: a person sees another's line on the cell of that row wherever it is in their own order, and not at all when their filters leave the row out.

The look

The avatars are HEEx: give the grid a :person slot to draw them with your own component; it gets each person as a map with :id, :name, :color, :avatar, :initials, :cell, :range, :editing, :view and :top. Each avatar is a button that follows the person: the grid shows their order, filters and search (unless their grid has share_view={false}) and keeps their place in view:

<:person :let={person}>
  <.avatar src={person.avatar} name={person.name} ring={person.color} />
</:person>

The lines and labels on the cells are drawn by the browser: they are .masu-peer and .masu-peer-label elements with the person's color in --masu-peer-color (see the Theming guide), and configure/1 of the bundle draws the label with a function of your own.

Editing at the same time

A cell that someone is editing shows it, and does not open an editor for anyone else: an advisory lock, as a database's. The guarantee is the server's: an edit of a cell that changed meanwhile is refused (see the Editing guide).

Summary

Types

A person who has the grid open.

Functions

The color of a user: their own color, or one of a palette of eight by their id, the same in every browser.

Types

person()

@type person() :: %{
  id: String.t(),
  name: String.t() | nil,
  color: String.t(),
  avatar: String.t() | nil,
  initials: String.t(),
  cell: [term()] | nil,
  range: map() | nil,
  editing: boolean(),
  view: map() | nil,
  top: term()
}

A person who has the grid open.

Functions

color(user)

@spec color(map()) :: String.t()

The color of a user: their own color, or one of a palette of eight by their id, the same in every browser.