Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

py/facett_demo/app.py

"""facett-demo — Streamlit entrypoint.

Run with the installed command (Streamlit is started in-process)::

    facett-demo                      # 127.0.0.1:8501
    facett-demo run --server.port 8502

or ``streamlit run <site-packages>/facett_demo/app.py``.

Mirrors facett-demo's core surfaces (MAP / GRAPH / POPULATION), each driven by a
LIVE korp-server RPC. The sidebar shows this app's version+git-hash AND the
connected server's version+hash, so it is always obvious which build backs the
demo. Every pane shows an honest "server unreachable / empty" panel rather than a
blank false-green when the server cannot answer.
"""

from __future__ import annotations

import os
from typing import Any

import streamlit as st

from facett_demo.buildinfo import build_info, version_string
from facett_demo.client import (
    DEFAULT_TARGET,
    KORP_EXTRA_HINT,
    KorpClient,
    KorpUnavailable,
    korp_available,
)
from facett_demo.panes import (
    gallery_pane,
    map_pane,
    graph_pane,
    population_pane,
    errcode_pane,
    offline_pane,
)


def _sidebar() -> KorpClient:
    st.sidebar.title("facett-demo")
    st.sidebar.caption("Python/Streamlit companion to facett-demo")

    bi = build_info()
    st.sidebar.markdown("**App build**")
    st.sidebar.code(f"{bi['version']} ({bi['git_hash']})\nsource: {bi['source']}", language=None)

    target = st.sidebar.text_input("korp-server", value=DEFAULT_TARGET, key="server_target")
    client = KorpClient(target=target)

    st.sidebar.markdown("**Server**")
    if not korp_available():
        # The [korp] extra is optional: say so instead of probing a wire that is not here.
        st.sidebar.info(f"korp wire not installed — offline mode\n\n`{KORP_EXTRA_HINT}`")
    else:
        try:
            h = client.health()
            st.sidebar.success(f"connected · {h.status}")
            st.sidebar.code(f"korp {h.version}\nuptime {h.uptime_seconds}s", language=None)
        except KorpUnavailable as e:
            st.sidebar.error("server unreachable")
            st.sidebar.code(str(e), language=None)

    # Dev mode: every canvas shows its navgraph numbers (1, 1.1, 1.1.~3 …). The wasm
    # draws them; Ctrl+Shift+G in a canvas cycles the overlay, Ctrl+G <n> Enter focuses.
    # Starts ON when the app runs with FACETT_DEVID=1 (the same switch as a release
    # facett binary's dev-id chips).
    dev_default = os.environ.get("FACETT_DEVID", "").strip().lower() in ("1", "true", "on", "yes")
    st.sidebar.toggle("dev mode · navgraph numbers", value=dev_default, key="dev_mode")
    st.sidebar.caption("🎨 Komponenter (every group) → the shipped wasm (no RPC, no server)")
    st.sidebar.caption("korp tabs → live RPCs:")
    st.sidebar.caption("🗺 Map → Assets + MapQuery")
    st.sidebar.caption("🕸 Graph → Graph")
    st.sidebar.caption("📊 Population → IcebergTables + TablePreview + Population")
    st.sidebar.caption("🩷 Errors → the facett error-code catalog (static, no RPC)")
    return client


#: The live korp panes, each filed under the facett-demo tab group its surface belongs
#: to (`gallery_pane.GROUPS`): the map under Kartor, the graph navigator under Grafer,
#: the Iceberg browser + population diagrams under Tabeller & diagram, the error-code
#: catalog under App & UI (where the demo keeps `Errors`).
#:
#: The third field is what the tab mounts WITHOUT the korp wire (the optional
#: `[korp]` extra): the same components on the gallery's generated/fixture data,
#: under a banner that says so (`offline_pane`). `None` = the pane needs no korp.
_LIVE_PANES: dict[str, list[tuple[str, Any, list[str] | None]]] = {
    "graph": [("\U0001F578 korp · Graph", graph_pane.render, [graph_pane.COMPONENT])],
    "map": [("\U0001F5FA korp · Map", map_pane.render, [map_pane.COMPONENT])],
    "data": [(
        "\U0001F4CA korp · Population",
        population_pane.render,
        [population_pane.STAT_COMPONENT, population_pane.TABLE_COMPONENT],
    )],
    "ui": [("\U0001FA77 Errors", errcode_pane.render, None)],
}

#: The sub-tab every group opens on: the gallery's wasm components for that group.
COMPONENTS_TAB = "\U0001F3A8 Komponenter"


def main() -> None:
    st.set_page_config(page_title="facett-demo", page_icon="🧭", layout="wide")
    st.title("🧭 facett-demo")
    st.caption(
        "Grouped like facett-demo's tabs (Grafer, Kartor, …). In each group, "
        "**Komponenter** is facett-demo itself: every wrapped component, every pixel drawn "
        "by the Rust wasm on WebGPU, no server needed — the graph components with the "
        "demo's size + generator (100 → 100k, the universe to 1M, generated in the wasm). "
        "The korp tabs beside it mirror facett-demo's live surfaces from the korp-server "
        "gRPC control plane. No mocks, and no JavaScript renderer anywhere."
    )

    client = _sidebar()

    # The GALLERY leads every group, and deliberately so. It is the facett-demo showcase
    # and it needs NO korp-server, no database and no network: a demo whose first tab
    # is empty because a backend is down is a demo nobody can show. The live-RPC panes
    # follow it inside their group.
    mode = gallery_pane.render_header()
    groups = gallery_pane.top_groups()
    for tab, (key, _label) in zip(st.tabs([label for _, label in groups]), groups):
        with tab:
            live = _LIVE_PANES.get(key, [])
            if not live:
                gallery_pane.render_group(key, mode)
                continue
            subs = st.tabs([COMPONENTS_TAB] + [name for name, _, _ in live])
            with subs[0]:
                gallery_pane.render_group(key, mode)
            for sub, (name, pane, offline) in zip(subs[1:], live):
                with sub:
                    if offline is not None and not korp_available():
                        offline_pane.render(name, offline, key=key)
                    else:
                        pane(client)

    st.divider()
    st.caption(f"{version_string()} · server target `{os.environ.get('KORP_SERVER', DEFAULT_TARGET)}`")


# Streamlit (and AppTest) execute this module top-to-bottom on every rerun, so
# main() is called unconditionally — no __main__ guard (there is no double run).
main()