api/request.ex
Request
A request arcing from a client to a server and the response arcing back, with method, path, status and latency.
# in the module that renders the template
use EHVisuals
<div class="h-96 rounded-xl border border-border">
<.api_request animated />
</div>
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
client |
string | "Orders app" |
Up to 14 characters. |
server |
string | "EHOS API" |
Up to 14 characters. |
method |
string | "POST" |
|
path |
string | "/v1/orders" |
Up to 24 characters. |
status |
string | "201 Created" |
A status that starts with 4 or 5 turns the response coral. Up to 22 characters. |
latency |
string | "38 ms" |
Frame props, shared by every composition
| Prop | Type | Default | What it does |
|---|---|---|---|
animated |
boolean | false |
Turns motion on. Without it the composition renders in its final, still state. |
trigger |
"mount" | "in-view" | "in-view-repeat" | "in-view" |
When the entrance plays: on page load, once when scrolled into view, or every time it comes back into view. |
hover |
boolean | false |
Enables the hover micro-interactions the composition supports. |
glow |
boolean | true |
The warm incandescent halo behind the composition. |
fade_out |
boolean | false |
Fades the bottom edge into the surface underneath. |
isometric |
boolean | false |
Tilts the composition into the isometric plane of the platform stack. |
theme |
"dark" | "light" | "dark" |
`dark` for the site canvas, `light` for the cream reversed surface. |
class |
string | nil |
Extra classes for the frame. |
rest |
global | — |
Any other HTML attribute, set on the frame. |
Source
Two files. Copy them into your app if you would rather own the code than depend on the library.
lib/eh_visuals/api/request.ex
defmodule EHVisuals.Api.Request do
@moduledoc """
One API call: the request arcs from the client to the server, the response
arcs back, and the round trip time sits between them.
"""
use EHVisuals.Composition,
name: "api/request",
title: "Request",
description:
"A request arcing from a client to a server and the response arcing back, with method, path, status and latency."
@request_path "M76 78 C76 14 364 14 364 70"
@response_path "M364 158 C364 222 76 222 76 166"
attr(:client, :string, default: "Orders app", doc: "Up to 14 characters.")
attr(:server, :string, default: "EHOS API", doc: "Up to 14 characters.")
attr(:method, :string, default: "POST")
attr(:path, :string, default: "/v1/orders", doc: "Up to 24 characters.")
attr(:status, :string,
default: "201 Created",
doc: "A status that starts with 4 or 5 turns the response coral. Up to 22 characters."
)
attr(:latency, :string, default: "38 ms")
attr(:label, :string,
default: "A POST request from a client to a server, answered with 201 Created in 38 ms"
)
frame_attrs()
@spec api_request(map()) :: Phoenix.LiveView.Rendered.t()
def api_request(assigns) do
method_w = 14 + String.length(assigns.method) * 5.6
path_w = String.length(assigns.path) * 6.0
request_w = method_w + 8 + path_w
status_w = 18 + String.length(assigns.status) * 5.6
assigns =
assigns
|> assign(:failed, String.starts_with?(assigns.status, ["4", "5"]))
|> assign(:method_w, Float.round(method_w, 1))
|> assign(:request_x, Float.round(220 - request_w / 2, 1))
|> assign(:request_w, Float.round(request_w, 1))
|> assign(:status_w, Float.round(status_w, 1))
|> assign(:request_path, @request_path)
|> assign(:response_path, @response_path)
~H"""
<.frame name="api/request" label={@label} {frame_opts(assigns)}>
<svg viewBox="0 0 440 236">
<g class="ehv-h-lift">
<path
class="ehv-line-ink ehv-a-draw ehv-i-2"
d={@request_path}
pathLength="1"
/>
<path class="ehv-fill-2 ehv-a-fade ehv-i-6" d="M359.5 69 h9 l-4.5 8 z" />
<path
class={[
if(@failed, do: "ehv-line-coral", else: "ehv-line-lime"),
"ehv-a-draw ehv-i-7"
]}
d={@response_path}
pathLength="1"
/>
<path
class={[
if(@failed, do: "ehv-fill-coral", else: "ehv-api-request__tip"),
"ehv-a-fade ehv-i-11"
]}
d="M71.5 167 h9 l-4.5 -8 z"
/>
<g :if={@animated} class="ehv-smil ehv-a-fade ehv-i-12">
<circle class="ehv-fill-ink" r="3" opacity="0">
<animateMotion
path={@request_path}
dur="3.6s"
keyPoints="0;1;1"
keyTimes="0;0.45;1"
calcMode="linear"
repeatCount="indefinite"
/>
<animate
attributeName="opacity"
values="0;1;1;0;0"
keyTimes="0;0.05;0.4;0.45;1"
dur="3.6s"
repeatCount="indefinite"
/>
</circle>
<circle class={if @failed, do: "ehv-fill-coral", else: "ehv-fill-lime"} r="3" opacity="0">
<animateMotion
path={@response_path}
dur="3.6s"
keyPoints="0;0;1;1"
keyTimes="0;0.5;0.95;1"
calcMode="linear"
repeatCount="indefinite"
/>
<animate
attributeName="opacity"
values="0;0;1;1;0"
keyTimes="0;0.5;0.55;0.9;1"
dur="3.6s"
repeatCount="indefinite"
/>
</circle>
</g>
<g class="ehv-a-rise ehv-i-0">
<rect class="ehv-card" x="16.5" y="78.5" width="119" height="79" rx="10" />
<rect class="ehv-node-soft" x="30.5" y="92.5" width="20" height="15" rx="3.5" />
<line class="ehv-line" x1="31" x2="50" y1="97.5" y2="97.5" />
<text class="ehv-t-strong" x="30" y="131" font-size="12.5">{@client}</text>
<text class="ehv-mono" x="30" y="145" font-size="7.5">CLIENT</text>
</g>
<g class="ehv-a-rise ehv-i-1">
<rect class="ehv-card" x="304.5" y="78.5" width="119" height="79" rx="10" />
<rect class="ehv-node-soft" x="318.5" y="91.5" width="20" height="7" rx="2.5" />
<rect class="ehv-node-soft" x="318.5" y="101.5" width="20" height="7" rx="2.5" />
<circle class="ehv-fill-lime ehv-a-pulse" cx="333.5" cy="95" r="1.5" />
<circle class="ehv-fill-3" cx="333.5" cy="105" r="1.5" />
<text class="ehv-t-strong" x="318" y="131" font-size="12.5">{@server}</text>
<text class="ehv-mono" x="318" y="145" font-size="7.5">SERVER</text>
</g>
<g class="ehv-a-fade ehv-i-4">
<rect
class="ehv-fill-canvas"
x={@request_x - 8}
y="18"
width={@request_w + 16}
height="24"
rx="6"
/>
<rect class="ehv-card-3" x={@request_x} y="20.5" width={@method_w} height="19" rx="5" />
<text
class="ehv-mono ehv-t-ink"
x={@request_x + @method_w / 2}
y="33.5"
font-size="8"
text-anchor="middle"
>
{String.upcase(@method)}
</text>
<text class="ehv-code ehv-t-2" x={@request_x + @method_w + 8} y="33.5" font-size="10">
{@path}
</text>
</g>
<g class="ehv-a-fade ehv-i-9">
<text class="ehv-t-display" x="220" y="124" font-size="26" text-anchor="middle">
{@latency}
</text>
<text class="ehv-mono" x="220" y="142" font-size="7.5" text-anchor="middle">
ROUND TRIP
</text>
</g>
<g class="ehv-a-pop ehv-i-10">
<rect
class="ehv-fill-canvas"
x={220 - @status_w / 2 - 6}
y="194"
width={@status_w + 12}
height="24"
rx="6"
/>
<rect
class={if @failed, do: "ehv-fill-coral-soft", else: "ehv-fill-lime-soft"}
x={220 - @status_w / 2}
y="196"
width={@status_w}
height="20"
rx="6"
/>
<text
class={["ehv-mono", if(@failed, do: "ehv-t-coral", else: "ehv-t-lime")]}
x="220"
y="209.5"
font-size="8.5"
text-anchor="middle"
>
{String.upcase(@status)}
</text>
</g>
</g>
</svg>
</.frame>
"""
end
end
assets/css/api/request.css
/* api/request */
.ehv-api-request { --ehv-max-w: 500px; --ehv-iso-scale: 0.82; --ehv-step: 110ms; }
/* The arrowhead takes the line colour, so it stays readable on the light surface. */
.ehv-api-request__tip { fill: var(--ehv-lime); }