Jacob Schmidt 603963c935 Refactor bank UI to bridge-driven single-page flow
- Replace separate bank/ATM pages with a unified `index.html` app bundle
- Split bank init into `initClass`, `initSessionService`, and `initUIBridge`
- Route UI events through `BankUIBridge` and refresh session payloads after sync
2026-03-14 12:11:34 -05:00

832 lines
32 KiB
JavaScript

(function () {
const GarageApp = (window.GarageApp = window.GarageApp || {});
const { h } = GarageApp.runtime;
const WindowTitleBar = window.SharedUI.componentFns.WindowTitleBar;
const store = GarageApp.store;
const actions = GarageApp.actions;
const { categories, garage, nearby, session } = GarageApp.data;
function q(query, values) {
const needle = String(query || "")
.trim()
.toLowerCase();
if (!needle) {
return true;
}
return values.some((value) =>
String(value || "")
.toLowerCase()
.includes(needle),
);
}
function pct(value) {
return Math.max(0, Math.min(100, Math.round(Number(value || 0) * 100)));
}
function categoryLabel(category) {
const match = categories.find(
(entry) => entry.id === String(category || "other").toLowerCase(),
);
return match ? match.label : "Other";
}
function distanceLabel(value) {
return `${Math.round(Number(value || 0))} m`;
}
function plateLabel(value) {
return String(value || "").trim() || "Untracked";
}
function statusLabel(vehicle) {
if (!vehicle) {
return "-";
}
if (vehicle.entryKind === "stored") {
return "Stored";
}
return vehicle.isEmpty === false ? "Crewed" : "Ready";
}
function normalizeHitPointLabel(value) {
return String(value || "")
.replace(/^Hit/i, "")
.replace(/([a-z])([A-Z])/g, "$1 $2")
.replace(/_/g, " ")
.trim();
}
function sameEntry(left, right) {
if (!left || !right) {
return false;
}
return (
String(left.entryKind || "") === String(right.entryKind || "") &&
String(left.plate || "") === String(right.plate || "") &&
String(left.netId || "") === String(right.netId || "")
);
}
function selectedEntry(state) {
if (state.selectedKind === "stored") {
return (
(garage.vehicles || []).find(
(vehicle) =>
String(vehicle.plate || "") === state.selectedId,
) || null
);
}
if (state.selectedKind === "nearby") {
return (
(nearby.vehicles || []).find(
(vehicle) =>
String(vehicle.netId || "") === state.selectedId,
) || null
);
}
return null;
}
function visibleVehicles(vehicles, state) {
return (vehicles || []).filter((vehicle) => {
if (
state.categoryFilter !== "all" &&
String(vehicle.category || "").toLowerCase() !==
state.categoryFilter
) {
return false;
}
return q(state.searchQuery, [
vehicle.displayName,
vehicle.classname,
vehicle.plate,
vehicle.netId,
vehicle.category,
]);
});
}
function stat(label, value, tone = "") {
return h(
"div",
{
className: tone
? `garage-stat-card is-${tone}`
: "garage-stat-card",
},
h("span", { className: "garage-stat-label" }, label),
h("span", { className: "garage-stat-value" }, value),
);
}
function meter(label, percent, tone) {
return h(
"div",
{ className: "garage-meter" },
h(
"div",
{ className: "garage-meter-label-row" },
h("span", { className: "garage-meter-label" }, label),
h("span", { className: "garage-meter-value" }, `${percent}%`),
),
h(
"div",
{ className: "garage-meter-track" },
h("span", {
className: `garage-meter-fill is-${tone}`,
style: { width: `${percent}%` },
}),
),
);
}
function vehicleItem(vehicle, currentSelection) {
const id =
vehicle.entryKind === "stored"
? String(vehicle.plate || "")
: String(vehicle.netId || "");
const isNearby = vehicle.entryKind === "nearby";
return h(
"button",
{
type: "button",
className: sameEntry(vehicle, currentSelection)
? "garage-vehicle-item is-selected"
: "garage-vehicle-item",
onClick: () => actions.selectEntry(vehicle.entryKind, id),
},
h(
"div",
{ className: "garage-vehicle-item-head" },
h(
"div",
{ className: "garage-vehicle-copy" },
h(
"span",
{ className: "garage-vehicle-title" },
vehicle.displayName || vehicle.classname || "Vehicle",
),
h(
"span",
{ className: "garage-vehicle-meta" },
isNearby
? `Nearby ${distanceLabel(vehicle.distance)}`
: `Plate ${plateLabel(vehicle.plate)}`,
),
),
h(
"span",
{
className:
isNearby && vehicle.isEmpty === false
? "garage-badge is-warning"
: "garage-badge",
},
isNearby
? vehicle.isEmpty === false
? "Crewed"
: "Empty"
: categoryLabel(vehicle.category),
),
),
h(
"div",
{ className: "garage-inline-meters" },
meter("Health", pct(vehicle.health), "health"),
meter("Fuel", pct(vehicle.fuel), "fuel"),
),
);
}
function vehicleList(title, eyebrow, scrollId, vehicles, currentSelection) {
return h(
"section",
{ className: "garage-card garage-list-card" },
h(
"div",
{ className: "garage-card-header" },
h(
"div",
null,
h("span", { className: "garage-eyebrow" }, eyebrow),
h("h2", { className: "garage-section-title" }, title),
),
h(
"span",
{ className: "garage-pill" },
`${vehicles.length} ${vehicles.length === 1 ? "Vehicle" : "Vehicles"}`,
),
),
h(
"div",
{
className: "garage-card-body garage-scroll-body",
"data-preserve-scroll-id": scrollId,
},
vehicles.length > 0
? vehicles.map((vehicle) =>
vehicleItem(vehicle, currentSelection),
)
: h(
"div",
{ className: "garage-empty-state" },
h(
"h3",
{ className: "garage-empty-title" },
"No matching vehicles",
),
h(
"p",
{ className: "garage-empty-copy" },
"Adjust the current search or category filter to view more records.",
),
),
),
);
}
function hitPointRows(hitPoints) {
const rows = (Array.isArray(hitPoints) ? hitPoints : [])
.slice()
.sort(
(left, right) =>
Number(right.value || 0) - Number(left.value || 0),
)
.slice(0, 6)
.filter((row) => Number(row.value || 0) > 0);
if (rows.length === 0) {
return h(
"div",
{ className: "garage-empty-inline" },
"No subsystem damage reported.",
);
}
return h(
"div",
{ className: "garage-hitpoint-grid" },
rows.map((row) =>
h(
"div",
{ className: "garage-hitpoint-row" },
h(
"div",
{ className: "garage-hitpoint-copy" },
h(
"span",
{ className: "garage-hitpoint-name" },
normalizeHitPointLabel(row.name) || "Subsystem",
),
row.selection
? h(
"span",
{ className: "garage-hitpoint-selection" },
row.selection,
)
: null,
),
h(
"span",
{ className: "garage-hitpoint-value" },
`${Math.round(Number(row.value || 0) * 100)}%`,
),
),
),
);
}
function detailPanel(currentSelection, state) {
if (!currentSelection) {
return h(
"section",
{ className: "garage-card garage-detail-card" },
h(
"div",
{ className: "garage-card-header" },
h(
"div",
null,
h("span", { className: "garage-eyebrow" }, "Selection"),
h(
"h2",
{ className: "garage-section-title" },
"Vehicle Detail",
),
),
),
h(
"div",
{ className: "garage-card-body garage-detail-empty" },
h(
"h3",
{ className: "garage-empty-title" },
"Select a vehicle",
),
h(
"p",
{ className: "garage-empty-copy" },
"Choose a stored record to retrieve or a nearby vehicle to store.",
),
),
);
}
const isStored = currentSelection.entryKind === "stored";
const pendingAction = String(state.pendingAction || "");
const isBusy =
pendingAction === "retrieve" || pendingAction === "store";
const canRetrieve = isStored && !session.spawnBlocked && !isBusy;
const canStore =
!isStored && currentSelection.isEmpty !== false && !isBusy;
return h(
"section",
{ className: "garage-card garage-detail-card" },
h(
"div",
{ className: "garage-card-header" },
h(
"div",
null,
h(
"span",
{ className: "garage-eyebrow" },
isStored ? "Stored Record" : "Nearby Vehicle",
),
h(
"h2",
{ className: "garage-section-title" },
currentSelection.displayName ||
currentSelection.classname ||
"Vehicle",
),
),
h(
"span",
{
className:
currentSelection.entryKind === "nearby" &&
currentSelection.isEmpty === false
? "garage-badge is-warning"
: "garage-badge",
},
isStored
? `Plate ${plateLabel(currentSelection.plate)}`
: currentSelection.isEmpty === false
? "Crewed"
: "Ready",
),
),
h(
"div",
{ className: "garage-card-body garage-detail-body" },
h(
"div",
{ className: "garage-detail-grid" },
h(
"div",
{ className: "garage-detail-copy" },
h(
"div",
{ className: "garage-detail-meta" },
stat(
"Category",
categoryLabel(currentSelection.category),
),
stat(
"Status",
statusLabel(currentSelection),
currentSelection.entryKind === "nearby" &&
currentSelection.isEmpty === false
? "danger"
: "",
),
stat(
isStored ? "Record" : "Distance",
isStored
? plateLabel(currentSelection.plate)
: distanceLabel(currentSelection.distance),
isStored ? "" : "accent",
),
),
h(
"div",
{ className: "garage-meter-stack" },
meter(
"Health",
pct(currentSelection.health),
"health",
),
meter("Fuel", pct(currentSelection.fuel), "fuel"),
),
h(
"div",
{ className: "garage-action-row" },
isStored
? h(
"button",
{
type: "button",
className:
"garage-btn garage-btn-primary",
disabled: !canRetrieve,
onClick: () =>
actions.requestRetrieveSelected(),
},
pendingAction === "retrieve"
? "Retrieving..."
: "Retrieve Vehicle",
)
: h(
"button",
{
type: "button",
className:
"garage-btn garage-btn-primary",
disabled: !canStore,
onClick: () =>
actions.requestStoreSelected(),
},
pendingAction === "store"
? "Storing..."
: "Store Vehicle",
),
h(
"button",
{
type: "button",
className:
"garage-btn garage-btn-secondary",
disabled: isBusy,
onClick: () => actions.refreshGarage(),
},
"Refresh",
),
),
h(
"p",
{ className: "garage-detail-note" },
isStored
? session.spawnBlocked
? "The garage spawn lane is currently blocked."
: "Retrieve this stored vehicle into the active spawn lane."
: currentSelection.isEmpty === false
? "Only empty nearby vehicles can be stored."
: "Store this nearby vehicle back into persistent garage storage.",
),
),
h(
"div",
{ className: "garage-detail-subsystems" },
h(
"div",
{ className: "garage-subsystem-header" },
h(
"span",
{ className: "garage-eyebrow" },
"Subsystems",
),
h(
"span",
{ className: "garage-detail-caption" },
"Highest damage first",
),
),
hitPointRows(currentSelection.hitPoints),
),
),
),
);
}
GarageApp.components = GarageApp.components || {};
GarageApp.components.App = function App() {
const state = {
categoryFilter: store.getCategoryFilter(),
notice: store.getNotice(),
pendingAction: store.getPendingAction(),
searchQuery: store.getSearchQuery(),
selectedId: store.getSelectedId(),
selectedKind: store.getSelectedKind(),
};
const currentSelection = selectedEntry(state);
const storedVehicles = visibleVehicles(garage.vehicles || [], state);
const nearbyVehicles = visibleVehicles(nearby.vehicles || [], state);
const searchLabel = state.searchQuery
? `Search: ${state.searchQuery}`
: "Live";
return h(
"div",
{ className: "garage-shell" },
WindowTitleBar({
kicker: "FORGE Logistics",
title: "Vehicle Garage",
onClose: () => actions.closeGarage(),
closeLabel: "Close garage interface",
}),
state.notice.text
? h(
"div",
{ className: "garage-toast-stack" },
h(
"div",
{
className:
state.notice.type === "error"
? "garage-toast is-error"
: "garage-toast is-success",
},
state.notice.text,
),
)
: null,
h(
"div",
{ className: "garage-layout" },
h(
"aside",
{ className: "garage-sidebar" },
h(
"section",
{ className: "garage-module" },
h(
"div",
{ className: "garage-module-header" },
h(
"div",
null,
h(
"span",
{ className: "garage-eyebrow" },
"Search",
),
h(
"h2",
{ className: "garage-section-title" },
"Vehicle Records",
),
),
h(
"span",
{ className: "garage-pill" },
searchLabel,
),
),
h(
"div",
{ className: "garage-search-form" },
h("input", {
id: "garage-search-input",
type: "text",
className: "garage-search-input",
placeholder:
"Search by name, plate, or category",
value: state.searchQuery,
}),
h(
"div",
{ className: "garage-search-actions" },
h(
"button",
{
type: "button",
className:
"garage-btn garage-btn-primary",
onClick: () =>
actions.applySearchQuery(
document.getElementById(
"garage-search-input",
)?.value || "",
),
},
"Apply Search",
),
h(
"button",
{
type: "button",
className:
"garage-btn garage-btn-secondary",
onClick: () => actions.clearSearch(),
},
"Clear",
),
),
),
),
h(
"section",
{ className: "garage-module" },
h(
"div",
{ className: "garage-module-header" },
h(
"div",
null,
h(
"span",
{ className: "garage-eyebrow" },
"Filter",
),
h(
"h2",
{ className: "garage-section-title" },
"Vehicle Categories",
),
),
),
h(
"div",
{ className: "garage-category-grid" },
categories.map((category) =>
h(
"button",
{
type: "button",
className:
state.categoryFilter === category.id
? "garage-chip is-active"
: "garage-chip",
onClick: () =>
actions.selectCategory(category.id),
},
category.label,
),
),
),
),
h(
"section",
{ className: "garage-module" },
h(
"div",
{ className: "garage-module-header" },
h(
"div",
null,
h(
"span",
{ className: "garage-eyebrow" },
"Status",
),
h(
"h2",
{ className: "garage-section-title" },
"Garage Summary",
),
),
h(
"button",
{
type: "button",
className:
"garage-btn garage-btn-secondary",
disabled: Boolean(state.pendingAction),
onClick: () => actions.refreshGarage(),
},
"Refresh",
),
),
h(
"div",
{ className: "garage-summary-grid" },
stat(
"Stored",
`${session.capacityUsed}/${session.capacityMax}`,
),
stat("Nearby", session.nearbyCount, "accent"),
stat(
"Spawn Lane",
session.spawnStatus,
session.spawnBlocked ? "danger" : "",
),
),
),
),
h(
"main",
{ className: "garage-main" },
h(
"section",
{ className: "garage-panel" },
h(
"div",
{ className: "garage-panel-header" },
h(
"div",
null,
h(
"span",
{ className: "garage-eyebrow" },
"Operations Bay",
),
h(
"h1",
{ className: "garage-title" },
session.garageName || "Vehicle Garage",
),
),
h(
"span",
{ className: "garage-pill" },
`${session.capacityUsed}/${session.capacityMax} Stored`,
),
),
h(
"div",
{ className: "garage-panel-intro" },
h(
"p",
{ className: "garage-copy" },
"Retrieve stored vehicles into the active spawn lane or store nearby empty vehicles back into persistent ownership records.",
),
),
h(
"div",
{ className: "garage-dashboard" },
vehicleList(
"Stored Vehicles",
"Persistent Records",
"garage-stored-list",
storedVehicles,
currentSelection,
),
vehicleList(
"Nearby Vehicles",
"Store Window",
"garage-nearby-list",
nearbyVehicles,
currentSelection,
),
detailPanel(currentSelection, state),
),
),
),
),
h(
"footer",
{ className: "garage-footer-bar" },
h(
"div",
{ className: "garage-footer" },
h(
"div",
{ className: "garage-footer-block" },
h(
"span",
{ className: "garage-footer-title" },
"Storage Capacity",
),
h(
"span",
{ className: "garage-footer-copy" },
`${session.capacityUsed} of ${session.capacityMax} vehicle slot(s) are currently occupied.`,
),
),
h(
"div",
{ className: "garage-footer-block" },
h(
"span",
{ className: "garage-footer-title" },
"Retrieval Window",
),
h(
"span",
{ className: "garage-footer-copy" },
session.spawnBlocked
? "Spawn lane is blocked. Clear the bay before retrieving another vehicle."
: "Spawn lane is clear. Stored vehicles can be retrieved immediately.",
),
),
h(
"div",
{ className: "garage-footer-block" },
h(
"span",
{ className: "garage-footer-title" },
"Store Rules",
),
h(
"span",
{ className: "garage-footer-copy" },
"Only nearby empty vehicles can be stored. Nearby count updates from the live world state.",
),
),
),
),
);
};
})();