Issho.Components (Issho v0.4.0)

Copy Markdown View Source

Issho's pieces of a page, in HEEx. Import them where your app imports its own components (html_helpers in MyAppWeb), and use them as yours, <.people> and <.pointers>:

defp html_helpers do
  quote do
    import Issho.Components
    # ...
  end
end

(import Issho.Components, only: [pointers: 1] when your app has a component of the same name.) Style them with Issho's theme: issho.css, and issho-sumi.css after it for the Sumi theme (in the package's priv/static).

The people of a room

One list for the page, whatever its components (editors, grids, a canvas): each person and agent once, in their colour, the agents with whom they act for, the viewers as viewers, and what each says they are doing.

With a grant (Issho.Live.grant/4), the browser keeps it live (the IsshoPeople hook of Hooks from issho, in your LiveSocket):

<.people id="people" grant={@grant} />

In a LiveView that is in the room itself (Issho.Live.join/4), the server renders it, from Issho.Live.people/1:

<.people id="people" people={Issho.Live.people(@room)} self={@me} />

The look

Issho's theme (issho.css) styles every piece through custom properties: the avatars' size, shape, edge and initials, the badges, the pointers' names (--issho-avatar-radius, --issho-avatar-text, --issho-pointer-radius... see the file), and the palette of the people with no colour of their own (--issho-person-1 to -8). Set them in your CSS to match your app. The classes and data attributes are stable: leave the file out to style them all yourself. And draw a person or a pointer with your own markup: the :person slot of a list the server renders, or configure({person, pointer}) of the issho package for the live list and the pointers.

The page as a live space

Each person's pointer over the page, and their clicks, in their colour, with their name, over any part of the page: Issho's components and plain HEEx alike (the IsshoPointers hook):

<.pointers id="pointers" grant={@grant} />

A pointer is a point of an element, not of the screen, so each person sees it on the same part of their own page, whatever its size and scroll: the nearest element with data-issho-anchor, else with an id. Over a component that says what is under a point (point in the issho package: a canvas's point, a grid's cell), it is a place of the component's document, drawn where that place is on each screen (locate): the same cell in another order, the same point of a canvas panned elsewhere. So one pointer goes from block to block of the page, and into a block within a block. Off the screen, it is a marker at its edge that scrolls to it. Inside an element with data-issho-cursor="own" (a component that draws its people itself, and says nothing of its points) the component takes over. Over an element with data-issho-private, or a password field, the browser shares nothing, nor while its window is in the background. share={false} shows the others without sharing this person's pointer.

A block of another room on the page (a canvas shared in its own room, in a document) is that room's: its room draws its people there, and this layer draws no one in it. Following someone who goes into it follows them in its room too. See the "Building a component" guide.

Agents moving

An agent goes where it works as a hand would (Issho.Motion): its pointer glides there, in the time the agent says, to a place of a document (its caret, a node, a cell) or an element of the page (data-issho-anchor), and rings where it clicks. The pointers tell the room what each browser shows (the page in the foreground, the components on the screen, whether its person wants motion), so that an agent moves only for someone who sees it.

How much a person wants to see others move is the app's to say, from its own settings: motion="reduced" on the pointers, setMotion of the issho package, or data-issho-motion on the page's root element; else the browser's "reduce motion" setting. motion_switch/1 is a switch for it, to place and style as you like, or not at all.

Following

A click on someone in the people list follows them (follow={false} for a list that does not): on a page whose blocks share the room, each block shows where they are (the grid their view, the canvas their viewport, the text their caret) and the page scrolls to them. A link or a button with data-issho-follow that they click is clicked for those who follow them too: the agent clicks "Edit together", and its person, following it, is in the editor. follow_agents="on_behalf" on the pointers follows an agent at work for this person as soon as it moves (unless they stopped following it on this page). Going one's own way (a scroll, a click, a key) stops following. A block alone in its room follows within itself, by the same contract (follow of the issho package, or the issho:following event and data-issho-following on the page's root element for a block's own script).

Summary

Functions

The agents at work in a room (Issho.Work): each one's task, its steps and how far it is, whom it works for, and whether someone paused or stopped it, live, from the room of a grant (the IsshoAgents hook of Hooks). It is hidden while no agent has a task.

The threads of comments of a room (Issho.Comments): a panel of the open and resolved threads, with their comments, replies, reactions, edits and a composer, and a mark at each thread's place on the page, where its component locates it (locate of the issho package), as the pointers are. Comment on what you selected in a component, or on the page. In a LiveView that is in the room (Issho.Live.join/4), with the IsshoComments hook of Hooks in your LiveSocket

A switch for how much this person wants to see others move: pressed, pointers glide, carets travel and selections sweep; not, they are there at once. Optional: place it where your settings are, style it (.issho-motion-switch, aria-pressed), or use your own control with setMotion of the issho package (the IsshoMotion hook).

The pointers of the room's people over the page: a layer over the viewport.

Functions

agents(assigns)

The agents at work in a room (Issho.Work): each one's task, its steps and how far it is, whom it works for, and whether someone paused or stopped it, live, from the room of a grant (the IsshoAgents hook of Hooks). It is hidden while no agent has a task.

