improvement on gm windows, ai
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, h, ref } from "vue";
|
||||
import {
|
||||
useVueTable,
|
||||
getCoreRowModel,
|
||||
@@ -18,6 +18,7 @@ import { useViewerSelectionStore } from "../../ui/stores/viewerSelection";
|
||||
import type { ShipSnapshot } from "../../contractsShips";
|
||||
import type { StationSnapshot } from "../../contractsInfrastructure";
|
||||
import type { FactionSnapshot } from "../../contractsFactions";
|
||||
import type { MarketOrderSnapshot } from "../../contractsEconomy";
|
||||
|
||||
// ── Column ordering composable ─────────────────────────────────────────────
|
||||
|
||||
@@ -85,6 +86,20 @@ const factionMap = computed(() =>
|
||||
new Map(gmStore.factions.map((f) => [f.id, f.label])),
|
||||
);
|
||||
|
||||
const factionColorMap = computed(() =>
|
||||
new Map(gmStore.factions.map((f) => [f.id, f.color])),
|
||||
);
|
||||
|
||||
function renderColorCell(color: string | null | undefined) {
|
||||
const resolved = color && color !== "—" ? color : "#6b7280";
|
||||
return h("div", { class: "flex items-center justify-center" }, [
|
||||
h("span", {
|
||||
class: "inline-block h-3 w-3 rounded-full border border-white/20",
|
||||
style: { backgroundColor: resolved },
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
function titleCaseToken(value: string | null | undefined) {
|
||||
if (!value) return "—";
|
||||
return value
|
||||
@@ -106,6 +121,13 @@ function compactRate(value: number | null | undefined) {
|
||||
return `${sign}${value.toFixed(2)}/s`;
|
||||
}
|
||||
|
||||
function formatCargoAmount(value: number | null | undefined) {
|
||||
if (value == null || Number.isNaN(value)) return "—";
|
||||
const rounded = Math.round(value);
|
||||
if (Math.abs(value - rounded) < 0.005) return String(rounded);
|
||||
return value.toFixed(2).replace(/\.?0+$/, "");
|
||||
}
|
||||
|
||||
function getLeadObjective(faction: FactionSnapshot) {
|
||||
return [...(faction.objectives ?? [])]
|
||||
.sort((left, right) => right.priority - left.priority)
|
||||
@@ -184,6 +206,7 @@ type ShipRow = {
|
||||
id: string;
|
||||
label: string;
|
||||
class: string;
|
||||
factionColor: string;
|
||||
faction: string;
|
||||
system: string;
|
||||
state: string;
|
||||
@@ -201,6 +224,7 @@ const shipRows = computed<ShipRow[]>(() =>
|
||||
id: s.id,
|
||||
label: s.label,
|
||||
class: s.class,
|
||||
factionColor: factionColorMap.value.get(s.factionId) ?? "—",
|
||||
faction: factionMap.value.get(s.factionId) ?? s.factionId,
|
||||
system: s.systemId,
|
||||
state: titleCaseToken(s.state),
|
||||
@@ -218,6 +242,11 @@ const shipColumnHelper = createColumnHelper<ShipRow>();
|
||||
const shipColumns = [
|
||||
shipColumnHelper.accessor("label", { header: "Name" }),
|
||||
shipColumnHelper.accessor("class", { header: "Class" }),
|
||||
shipColumnHelper.accessor("factionColor", {
|
||||
id: "factionColor",
|
||||
header: "Color",
|
||||
cell: (info) => renderColorCell(info.getValue()),
|
||||
}),
|
||||
shipColumnHelper.accessor("faction", { header: "Faction" }),
|
||||
shipColumnHelper.accessor("system", { header: "System" }),
|
||||
shipColumnHelper.accessor("state", { header: "Ship State" }),
|
||||
@@ -226,13 +255,16 @@ const shipColumns = [
|
||||
shipColumnHelper.accessor("phase", { header: "Phase" }),
|
||||
shipColumnHelper.accessor("action", { header: "Current Action" }),
|
||||
shipColumnHelper.accessor("task", { header: "Task" }),
|
||||
shipColumnHelper.accessor("cargo", { header: "Cargo" }),
|
||||
shipColumnHelper.accessor("cargo", {
|
||||
header: "Cargo",
|
||||
cell: (info) => formatCargoAmount(info.getValue()),
|
||||
}),
|
||||
shipColumnHelper.accessor("health", { header: "HP" }),
|
||||
];
|
||||
|
||||
const shipFilter = ref("");
|
||||
const shipSorting = ref<SortingState>([]);
|
||||
const shipOrder = useColumnOrder(["label", "class", "faction", "system", "state", "objective", "behavior", "phase", "action", "task", "cargo", "health"]);
|
||||
const shipOrder = useColumnOrder(["label", "class", "factionColor", "faction", "system", "state", "objective", "behavior", "phase", "action", "task", "cargo", "health"]);
|
||||
|
||||
const shipTable = useVueTable({
|
||||
get data() { return shipRows.value; },
|
||||
@@ -259,22 +291,29 @@ type StationRow = {
|
||||
label: string;
|
||||
category: string;
|
||||
objective: string;
|
||||
factionColor: string;
|
||||
faction: string;
|
||||
system: string;
|
||||
process: string;
|
||||
workforce: string;
|
||||
docked: string;
|
||||
orders: number;
|
||||
orderDetails: MarketOrderSnapshot[];
|
||||
cargo: number;
|
||||
modules: number;
|
||||
};
|
||||
|
||||
const marketOrderMap = computed(() =>
|
||||
new Map(gmStore.marketOrders.map((o) => [o.id, o])),
|
||||
);
|
||||
|
||||
const stationRows = computed<StationRow[]>(() =>
|
||||
gmStore.stations.map((s) => ({
|
||||
id: s.id,
|
||||
label: s.label,
|
||||
category: s.category,
|
||||
objective: titleCaseToken(s.objective),
|
||||
factionColor: factionColorMap.value.get(s.factionId) ?? "—",
|
||||
faction: factionMap.value.get(s.factionId) ?? s.factionId,
|
||||
system: s.systemId,
|
||||
process: s.currentProcesses.length > 0
|
||||
@@ -283,6 +322,10 @@ const stationRows = computed<StationRow[]>(() =>
|
||||
workforce: `${Math.round(s.population)} / ${Math.round(s.populationCapacity)} · ${Math.round(s.workforceEffectiveRatio * 100)}%`,
|
||||
docked: `${s.dockedShips} / ${s.dockingPads}`,
|
||||
orders: s.marketOrderIds.length,
|
||||
orderDetails: s.marketOrderIds.flatMap((id) => {
|
||||
const order = marketOrderMap.value.get(id);
|
||||
return order ? [order] : [];
|
||||
}),
|
||||
cargo: s.inventory.reduce((sum, e) => sum + e.amount, 0),
|
||||
modules: s.installedModules.length,
|
||||
})),
|
||||
@@ -293,19 +336,27 @@ const stationColumns = [
|
||||
stationColumnHelper.accessor("label", { header: "Name" }),
|
||||
stationColumnHelper.accessor("category", { header: "Category" }),
|
||||
stationColumnHelper.accessor("objective", { header: "Objective" }),
|
||||
stationColumnHelper.accessor("factionColor", {
|
||||
id: "factionColor",
|
||||
header: "Color",
|
||||
cell: (info) => renderColorCell(info.getValue()),
|
||||
}),
|
||||
stationColumnHelper.accessor("faction", { header: "Faction" }),
|
||||
stationColumnHelper.accessor("system", { header: "System" }),
|
||||
stationColumnHelper.accessor("process", { header: "Production" }),
|
||||
stationColumnHelper.accessor("workforce", { header: "Workforce" }),
|
||||
stationColumnHelper.accessor("docked", { header: "Docked" }),
|
||||
stationColumnHelper.accessor("orders", { header: "Orders" }),
|
||||
stationColumnHelper.accessor("cargo", { header: "Cargo" }),
|
||||
stationColumnHelper.accessor("cargo", {
|
||||
header: "Cargo",
|
||||
cell: (info) => formatCargoAmount(info.getValue()),
|
||||
}),
|
||||
stationColumnHelper.accessor("modules", { header: "Modules" }),
|
||||
];
|
||||
|
||||
const stationFilter = ref("");
|
||||
const stationSorting = ref<SortingState>([]);
|
||||
const stationOrder = useColumnOrder(["label", "category", "objective", "faction", "system", "process", "workforce", "docked", "orders", "cargo", "modules"]);
|
||||
const stationOrder = useColumnOrder(["label", "category", "objective", "factionColor", "faction", "system", "process", "workforce", "docked", "orders", "cargo", "modules"]);
|
||||
|
||||
const stationTable = useVueTable({
|
||||
get data() { return stationRows.value; },
|
||||
@@ -330,6 +381,7 @@ const stationTable = useVueTable({
|
||||
type FactionRow = {
|
||||
id: string;
|
||||
label: string;
|
||||
color: string;
|
||||
planCycle: number;
|
||||
priority: string;
|
||||
strategicState: string;
|
||||
@@ -353,6 +405,7 @@ const factionRows = computed<FactionRow[]>(() =>
|
||||
return {
|
||||
id: f.id,
|
||||
label: f.label,
|
||||
color: f.color,
|
||||
planCycle: blackboard?.planCycle ?? 0,
|
||||
priority: describeFactionPriority(f),
|
||||
strategicState: describeFactionStrategicState(f),
|
||||
@@ -374,6 +427,10 @@ const factionRows = computed<FactionRow[]>(() =>
|
||||
const factionColumnHelper = createColumnHelper<FactionRow>();
|
||||
const factionColumns = [
|
||||
factionColumnHelper.accessor("label", { header: "Faction" }),
|
||||
factionColumnHelper.accessor("color", {
|
||||
header: "Color",
|
||||
cell: (info) => renderColorCell(info.getValue()),
|
||||
}),
|
||||
factionColumnHelper.accessor("planCycle", { header: "Cycle" }),
|
||||
factionColumnHelper.accessor("priority", { header: "Top Priority" }),
|
||||
factionColumnHelper.accessor("strategicState", { header: "Objective" }),
|
||||
@@ -392,7 +449,7 @@ const factionColumns = [
|
||||
|
||||
const factionFilter = ref("");
|
||||
const factionSorting = ref<SortingState>([]);
|
||||
const factionOrder = useColumnOrder(["label", "planCycle", "priority", "strategicState", "leadStep", "leadTask", "warReadiness", "economy", "threat", "fleets", "systems", "credits", "population", "shipsBuilt", "shipsLost"]);
|
||||
const factionOrder = useColumnOrder(["label", "color", "planCycle", "priority", "strategicState", "leadStep", "leadTask", "warReadiness", "economy", "threat", "fleets", "systems", "credits", "population", "shipsBuilt", "shipsLost"]);
|
||||
|
||||
const factionTable = useVueTable({
|
||||
get data() { return factionRows.value; },
|
||||
@@ -472,6 +529,31 @@ function isShipSelected(id: string) {
|
||||
function isStationSelected(id: string) {
|
||||
return selectedEntityKind.value === "station" && selectedEntityId.value === id;
|
||||
}
|
||||
|
||||
// ── Orders tooltip ─────────────────────────────────────────────────────────
|
||||
|
||||
const ordersTooltip = ref<{
|
||||
visible: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
orders: MarketOrderSnapshot[];
|
||||
}>({ visible: false, x: 0, y: 0, orders: [] });
|
||||
|
||||
function showOrdersTooltip(e: MouseEvent, orders: MarketOrderSnapshot[]) {
|
||||
if (orders.length === 0) return;
|
||||
ordersTooltip.value = { visible: true, x: e.clientX, y: e.clientY, orders };
|
||||
}
|
||||
|
||||
function moveOrdersTooltip(e: MouseEvent) {
|
||||
if (ordersTooltip.value.visible) {
|
||||
ordersTooltip.value.x = e.clientX;
|
||||
ordersTooltip.value.y = e.clientY;
|
||||
}
|
||||
}
|
||||
|
||||
function hideOrdersTooltip() {
|
||||
ordersTooltip.value.visible = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -660,6 +742,15 @@ function isStationSelected(id: string) {
|
||||
>
|
||||
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||
</span>
|
||||
<span
|
||||
v-else-if="cell.column.id === 'orders'"
|
||||
:class="row.original.orderDetails.length > 0 ? 'gm-orders-trigger' : ''"
|
||||
@mouseenter="showOrdersTooltip($event, row.original.orderDetails)"
|
||||
@mousemove="moveOrdersTooltip"
|
||||
@mouseleave="hideOrdersTooltip"
|
||||
>
|
||||
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||
</span>
|
||||
<FlexRender
|
||||
v-else
|
||||
:render="cell.column.columnDef.cell"
|
||||
@@ -740,4 +831,35 @@ function isStationSelected(id: string) {
|
||||
</div>
|
||||
</div>
|
||||
</GmWindow>
|
||||
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="ordersTooltip.visible && ordersTooltip.orders.length > 0"
|
||||
class="gm-orders-tooltip"
|
||||
:style="{ left: `${ordersTooltip.x + 14}px`, top: `${ordersTooltip.y + 14}px` }"
|
||||
>
|
||||
<table class="gm-orders-tooltip-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item</th>
|
||||
<th>Kind</th>
|
||||
<th>Amount</th>
|
||||
<th>Remaining</th>
|
||||
<th>Valuation</th>
|
||||
<th>State</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="order in ordersTooltip.orders" :key="order.id">
|
||||
<td>{{ order.itemId }}</td>
|
||||
<td>{{ titleCaseToken(order.kind) }}</td>
|
||||
<td class="tabular-nums">{{ order.amount }}</td>
|
||||
<td class="tabular-nums">{{ order.remainingAmount }}</td>
|
||||
<td class="tabular-nums">{{ order.valuation }}</td>
|
||||
<td>{{ titleCaseToken(order.state) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user