# UI Built on top of [HyUI](https://github.com/Elliesaur/HyUI), a third-party library that wraps Hytale's native Custom UI API in an HTML-like markup (HYUIML). HyUI is bundled into the Staff jar via the shade plugin so you don't need it installed separately. ## Reading the source Each page lives in [src/main/java/net/kewwbec/staff/ui/](../src/main/java/net/kewwbec/staff/ui/). Pages don't extend the SDK's `InteractiveCustomUIPage` anymore - they're plain classes that build a `PageBuilder` and expose an `open(store)` method. Navigation between pages is `new OtherPage(...).open(store)`. ## Page flow ``` /staffui | v [StaffHubPage] | +-> Player Lookup -> [PlayerLookupPage] (text search, click result for detail) | v [PlayerDetailPage] | +-> Ban / TempBan / Mute / TempMute / Kick / Warn --> [PunishFormPage] +-> Unban / Unmute (applied inline, page re-opens) +-> History --> [HistoryPage] ``` Every non-hub page has a Back button. ESC always closes (lifetime is `CanDismiss`). ## Page reference | Page | Purpose | |---|---| | [StaffHubPage](../src/main/java/net/kewwbec/staff/ui/StaffHubPage.java) | Top-level menu. Currently just routes to Player Lookup; add buttons here for new sections. | | [PlayerLookupPage](../src/main/java/net/kewwbec/staff/ui/PlayerLookupPage.java) | Text input + Search button. Online players are listed first; if you type a name not online, the players_seen table is consulted. Click a result to open detail. | | [PlayerDetailPage](../src/main/java/net/kewwbec/staff/ui/PlayerDetailPage.java) | Player info + active ban/mute lines + action buttons. Unban / Unmute fire immediately (no form); the page reopens with refreshed data. | | [PunishFormPage](../src/main/java/net/kewwbec/staff/ui/PunishFormPage.java) | Reason field, plus duration field for timed types. Validation errors re-open the same page with the entered values and a red error line. | | [HistoryPage](../src/main/java/net/kewwbec/staff/ui/HistoryPage.java) | Last 50 entries for the target. Read-only. | ## HyUI patterns we use All pages follow the same skeleton: ```java public final class SomePage { private final PlayerRef playerRef; private final StaffUIContext sCtx; private final PageBuilder page; public SomePage(PlayerRef playerRef, StaffUIContext sCtx, ...args) { this.playerRef = playerRef; this.sCtx = sCtx; // Build the HYUIML based on constructor state String html = """
"""; this.page = PageBuilder.pageForPlayer(playerRef) .withLifetime(CustomPageLifetime.CanDismiss) .fromHtml(html); // Wire listeners by element id page.addEventListener("someBtn", CustomUIEventBindingType.Activating, (ignored, pCtx) -> { // pCtx.getValue("inputId", String.class).orElse("") to read input values // Navigate by opening a new page instance }); } public void open(Store store) { page.open(store); } } ``` ### Reading input values Inside an event listener, the second argument is the HyUI page context, which exposes `getValue(id, type)`: ```java page.addEventListener("submitBtn", CustomUIEventBindingType.Activating, (ignored, pCtx) -> { String reason = pCtx.getValue("reasonInput", String.class).orElse("").trim(); ... }); ``` ### Re-rendering after state changes We do *not* mutate the page in place. Instead, we open a fresh page instance with new state: ```java new PunishFormPage(playerRef, sCtx, uuid, name, type, durationFromForm, reasonFromForm, "Error: ...").open(store); ``` This is simpler than HyUI's `updatePage(true)` re-render dance and keeps each page render side-effect-free. The cost is rebuilding the HTML each navigation - cheap compared to anything else we do. ### HYUIML class reference (the bits we use) - `
` - root, dims background, captures input - `
` - main framed window with header - `
` - inner content area - `` - clickable button (use `class="back-button"` for back-themed) - `` - text input - `

` / `