<.agents id="agents" grant={@grant} />

The person an agent works for has Pause, Resume and Stop. The others have them too with anyone (when your policy allows them :interrupt; it decides either way). A paused or stopped agent changes nothing more in the room: its next tool says who paused or stopped it, and what to do.

Attributes

  • id (:string) (required)
  • grant (:map) (required) - a grant of the room, from Issho.Live.grant/4.
  • anyone (:boolean) - whether to offer Pause and Stop for the agents of others too. Defaults to false.
  • label (:string) - Defaults to "Agents at work".
  • class (:string) - Defaults to nil.
  • Global attributes are accepted.

comments(assigns)

The threads of comments of a room (Issho.Comments): a panel of the open and resolved threads, with their comments, replies, reactions, edits and a composer, and a mark at each thread's place on the page, where its component locates it (locate of the issho package), as the pointers are. Comment on what you selected in a component, or on the page. In a LiveView that is in the room (Issho.Live.join/4), with the IsshoComments hook of Hooks in your LiveSocket:

<.comments id="comments" room={@room} />

Say what a thread's place is with the :place slot (it gets the place, nil for the page, and the default words):

<.comments id="comments" room={@room}>
  <:place :let={%{place: place, default: default}}>
    {MyApp.Places.label(place) || default}
  </:place>
</.comments>

Typing @ in a comment opens a list of the people of the room and of mentionable (people who are not here: [%{key: "user:42", name: "Ada"}]), filtered as one types: the arrows, Enter or Tab, Escape. Whom one picks is mentioned, and hears of it ("comment.created"'s mentions); an @ and a name typed in full mentions them too.

Write the comments with an input of your own, a Kotoba editor with its own mentions menu, say, with the :input slot (it gets the input's id, name ("body"), value, placeholder, and a form field of them) and read, a function of the composer's form params that gives the comment's words and the keys of whom they mention:

<.comments id="comments" room={@room} read={&MyApp.Comments.read/1}>
  <:input :let={input}>
    <Kotoba.Components.kotoba field={input.field} features={~w(bold italic links mentions)} prompts={@people_prompts} />
  </:input>
</.comments>

def read(%{"body" => json}) do
  {:ok, content} = Kotoba.Content.cast(json)
  {:ok, doc} = Kotoba.Document.parse(content.doc)
  %{body: content.text, mentions: Enum.map(Kotoba.Document.mentions(doc), & &1.id)}
end

(Kotoba's mention ids are then the actors' keys.) A comment keeps its words as text.

Attributes

  • id (:string) (required)
  • room (:string) (required) - the room, which the LiveView has joined.
  • mentionable (:list) - people one may mention who are not in the room. Defaults to [].
  • marks (:boolean) - whether the page marks each thread's place. Defaults to true.
  • class (:string) - Defaults to nil.
  • read (:any) - with :input, a function of a composer's form params that gives %{body: text, mentions: [key]}. Defaults to nil.

Slots

  • place - a thread's place, in words: gets %{place: place, default: words}.
  • input - the composers' input, of your own (a Kotoba editor): gets %{id: id, name: "body", value: text, placeholder: words}; with read.

motion_switch(assigns)

A switch for how much this person wants to see others move: pressed, pointers glide, carets travel and selections sweep; not, they are there at once. Optional: place it where your settings are, style it (.issho-motion-switch, aria-pressed), or use your own control with setMotion of the issho package (the IsshoMotion hook).

Attributes

  • id (:string) (required)
  • label (:string) - Defaults to "Show others moving".
  • event (:string) - an event pushed to the LiveView with %{"motion" => "full" | "reduced"} when it changes, to keep it in the person's settings. Defaults to nil.

  • class (:string) - Defaults to nil.
  • Global attributes are accepted.

people(assigns)

Attributes

  • id (:string) (required)
  • grant (:map) - a grant of the room, from Issho.Live.grant/4. Defaults to nil.
  • people (:map) - the room's people, from Issho.Live.people/1 (without a grant). Defaults to nil.
  • self (:string) - this actor's key, with people. Defaults to nil.
  • max (:integer) - how many avatars before "+N". Defaults to 8.
  • label (:string) - Defaults to "People here".
  • follow (:boolean) - with a grant: whether a click on someone follows them. Defaults to true.
  • class (:string) - Defaults to nil.
  • Global attributes are accepted.

Slots

  • person - with people: draws a person in place of the avatar (:let: key, name, avatar, initials, color, kind, you); with a grant, the browser draws them: configure({person}) of the issho package.

pointers(assigns)

The pointers of the room's people over the page: a layer over the viewport.

Attributes

  • id (:string) (required)
  • grant (:map) (required) - a grant of the room, from Issho.Live.grant/4.
  • share (:boolean) - whether this person's pointer is shared. Defaults to true.
  • motion (:string) - how much this person wants to see others move, from the app's settings; the browser's setting unless given. Defaults to nil. Must be one of nil, "full", or "reduced".
  • follow_agents (:string) - "on_behalf": an agent at work for this person is followed when it starts to move, unless they stopped following it. Defaults to nil. Must be one of nil, or "on_behalf".
  • class (:string) - Defaults to nil.
  • Global attributes are accepted.