How-to guides

User interface (UI)

A Komod's browser UI is one or more Blazor components that KOFA's client loads at run time from the package. The UI only displays and sends commands; it has no ports of its own, the data comes from the server Komod.

Project

The UI is a separate project (X.Ui, Microsoft.NET.Sdk.Razor) that shares Package.props with the server project. The KomodClientAssemblies property of Package.props lists the dlls to load. The kofa-komod and kofa-komod-quickstart templates set this up.

View kinds

The [KomodView("<komod id>", ViewKind.X)] attribute binds the component to the Komod, and it derives from KomodComponentBase:

ViewKind Where it appears
Widget A dashboard tile.
Page The instance's full page.
Settings A custom settings editor (otherwise a form is generated).

A Komod can have one of each.

<div style="display:flex;flex-direction:column;gap:6px;font-variant-numeric:tabular-nums">
    <div style="font-size:1.8rem;font-weight:600">
        @(_temperature is { } t ? t.ToString("0.0", CultureInfo.InvariantCulture) + " °C" : "–")
    </div>
    <div style="display:flex;align-items:center;gap:8px">
        <button type="button" @onclick="() => AdjustTargetAsync(-0.5)">−</button>
        <span>cél: @(_target?.ToString("0.0", CultureInfo.InvariantCulture) ?? "–") °C</span>
        <button type="button" @onclick="() => AdjustTargetAsync(+0.5)">+</button>
    </div>
    <div style="font-size:.85rem;opacity:.8">Fűtés: @(_heating ? "BE" : "KI")</div>
    @if (_error is not null)
    {
        <div style="font-size:.8rem;color:#c42b1c">@_error</div>
    }
</div>

Members given by the base class (KomodComponentBase)

Member Meaning
InstanceId, InstanceLabel, KomodId The ids of the instance.
Kofa (IKomodClientContext) Access to the server: subscribe, settings, command, file.
Port("id") A PortRef to one of the instance's ports.
AssetUrl("x.js") The URL of a file in the package's wwwroot/.
InjectAsync, InvokeCommandAsync Shortcuts for submitting an input and for a command.
Settings, SettingsChanged Only in a ViewKind.Settings view: the setting being edited and the change notification.

Live data

SubscribeAsync<T> gives an asynchronous stream. You can subscribe to both output and input ports; the server thins the stream (maxRateHz) and re-subscribes by itself after a package reload.

protected override async Task OnInitializedAsync()
{
    _target = ReadTarget(await Kofa.GetSettingsAsync(InstanceId, _cts.Token));

    // Both an input and an output port can be subscribed; the server thins the stream to maxRateHz.
    _loops.Add(Task.Run(async () =>
    {
        await foreach (var message in Kofa.SubscribeAsync<Scalar>(Port("temperature"), maxRateHz: 5, _cts.Token))
        {
            _temperature = message.Value.Value;
            await InvokeAsync(StateHasChanged);
        }
    }));
    _loops.Add(Task.Run(async () =>
    {
        await foreach (var message in Kofa.SubscribeAsync<BoolValue>(Port("heater"), maxRateHz: 5, _cts.Token))
        {
            _heating = message.Value.Value;
            await InvokeAsync(StateHasChanged);
        }
    }));
}

Note

Stop the subscribing loop in DisposeAsync with the CancellationToken (see the whole template file), otherwise it keeps running after the page is left.

Settings editor

If the automatic form is not enough, write a [KomodView("<id>", ViewKind.Settings)] component. You get the setting being edited in the Settings parameter (filled with defaults), and report a change with the SettingsChanged callback. The change goes into the draft and takes effect after the Apply button.

For changes at run time (from a Widget or Page view) use IKomodClientContext.GetSettingsAsync / SaveSettingsAsync: the server validates, applies live (or restarts if OnSettingsChangedAsync returns false). On an error a KomodClientException comes with a message.

Static files and JavaScript

The UI project's wwwroot/ folder goes into the package, and the server serves it under /komods/<id>/<version>/wwwroot/. In the view AssetUrl gives the URL; a JS module is loaded like this:

_module = await JS.InvokeAsync<IJSObjectReference>("import", AssetUrl("remote-input.js"));

Keyboard, gamepad, pointer capture and similar event handling should stay in the Komod's own JS module.

Warning

The files in wwwroot/ can be downloaded without signing in, so do not put secrets into them. A view should call the server API not with a relative URL but with the methods of IKomodClientContext.

Size on the dashboard

The tile height can be adjusted. If the view can fill the space (map, image, 3D), the root element's height should follow the --kofa-view-height CSS variable, with a default of your own:

<div style="position:relative;height:var(--kofa-view-height, 260px)">…</div>

Styling

A Komod UI currently does not bring its own CSS to the client: give the style inline in the component (the template does so), or load a <link> from wwwroot/. The base font and colors come from the client; avoid fixed colors, for the light and dark theme.

Limits

  • The released client is trimmed, not every framework API is available. kofa-pack warns.
  • The UI cannot reach the server's file system directly; for files see Files.