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
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, fromIssho.Live.grant/4.anyone(:boolean) - whether to offer Pause and Stop for the agents of others too. Defaults tofalse.label(:string) - Defaults to"Agents at work".class(:string) - Defaults tonil.- Global attributes are accepted.
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 totrue.class(:string) - Defaults tonil.read(:any) - with:input, a function of a composer's form params that gives%{body: text, mentions: [key]}. Defaults tonil.
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}; withread.
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 tonil.class(:string) - Defaults tonil.- Global attributes are accepted.
Attributes
id(:string) (required)grant(:map) - a grant of the room, fromIssho.Live.grant/4. Defaults tonil.people(:map) - the room's people, fromIssho.Live.people/1(without a grant). Defaults tonil.self(:string) - this actor's key, withpeople. Defaults tonil.max(:integer) - how many avatars before "+N". Defaults to8.label(:string) - Defaults to"People here".follow(:boolean) - with a grant: whether a click on someone follows them. Defaults totrue.class(:string) - Defaults tonil.- Global attributes are accepted.
Slots
person- withpeople: 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 theisshopackage.
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, fromIssho.Live.grant/4.share(:boolean) - whether this person's pointer is shared. Defaults totrue.motion(:string) - how much this person wants to see others move, from the app's settings; the browser's setting unless given. Defaults tonil. Must be one ofnil,"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 tonil. Must be one ofnil, or"on_behalf".class(:string) - Defaults tonil.- Global attributes are accepted.