"""What the install box of Get the files shows under each tab.
Two things per tab, both drawn from data:
- The steps that differ by system, opening a terminal and pasting the line,
as an inline SVG: one card per step, a pictogram and a short caption,
drawn with sitediagrams' primitives so it takes the theme's colours in
light and dark. The text steps above it (platforms/_guides.yml,
terminals) stay the accessible source. The SVG says the same in its title
and desc. The pictograms are generic shapes (a menu, a search field, a
window, keys, a mouse, the power symbol), no vendor's interface or logo.
- The installer demo recorded for that place (scripts/make_demo.py): the
full-screen interface on a handheld under "On the device", the terminal
recording under the others but Windows. No Windows recording is
published (make_demo.py says why), and a curl line recorded in a Linux
terminal under the PowerShell command would contradict it.
"""
from __future__ import annotations
from dataclasses import dataclass
from xml.sax.saxutils import escape
import sitemarkup as markup
from sitediagrams import (
FONT,
MONO_STYLE,
NARROW,
STROKE,
TEXT_SIZE,
WIDE,
_badge,
_badge_side,
_line,
_n,
_rect,
_text,
wrap,
)
KEY_SIZE = 11.5
ICON_H = 44.0
@dataclass(frozen=True)
class Step:
"""One card: a pictogram (*icon* and its *word*, the text it shows:
keys joined by "+", the word typed in a search field) and a caption."""
icon: str
caption: str
word: str = ""
STEPS: dict[str, tuple[str, tuple[Step, ...]]] = {
"windows": ("Windows: open PowerShell and paste the line", (
Step("menu", "Open the Start menu"),
Step("search", "Type PowerShell and press Enter", "PowerShell"),
Step("copy", "Click Copy, the button by the line"),
Step("mouse", "Right-click in the window to paste"),
Step("keys", "Press Enter", "Enter"),
)),
"macos": ("macOS: open Terminal and paste the line", (
Step("keys", "Press Cmd+Space", "Cmd+Space"),
Step("search", "Type Terminal and press Return", "Terminal"),
Step("copy", "Click Copy, the button by the line"),
Step("keys", "Press Cmd+V to paste", "Cmd+V"),
Step("keys", "Press Return", "Return"),
)),
"linux": ("Linux: open a terminal and paste the line", (
Step("keys", "Open a terminal, often Ctrl+Alt+T", "Ctrl+Alt+T"),
Step("copy", "Click Copy, the button by the line"),
Step("keys", "Press Ctrl+Shift+V to paste", "Ctrl+Shift+V"),
Step("keys", "Press Enter", "Enter"),
)),
"steamdeck": ("Steam Deck: open Konsole and paste the line", (
Step("power", "Hold power, pick Switch to Desktop"),
Step("window", "Open Konsole"),
Step("copy", "Click Copy, the button by the line"),
Step("keys", "Press Ctrl+Shift+V to paste", "Ctrl+Shift+V"),
Step("keys", "Press Enter", "Enter"),
)),
}
# The recording each tab shows (docs_assets/demo/demo.json keys), "" for none.
TAB_DEMOS = {"device": "handheld", "windows": ""}
DEFAULT_DEMO = "default"
def demo_variant(tab: str) -> str:
return TAB_DEMOS.get(tab, DEFAULT_DEMO)
def description(tab: str) -> str:
"""The steps in words: the SVG's desc."""
return " ".join(f"{index}. {step.caption}." for index, step in
enumerate(STEPS[tab][1], start=1))
# Pictograms, drawn in a box *w* wide and ICON_H high from (x, y).
def _stroke(d: str, cls: str = "rb-d-line", width: float = 1.5) -> str:
return f''
def _key_width(label: str) -> float:
return max(20.0, len(label) * KEY_SIZE * 0.62 + 10)
def _keys(x: float, y: float, w: float, word: str) -> list[str]:
labels = word.split("+")
total = sum(_key_width(label) for label in labels) + 4 * (len(labels) - 1)
left = x + (w - total) / 2
out = []
top = y + ICON_H / 2 - 12
for label in labels:
width = _key_width(label)
out.append(_rect(left, top, width, 24, accent=True))
out.append(
f'{escape(label)}')
left += width + 4
return out
def _menu(x: float, y: float, w: float) -> list[str]:
cx, top = x + w / 2, y + 4
out = [_rect(cx - 22, top, 44, 36)]
out += [_stroke(f"M{_n(cx - 14)} {_n(top + 10 + 8 * i)} H{_n(cx + 14)}") for i in range(3)]
return out
def _search(x: float, y: float, w: float, word: str) -> list[str]:
width = min(w - 4, len(word) * KEY_SIZE * 0.62 + 34)
left, top = x + (w - width) / 2, y + ICON_H / 2 - 12
return [
_rect(left, top, width, 24, accent=True),
f'',
_stroke(f"M{_n(left + 15)} {_n(top + 15)} L{_n(left + 19)} {_n(top + 19)}"),
f'{escape(word)}',
]
def _window(x: float, y: float, w: float) -> list[str]:
cx, top = x + w / 2, y + 2
return [
_rect(cx - 32, top, 64, 40),
_stroke(f"M{_n(cx - 32)} {_n(top + 9)} H{_n(cx + 32)}"),
f'>_',
]
def _copy(x: float, y: float, w: float) -> list[str]:
cx, top = x + w / 2, y + 6
return [_rect(cx - 12, top, 18, 24), _rect(cx - 6, top + 7, 18, 24, accent=True)]
def _mouse(x: float, y: float, w: float) -> list[str]:
cx, top = x + w / 2, y + 2
return [
f'',
# The right button, the one to press.
f'',
_stroke(f"M{_n(cx)} {_n(top)} V{_n(top + 14)} M{_n(cx - 13)} {_n(top + 14)} "
f"H{_n(cx + 13)}"),
]
def _power(x: float, y: float, w: float) -> list[str]:
cx, cy = x + w / 2, y + ICON_H / 2 + 2
return [
_stroke(f"M{_n(cx - 9)} {_n(cy - 11)} A14 14 0 1 0 {_n(cx + 9)} {_n(cy - 11)}", width=2.5),
_stroke(f"M{_n(cx)} {_n(cy - 18)} V{_n(cy - 2)}", width=2.5),
]
def _icon(step: Step, x: float, y: float, w: float) -> list[str]:
if step.icon == "keys":
return _keys(x, y, w, step.word)
if step.icon == "search":
return _search(x, y, w, step.word)
return {"menu": _menu, "window": _window, "copy": _copy, "mouse": _mouse,
"power": _power}[step.icon](x, y, w)
def _wide(steps: tuple[Step, ...]) -> tuple[float, float, list[str]]:
"""The cards in a row, joined by arrows."""
gap = 14.0
width = (WIDE - gap * (len(steps) - 1) - 2) / len(steps)
captions = [wrap(step.caption, width - 12, TEXT_SIZE) for step in steps]
height = 34 + ICON_H + 10 + max(len(lines) for lines in captions) * 18 + 8
out: list[str] = []
for index, (step, lines) in enumerate(zip(steps, captions)):
x = 1 + index * (width + gap)
out.append(_rect(x, 1, width, height))
out += _badge(x + 8, 8, str(index + 1))
out += _icon(step, x + 4, 30, width - 8)
y = 30 + ICON_H + 24
for line in lines:
out.append(_text(x + width / 2, y, line, anchor="middle"))
y += 18
if index < len(steps) - 1:
mid = 1 + height / 2
out += _line([(x + width + 1, mid), (x + width + gap - 1, mid)])
return WIDE, height + 2, out
def _narrow(steps: tuple[Step, ...]) -> tuple[float, float, list[str]]:
"""One card a row: number, pictogram, caption."""
icon_w, row_gap = 112.0, 8.0
text_x = 8 + _badge_side("9") + 6 + icon_w + 8
out: list[str] = []
y = 1.0
for index, step in enumerate(steps):
lines = wrap(step.caption, NARROW - text_x - 8, TEXT_SIZE)
height = max(ICON_H + 12, len(lines) * 18 + 16)
out.append(_rect(1, y, NARROW - 2, height))
out += _badge(8, y + height / 2 - 10, str(index + 1))
out += _icon(step, 8 + _badge_side("9") + 6, y + (height - ICON_H) / 2, icon_w)
ty = y + height / 2 - (len(lines) - 1) * 9 + 5
for line in lines:
out.append(_text(text_x, ty, line))
ty += 18
y += height + row_gap
return NARROW, y - row_gap + 1, out
def _svg(tab: str, layout: str) -> str:
title, steps = STEPS[tab]
width, height, body = (_wide if layout == "wide" else _narrow)(steps)
base = f"rb-steps-{tab}-{layout}"
return (
f'"
)
def steps_figure(tab: str) -> list[str]:
"""The figure of a tab's steps, both layouts, or nothing for a tab with
no drawing."""
if tab not in STEPS:
return []
return [f'',
_svg(tab, "wide") + _svg(tab, "narrow"), "", ""]
def tab_demo(tab: str) -> list[str]:
"""The recording a tab shows, as generate_site writes the component."""
from generate_site import install_demo # noqa: PLC0415
variant = demo_variant(tab)
return install_demo(variant, tab) if variant else []
def install_box(tabs: tuple[str, ...]) -> tuple[dict[str, list[str]], dict[str, list[str]]]:
"""What terminal_tabs puts under each tab's steps and under its command."""
return ({tab: steps_figure(tab) for tab in tabs},
{tab: tab_demo(tab) for tab in tabs})