Add player onboarding and tactical viewer updates
This commit is contained in:
@@ -76,6 +76,50 @@ function formatAmount(value: number) {
|
||||
return Math.abs(value - rounded) < 0.005 ? String(rounded) : value.toFixed(1);
|
||||
}
|
||||
|
||||
function formatPercent(value: number) {
|
||||
return `${Math.round(value * 100)}%`;
|
||||
}
|
||||
|
||||
function joinDetail(parts: Array<string | null | undefined>) {
|
||||
return parts.filter((part): part is string => !!part && part.trim().length > 0).join(" · ");
|
||||
}
|
||||
|
||||
function describeOrderTarget(order: {
|
||||
itemId?: string | null;
|
||||
targetEntityId?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
nodeId?: string | null;
|
||||
constructionSiteId?: string | null;
|
||||
sourceStationId?: string | null;
|
||||
destinationStationId?: string | null;
|
||||
moduleId?: string | null;
|
||||
}) {
|
||||
return order.itemId
|
||||
?? order.targetEntityId
|
||||
?? order.targetSystemId
|
||||
?? order.nodeId
|
||||
?? order.constructionSiteId
|
||||
?? order.destinationStationId
|
||||
?? order.sourceStationId
|
||||
?? order.moduleId
|
||||
?? "—";
|
||||
}
|
||||
|
||||
function describeSubTaskTarget(subTask: {
|
||||
itemId?: string | null;
|
||||
targetEntityId?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
targetNodeId?: string | null;
|
||||
moduleId?: string | null;
|
||||
}) {
|
||||
return subTask.itemId
|
||||
?? subTask.targetEntityId
|
||||
?? subTask.targetSystemId
|
||||
?? subTask.targetNodeId
|
||||
?? subTask.moduleId
|
||||
?? "—";
|
||||
}
|
||||
|
||||
const selectedShip = computed(() => {
|
||||
if (selectedEntityKind.value !== "ship" || !selectedEntityId.value) {
|
||||
return null;
|
||||
@@ -100,10 +144,10 @@ const playerShipIds = computed(() =>
|
||||
new Set(playerFaction.value?.assetRegistry.shipIds ?? []),
|
||||
);
|
||||
|
||||
const canAccessGm = computed(() => authStore.canAccessGm);
|
||||
const canAccessGmDirectly = computed(() => authStore.canAccessGm && !authStore.isActingAsAlternateIdentity);
|
||||
|
||||
const canDirectControlSelectedShip = computed(() =>
|
||||
!!selectedShip.value && (canAccessGm.value || playerShipIds.value.has(selectedShip.value.id)),
|
||||
!!selectedShip.value && (canAccessGmDirectly.value || playerShipIds.value.has(selectedShip.value.id)),
|
||||
);
|
||||
|
||||
const directOrders = computed(() =>
|
||||
@@ -135,20 +179,208 @@ const formBehaviorNotes = computed(() =>
|
||||
getShipBehaviorNotes(behaviorForm.kind),
|
||||
);
|
||||
|
||||
watch(selectedShip, (ship) => {
|
||||
if (!ship) {
|
||||
return;
|
||||
const shipStatusRows = computed(() => {
|
||||
if (!selectedShip.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
behaviorForm.kind = ship.defaultBehavior.kind;
|
||||
behaviorForm.areaSystemId = ship.defaultBehavior.areaSystemId ?? ship.systemId ?? "";
|
||||
behaviorForm.itemId = ship.defaultBehavior.itemId ?? "ore";
|
||||
mineOrderForm.systemId = ship.systemId ?? "";
|
||||
mineOrderForm.itemId = "ore";
|
||||
moveOrderSystemId.value = ship.systemId ?? "";
|
||||
actionStatus.value = "";
|
||||
actionError.value = "";
|
||||
}, { immediate: true });
|
||||
return [
|
||||
{ label: "State", value: titleCase(selectedShip.value.state) },
|
||||
{ label: "Behavior", value: getShipBehaviorLabel(selectedShip.value.defaultBehavior.kind) },
|
||||
{ label: "Control", value: titleCase(selectedShip.value.controlSourceKind) },
|
||||
{ label: "Assignment", value: selectedShip.value.assignment?.kind ?? "unassigned" },
|
||||
{
|
||||
label: "Plan",
|
||||
value: selectedShip.value.activePlan
|
||||
? `${selectedShip.value.activePlan.kind} · ${titleCase(selectedShip.value.activePlan.status)}`
|
||||
: "none",
|
||||
},
|
||||
{ label: "Failure", value: selectedShip.value.lastAccessFailureReason ?? "none" },
|
||||
{ label: "Commander", value: selectedShip.value.commanderId ?? "none" },
|
||||
{ label: "Docked", value: selectedShip.value.dockedStationId ?? "no" },
|
||||
];
|
||||
});
|
||||
|
||||
const shipCargoSummaryRows = computed(() => {
|
||||
if (!selectedShip.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const usedCargo = selectedShip.value.inventory.reduce((sum, entry) => sum + entry.amount, 0);
|
||||
return [
|
||||
{ label: "Used", value: formatAmount(usedCargo) },
|
||||
{ label: "Capacity", value: formatAmount(selectedShip.value.cargoCapacity) },
|
||||
{ label: "Free", value: formatAmount(Math.max(selectedShip.value.cargoCapacity - usedCargo, 0)) },
|
||||
{ label: "Travel", value: `${formatAmount(selectedShip.value.travelSpeed)} ${selectedShip.value.travelSpeedUnit}` },
|
||||
{ label: "Hull", value: formatAmount(selectedShip.value.health) },
|
||||
{ label: "Regime", value: titleCase(selectedShip.value.spatialState.movementRegime) },
|
||||
];
|
||||
});
|
||||
|
||||
const shipCargoRows = computed(() =>
|
||||
selectedShip.value?.inventory.map((entry) => ({
|
||||
key: entry.itemId,
|
||||
ware: entry.itemId,
|
||||
amount: formatAmount(entry.amount),
|
||||
})) ?? [],
|
||||
);
|
||||
|
||||
const shipBehaviorRows = computed(() => {
|
||||
if (!selectedShip.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{ label: "Area", value: selectedShip.value.defaultBehavior.areaSystemId ?? "none" },
|
||||
{ label: "Item", value: selectedShip.value.defaultBehavior.itemId ?? "none" },
|
||||
{ label: "Home Station", value: selectedShip.value.defaultBehavior.homeStationId ?? "none" },
|
||||
{ label: "Target", value: selectedShip.value.defaultBehavior.targetEntityId ?? "none" },
|
||||
{ label: "Range", value: String(selectedShip.value.defaultBehavior.maxSystemRange) },
|
||||
{ label: "Known Only", value: selectedShip.value.defaultBehavior.knownStationsOnly ? "yes" : "no" },
|
||||
];
|
||||
});
|
||||
|
||||
const directOrderRows = computed(() =>
|
||||
directOrders.value.map((order) => ({
|
||||
id: order.id,
|
||||
label: getShipOrderLabel(order.kind),
|
||||
status: titleCase(order.status),
|
||||
target: describeOrderTarget(order),
|
||||
detail: joinDetail([
|
||||
`P${order.priority}`,
|
||||
titleCase(order.sourceKind),
|
||||
order.failureReason ?? undefined,
|
||||
]),
|
||||
})),
|
||||
);
|
||||
|
||||
const behaviorOrderRows = computed(() =>
|
||||
behaviorOrders.value.map((order) => ({
|
||||
id: order.id,
|
||||
label: getShipOrderLabel(order.kind),
|
||||
status: titleCase(order.status),
|
||||
target: describeOrderTarget(order),
|
||||
detail: joinDetail([
|
||||
`P${order.priority}`,
|
||||
getShipOrderSupportStatusLabel(order.kind) ?? undefined,
|
||||
getShipOrderNotes(order.kind) ?? undefined,
|
||||
order.failureReason ?? undefined,
|
||||
]),
|
||||
})),
|
||||
);
|
||||
|
||||
const shipPlanRows = computed(() => {
|
||||
if (!selectedShip.value?.activePlan) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return selectedShip.value.activePlan.steps.flatMap((step) => {
|
||||
const stepRow = {
|
||||
id: step.id,
|
||||
scope: "Step",
|
||||
activity: step.summary || titleCase(step.kind),
|
||||
status: titleCase(step.status),
|
||||
detail: joinDetail([
|
||||
step.blockingReason ?? undefined,
|
||||
`${step.subTasks.length} subtasks`,
|
||||
]),
|
||||
isSubTask: false,
|
||||
};
|
||||
|
||||
const subTaskRows = step.subTasks.map((subTask) => ({
|
||||
id: subTask.id,
|
||||
scope: "Subtask",
|
||||
activity: subTask.summary || titleCase(subTask.kind),
|
||||
status: titleCase(subTask.status),
|
||||
detail: joinDetail([
|
||||
describeSubTaskTarget(subTask),
|
||||
subTask.blockingReason ?? undefined,
|
||||
`${Math.round(subTask.progress * 100)}%`,
|
||||
]),
|
||||
isSubTask: true,
|
||||
}));
|
||||
|
||||
return [stepRow, ...subTaskRows];
|
||||
});
|
||||
});
|
||||
|
||||
const stationStatusRows = computed(() => {
|
||||
if (!selectedStation.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{ label: "Category", value: titleCase(selectedStation.value.category) },
|
||||
{ label: "Objective", value: titleCase(selectedStation.value.objective) },
|
||||
{ label: "Docked", value: `${selectedStation.value.dockedShips} / ${selectedStation.value.dockingPads}` },
|
||||
{
|
||||
label: "Population",
|
||||
value: `${formatAmount(selectedStation.value.population)} / ${formatAmount(selectedStation.value.populationCapacity)}`,
|
||||
},
|
||||
{ label: "Workforce", value: formatAmount(selectedStation.value.workforceRequired) },
|
||||
{ label: "Efficiency", value: formatPercent(selectedStation.value.workforceEffectiveRatio) },
|
||||
{ label: "Commander", value: selectedStation.value.commanderId ?? "none" },
|
||||
{ label: "Policy", value: selectedStation.value.policySetId ?? "none" },
|
||||
];
|
||||
});
|
||||
|
||||
const stationModuleRows = computed(() =>
|
||||
selectedStation.value?.installedModules.map((moduleId) => ({
|
||||
key: moduleId,
|
||||
module: moduleNameById.get(moduleId) ?? moduleId,
|
||||
moduleId,
|
||||
})) ?? [],
|
||||
);
|
||||
|
||||
const stationStorageRows = computed(() =>
|
||||
selectedStation.value?.storageUsage.map((entry) => ({
|
||||
key: entry.storageClass,
|
||||
storageClass: titleCase(entry.storageClass),
|
||||
used: formatAmount(entry.used),
|
||||
capacity: formatAmount(entry.capacity),
|
||||
fill: entry.capacity > 0 ? formatPercent(entry.used / entry.capacity) : "0%",
|
||||
})) ?? [],
|
||||
);
|
||||
|
||||
const stationInventoryRows = computed(() =>
|
||||
selectedStation.value?.inventory.map((entry) => ({
|
||||
key: entry.itemId,
|
||||
ware: entry.itemId,
|
||||
amount: formatAmount(entry.amount),
|
||||
})) ?? [],
|
||||
);
|
||||
|
||||
const stationProcessRows = computed(() =>
|
||||
selectedStation.value?.currentProcesses.map((process) => ({
|
||||
key: `${process.lane}-${process.label}`,
|
||||
lane: process.lane,
|
||||
label: process.label,
|
||||
progress: formatPercent(process.progress),
|
||||
timing: `${Math.ceil(process.timeRemainingSeconds)}s / ${Math.ceil(process.cycleSeconds)}s`,
|
||||
})) ?? [],
|
||||
);
|
||||
|
||||
watch(
|
||||
() => `${selectedEntityKind.value ?? "none"}:${selectedEntityId.value ?? "none"}`,
|
||||
() => {
|
||||
const ship = selectedShip.value;
|
||||
if (!ship) {
|
||||
actionStatus.value = "";
|
||||
actionError.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
behaviorForm.kind = ship.defaultBehavior.kind;
|
||||
behaviorForm.areaSystemId = ship.defaultBehavior.areaSystemId ?? ship.systemId ?? "";
|
||||
behaviorForm.itemId = ship.defaultBehavior.itemId ?? "ore";
|
||||
mineOrderForm.systemId = ship.systemId ?? "";
|
||||
mineOrderForm.itemId = "ore";
|
||||
moveOrderSystemId.value = ship.systemId ?? "";
|
||||
actionStatus.value = "";
|
||||
actionError.value = "";
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function focusShip(cameraMode?: "follow" | "tactical") {
|
||||
if (!selectedShip.value) {
|
||||
@@ -357,36 +589,52 @@ async function clearOrders() {
|
||||
</div>
|
||||
<div class="entity-inspector-panel__actions">
|
||||
<button type="button" class="entity-inspector-panel__action" @click="focusShip('tactical')">Focus</button>
|
||||
<button type="button" class="entity-inspector-panel__action" @click="focusShip('follow')">Follow</button>
|
||||
<button type="button" class="entity-inspector-panel__action" @click="focusShip('follow')">Track</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="entity-inspector-section">
|
||||
<h4>Status</h4>
|
||||
<div class="entity-inspector-grid">
|
||||
<div><span>State</span><strong>{{ titleCase(selectedShip.state) }}</strong></div>
|
||||
<div><span>Behavior</span><strong>{{ getShipBehaviorLabel(selectedShip.defaultBehavior.kind) }}</strong></div>
|
||||
<div><span>Control</span><strong>{{ selectedShip.controlSourceKind }}</strong></div>
|
||||
<div><span>Assignment</span><strong>{{ selectedShip.assignment?.kind ?? "unassigned" }}</strong></div>
|
||||
<div><span>Plan</span><strong>{{ selectedShip.activePlan ? `${selectedShip.activePlan.kind} · ${selectedShip.activePlan.status}` : "none" }}</strong></div>
|
||||
<div><span>Failure</span><strong>{{ selectedShip.lastAccessFailureReason ?? "none" }}</strong></div>
|
||||
<div class="entity-inspector-table-wrap">
|
||||
<table class="entity-inspector-table entity-inspector-table--kv">
|
||||
<tbody>
|
||||
<tr v-for="row in shipStatusRows" :key="row.label">
|
||||
<th scope="row">{{ row.label }}</th>
|
||||
<td>{{ row.value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="entity-inspector-section">
|
||||
<h4>Cargo</h4>
|
||||
<div class="entity-inspector-grid">
|
||||
<div><span>Used</span><strong>{{ formatAmount(selectedShip.inventory.reduce((sum, entry) => sum + entry.amount, 0)) }}</strong></div>
|
||||
<div><span>Capacity</span><strong>{{ formatAmount(selectedShip.cargoCapacity) }}</strong></div>
|
||||
<div><span>Travel</span><strong>{{ formatAmount(selectedShip.travelSpeed) }} {{ selectedShip.travelSpeedUnit }}</strong></div>
|
||||
<div><span>Hull</span><strong>{{ formatAmount(selectedShip.health) }}</strong></div>
|
||||
<div class="entity-inspector-table-wrap">
|
||||
<table class="entity-inspector-table entity-inspector-table--kv">
|
||||
<tbody>
|
||||
<tr v-for="row in shipCargoSummaryRows" :key="row.label">
|
||||
<th scope="row">{{ row.label }}</th>
|
||||
<td>{{ row.value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="shipCargoRows.length > 0" class="entity-inspector-table-wrap">
|
||||
<table class="entity-inspector-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Ware</th>
|
||||
<th scope="col" class="entity-inspector-table__numeric">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in shipCargoRows" :key="row.key">
|
||||
<td>{{ row.ware }}</td>
|
||||
<td class="entity-inspector-table__numeric">{{ row.amount }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<ul v-if="selectedShip.inventory.length > 0" class="entity-inspector-list">
|
||||
<li v-for="entry in selectedShip.inventory" :key="entry.itemId">
|
||||
<span>{{ entry.itemId }}</span>
|
||||
<strong>{{ formatAmount(entry.amount) }}</strong>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else class="entity-inspector-empty">No cargo.</div>
|
||||
</div>
|
||||
|
||||
@@ -395,13 +643,15 @@ async function clearOrders() {
|
||||
<div v-if="selectedBehaviorStatus || selectedBehaviorNotes" class="entity-inspector-note">
|
||||
{{ [selectedBehaviorStatus, selectedBehaviorNotes].filter(Boolean).join(" · ") }}
|
||||
</div>
|
||||
<div class="entity-inspector-grid">
|
||||
<div><span>Area</span><strong>{{ selectedShip.defaultBehavior.areaSystemId ?? "none" }}</strong></div>
|
||||
<div><span>Item</span><strong>{{ selectedShip.defaultBehavior.itemId ?? "none" }}</strong></div>
|
||||
<div><span>Home Station</span><strong>{{ selectedShip.defaultBehavior.homeStationId ?? "none" }}</strong></div>
|
||||
<div><span>Target</span><strong>{{ selectedShip.defaultBehavior.targetEntityId ?? "none" }}</strong></div>
|
||||
<div><span>Range</span><strong>{{ selectedShip.defaultBehavior.maxSystemRange }}</strong></div>
|
||||
<div><span>Known Only</span><strong>{{ selectedShip.defaultBehavior.knownStationsOnly ? "yes" : "no" }}</strong></div>
|
||||
<div class="entity-inspector-table-wrap">
|
||||
<table class="entity-inspector-table entity-inspector-table--kv">
|
||||
<tbody>
|
||||
<tr v-for="row in shipBehaviorRows" :key="row.label">
|
||||
<th scope="row">{{ row.label }}</th>
|
||||
<td>{{ row.value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="canDirectControlSelectedShip" class="entity-inspector-form">
|
||||
<label class="entity-inspector-field">
|
||||
@@ -473,52 +723,86 @@ async function clearOrders() {
|
||||
</div>
|
||||
<div v-if="actionStatus" class="entity-inspector-message entity-inspector-message--ok">{{ actionStatus }}</div>
|
||||
<div v-if="actionError" class="entity-inspector-message entity-inspector-message--error">{{ actionError }}</div>
|
||||
<ul v-if="directOrders.length > 0" class="entity-inspector-list">
|
||||
<li v-for="order in directOrders" :key="order.id">
|
||||
<span>{{ getShipOrderLabel(order.kind) }} · {{ order.status }}</span>
|
||||
<div class="entity-inspector-order-actions">
|
||||
<strong>{{ order.itemId ?? order.targetEntityId ?? order.targetSystemId ?? "—" }}</strong>
|
||||
<button
|
||||
v-if="canDirectControlSelectedShip"
|
||||
type="button"
|
||||
class="entity-inspector-order-remove"
|
||||
:disabled="actionBusy"
|
||||
@click="removeOrder(order.id)"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="directOrderRows.length > 0" class="entity-inspector-table-wrap">
|
||||
<table class="entity-inspector-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Order</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Target</th>
|
||||
<th scope="col">Detail</th>
|
||||
<th v-if="canDirectControlSelectedShip" scope="col" class="entity-inspector-table__action-col">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="order in directOrderRows" :key="order.id">
|
||||
<td>{{ order.label }}</td>
|
||||
<td>{{ order.status }}</td>
|
||||
<td>{{ order.target }}</td>
|
||||
<td class="entity-inspector-table__detail">{{ order.detail }}</td>
|
||||
<td v-if="canDirectControlSelectedShip" class="entity-inspector-table__action-col">
|
||||
<button
|
||||
type="button"
|
||||
class="entity-inspector-order-remove"
|
||||
:disabled="actionBusy"
|
||||
@click="removeOrder(order.id)"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else class="entity-inspector-empty">No direct orders queued.</div>
|
||||
<div class="entity-inspector-divider">
|
||||
<span>Behavior: {{ getShipBehaviorLabel(selectedShip.defaultBehavior.kind) }}</span>
|
||||
</div>
|
||||
<ul v-if="behaviorOrders.length > 0" class="entity-inspector-list">
|
||||
<li v-for="order in behaviorOrders" :key="order.id">
|
||||
<span>{{ getShipOrderLabel(order.kind) }} · {{ order.status }}</span>
|
||||
<strong>{{ [order.itemId ?? order.targetEntityId ?? order.targetSystemId ?? "—", getShipOrderSupportStatusLabel(order.kind), getShipOrderNotes(order.kind)].filter(Boolean).join(" · ") }}</strong>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="behaviorOrderRows.length > 0" class="entity-inspector-table-wrap">
|
||||
<table class="entity-inspector-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Order</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Target</th>
|
||||
<th scope="col">Detail</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="order in behaviorOrderRows" :key="order.id">
|
||||
<td>{{ order.label }}</td>
|
||||
<td>{{ order.status }}</td>
|
||||
<td>{{ order.target }}</td>
|
||||
<td class="entity-inspector-table__detail">{{ order.detail }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else class="entity-inspector-empty">No behavior orders queued.</div>
|
||||
</div>
|
||||
|
||||
<div class="entity-inspector-section">
|
||||
<h4>Plan Steps</h4>
|
||||
<ul v-if="selectedShip.activePlan" class="entity-inspector-plan">
|
||||
<li v-for="step in selectedShip.activePlan.steps" :key="step.id">
|
||||
<div class="entity-inspector-plan__step">
|
||||
<span>{{ step.kind }} · {{ step.status }}</span>
|
||||
<strong>{{ step.blockingReason ?? "ok" }}</strong>
|
||||
</div>
|
||||
<ul class="entity-inspector-subtasks">
|
||||
<li v-for="subTask in step.subTasks" :key="subTask.id">
|
||||
<span>{{ subTask.kind }} · {{ subTask.status }}</span>
|
||||
<strong>{{ subTask.blockingReason ?? `${Math.round(subTask.progress * 100)}%` }}</strong>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="shipPlanRows.length > 0" class="entity-inspector-table-wrap">
|
||||
<table class="entity-inspector-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Scope</th>
|
||||
<th scope="col">Activity</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Detail</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in shipPlanRows" :key="row.id" :class="row.isSubTask ? 'entity-inspector-table__row--subtask' : ''">
|
||||
<td>{{ row.scope }}</td>
|
||||
<td :class="row.isSubTask ? 'entity-inspector-table__subtask' : ''">{{ row.activity }}</td>
|
||||
<td>{{ row.status }}</td>
|
||||
<td class="entity-inspector-table__detail">{{ row.detail }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else class="entity-inspector-empty">No active plan.</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -537,46 +821,102 @@ async function clearOrders() {
|
||||
|
||||
<div class="entity-inspector-section">
|
||||
<h4>Status</h4>
|
||||
<div class="entity-inspector-grid">
|
||||
<div><span>Category</span><strong>{{ titleCase(selectedStation.category) }}</strong></div>
|
||||
<div><span>Objective</span><strong>{{ titleCase(selectedStation.objective) }}</strong></div>
|
||||
<div><span>Docked</span><strong>{{ selectedStation.dockedShips }} / {{ selectedStation.dockingPads }}</strong></div>
|
||||
<div><span>Population</span><strong>{{ formatAmount(selectedStation.population) }} / {{ formatAmount(selectedStation.populationCapacity) }}</strong></div>
|
||||
<div><span>Workforce</span><strong>{{ formatAmount(selectedStation.workforceRequired) }}</strong></div>
|
||||
<div><span>Efficiency</span><strong>{{ Math.round(selectedStation.workforceEffectiveRatio * 100) }}%</strong></div>
|
||||
<div class="entity-inspector-table-wrap">
|
||||
<table class="entity-inspector-table entity-inspector-table--kv">
|
||||
<tbody>
|
||||
<tr v-for="row in stationStatusRows" :key="row.label">
|
||||
<th scope="row">{{ row.label }}</th>
|
||||
<td>{{ row.value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="entity-inspector-section">
|
||||
<h4>Modules</h4>
|
||||
<ul v-if="selectedStation.installedModules.length > 0" class="entity-inspector-list">
|
||||
<li v-for="moduleId in selectedStation.installedModules" :key="moduleId">
|
||||
<span>{{ moduleNameById.get(moduleId) ?? moduleId }}</span>
|
||||
<strong>{{ moduleId }}</strong>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="stationModuleRows.length > 0" class="entity-inspector-table-wrap">
|
||||
<table class="entity-inspector-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Module</th>
|
||||
<th scope="col">Id</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in stationModuleRows" :key="row.key">
|
||||
<td>{{ row.module }}</td>
|
||||
<td>{{ row.moduleId }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else class="entity-inspector-empty">No modules installed.</div>
|
||||
</div>
|
||||
|
||||
<div class="entity-inspector-section">
|
||||
<h4>Storage</h4>
|
||||
<ul v-if="selectedStation.inventory.length > 0" class="entity-inspector-list">
|
||||
<li v-for="entry in selectedStation.inventory" :key="entry.itemId">
|
||||
<span>{{ entry.itemId }}</span>
|
||||
<strong>{{ formatAmount(entry.amount) }}</strong>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else class="entity-inspector-empty">No inventory.</div>
|
||||
<div v-if="stationStorageRows.length > 0" class="entity-inspector-table-wrap">
|
||||
<table class="entity-inspector-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Class</th>
|
||||
<th scope="col" class="entity-inspector-table__numeric">Used</th>
|
||||
<th scope="col" class="entity-inspector-table__numeric">Capacity</th>
|
||||
<th scope="col" class="entity-inspector-table__numeric">Fill</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in stationStorageRows" :key="row.key">
|
||||
<td>{{ row.storageClass }}</td>
|
||||
<td class="entity-inspector-table__numeric">{{ row.used }}</td>
|
||||
<td class="entity-inspector-table__numeric">{{ row.capacity }}</td>
|
||||
<td class="entity-inspector-table__numeric">{{ row.fill }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="stationInventoryRows.length > 0" class="entity-inspector-table-wrap">
|
||||
<table class="entity-inspector-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Ware</th>
|
||||
<th scope="col" class="entity-inspector-table__numeric">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in stationInventoryRows" :key="row.key">
|
||||
<td>{{ row.ware }}</td>
|
||||
<td class="entity-inspector-table__numeric">{{ row.amount }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else-if="stationStorageRows.length === 0" class="entity-inspector-empty">No inventory.</div>
|
||||
</div>
|
||||
|
||||
<div class="entity-inspector-section">
|
||||
<h4>Production</h4>
|
||||
<ul v-if="selectedStation.currentProcesses.length > 0" class="entity-inspector-list">
|
||||
<li v-for="process in selectedStation.currentProcesses" :key="`${process.lane}-${process.label}`">
|
||||
<span>{{ process.label }}</span>
|
||||
<strong>{{ Math.round(process.progress * 100) }}% · {{ Math.ceil(process.timeRemainingSeconds) }}s</strong>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="stationProcessRows.length > 0" class="entity-inspector-table-wrap">
|
||||
<table class="entity-inspector-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Lane</th>
|
||||
<th scope="col">Process</th>
|
||||
<th scope="col" class="entity-inspector-table__numeric">Progress</th>
|
||||
<th scope="col" class="entity-inspector-table__numeric">Timing</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in stationProcessRows" :key="row.key">
|
||||
<td>{{ row.lane }}</td>
|
||||
<td>{{ row.label }}</td>
|
||||
<td class="entity-inspector-table__numeric">{{ row.progress }}</td>
|
||||
<td class="entity-inspector-table__numeric">{{ row.timing }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else class="entity-inspector-empty">No active processes.</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user