@7365admin1/layer-common 4.97.1-staging.607 → 4.97.1-staging.608
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Robot/BuildingMap.vue +21 -7
- package/components/Robot/Detail.vue +20 -1
- package/components/Robot/MiniMap.vue +11 -5
- package/components/Robot/SiteFleet.vue +14 -1
- package/package.json +1 -1
- package/utils/robot-display.ts +15 -3
- package/utils/robot-floors.ts +22 -0
- package/utils/robot-maps.ts +5 -2
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
</div>
|
|
84
84
|
<div class="hero-bmap__seg" role="group" aria-label="Floor">
|
|
85
85
|
<button type="button" :aria-pressed="focus === null" @click="focus = null">All</button>
|
|
86
|
-
<button v-for="f in floors" :key="f" type="button" :aria-pressed="focus === f" @click="focus = f">{{
|
|
86
|
+
<button v-for="f in floors" :key="f" type="button" :aria-pressed="focus === f" @click="focus = f">{{ levelName(f) }}</button>
|
|
87
87
|
</div>
|
|
88
88
|
<span class="hero-bmap__readout">{{ cameraReadout(cam) }}</span>
|
|
89
89
|
<span class="hero-bmap__readout">Drag to rotate · shift+drag or right-drag to pan · scroll to zoom</span>
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
</button>
|
|
185
185
|
</div>
|
|
186
186
|
|
|
187
|
-
<div class="hero-bmap__floorlabel" :style="{ transform: `translateZ(6px) ${unskew}` }">{{
|
|
187
|
+
<div class="hero-bmap__floorlabel" :style="{ transform: `translateZ(6px) ${unskew}` }">{{ levelName(f.floorNo) }}</div>
|
|
188
188
|
</div>
|
|
189
189
|
|
|
190
190
|
<div
|
|
@@ -212,7 +212,7 @@
|
|
|
212
212
|
<aside class="hero-bmap__aside">
|
|
213
213
|
<div class="hero-aside__title hero-bmap__asidetitle">Floors · top down</div>
|
|
214
214
|
<div v-for="f in legend" :key="f.floorNo" class="hero-bmap__legendrow">
|
|
215
|
-
<b>{{
|
|
215
|
+
<b>{{ levelName(f.floorNo) }}</b>
|
|
216
216
|
<div v-if="f.robotMaps.length" class="hero-mapsel__hint">Robot map: {{ f.robotMaps.map((m) => m.label + (m.current ? " (robot is here)" : "")).join(", ") }}</div>
|
|
217
217
|
<div class="hero-bmap__chips">
|
|
218
218
|
<button v-for="r in f.robots" :key="r.id" type="button" class="hero-bmap__chip" @click="emit('open', r.id)"><span :class="`hero-tone--${tone(r)}`">{{ r.name }}</span></button>
|
|
@@ -242,11 +242,12 @@ import { floorAspect, floorDoors, placementInWorld, planBoxIn, routeOverlay, vie
|
|
|
242
242
|
import useRobotLive, { siteStatesFeed } from "../../composables/useRobotLive";
|
|
243
243
|
import { openDoorNames } from "../../utils/robot-fleet-core";
|
|
244
244
|
import { robotMapViews, robotMapsByFloor, type TRobotMapView } from "../../utils/robot-maps";
|
|
245
|
+
import { siteFloorKey, siteFloorLabel, type TSiteFloor } from "../../utils/robot-floors";
|
|
245
246
|
import {
|
|
246
247
|
BUILDING_TAG,
|
|
247
248
|
nameTagSide,
|
|
248
249
|
brandName,
|
|
249
|
-
|
|
250
|
+
siteFloorOf,
|
|
250
251
|
mapFloors,
|
|
251
252
|
mapRobots,
|
|
252
253
|
pickFloor,
|
|
@@ -319,7 +320,19 @@ const siteLabel = computed(() => props.siteName || names.value.siteName || "This
|
|
|
319
320
|
const ids = computed(() => rows.value.map((r) => String(r._id)));
|
|
320
321
|
const { states, connected } = useRobotLive(ids, siteStatesFeed());
|
|
321
322
|
const routes = ref<Record<string, TPatrolRouteView[]>>({});
|
|
322
|
-
const { getPatrolRoutes, getRobotBrands, getRobotMaps } = useRobots();
|
|
323
|
+
const { getPatrolRoutes, getRobotBrands, getRobotMaps, getSiteFloors } = useRobots();
|
|
324
|
+
/** The site's floor names: the robots' own maps and routes go on the site floor (the PadBot's 3 = 34). */
|
|
325
|
+
const siteFloors = ref<TSiteFloor[]>([]);
|
|
326
|
+
watch(
|
|
327
|
+
() => props.site,
|
|
328
|
+
async (site) => {
|
|
329
|
+
siteFloors.value = [];
|
|
330
|
+
const res = await getSiteFloors(site).catch(() => null);
|
|
331
|
+
if (site === props.site) siteFloors.value = res?.floors ?? [];
|
|
332
|
+
},
|
|
333
|
+
{ immediate: true },
|
|
334
|
+
);
|
|
335
|
+
const levelName = (f: number) => siteFloorLabel(siteFloors.value, f, states.value);
|
|
323
336
|
/** Each security robot's own maps (PadBot), read once per robot: drawn on their floor when the floor has no saved plan. */
|
|
324
337
|
const robotMaps = ref<Record<string, Array<Record<string, any>>>>({});
|
|
325
338
|
watch(ids, async (list) => {
|
|
@@ -346,7 +359,8 @@ const shapes = computed<TEditorShape[]>(() => shapesFromMap(map.value));
|
|
|
346
359
|
const robotFloorMaps = computed(() => {
|
|
347
360
|
const all: TRobotMapView[] = [];
|
|
348
361
|
for (const id of ids.value) {
|
|
349
|
-
|
|
362
|
+
const site = { key: (n: number) => siteFloorKey(siteFloors.value, id, n), name: levelName };
|
|
363
|
+
for (const m of robotMapViews(robotMaps.value[id], states.value[id]?.mapId ?? null, routes.value[id] ?? [], site)) {
|
|
350
364
|
const i = all.findIndex((x) => x.mapId === m.mapId);
|
|
351
365
|
if (i < 0) all.push(m);
|
|
352
366
|
else if (m.current) all[i] = { ...all[i]!, current: true };
|
|
@@ -364,7 +378,7 @@ const floorRoutes = (floorNo: number) => {
|
|
|
364
378
|
for (const id of ids.value) {
|
|
365
379
|
const st = states.value[id];
|
|
366
380
|
for (const r of routes.value[id] ?? []) {
|
|
367
|
-
const f = r.floorNo
|
|
381
|
+
const f = r.floorNo !== null && r.floorNo !== undefined ? siteFloorKey(siteFloors.value, id, r.floorNo) : siteFloorOf(st);
|
|
368
382
|
if (f === floorNo && !seen.has(r.id)) seen.set(r.id, r);
|
|
369
383
|
}
|
|
370
384
|
}
|
|
@@ -336,6 +336,7 @@ import RobotEmergencyStop from "./EmergencyStop.vue";
|
|
|
336
336
|
import RobotSetupPanel from "./SetupPanel.vue";
|
|
337
337
|
import { faultPhotos } from "../../utils/robot-control";
|
|
338
338
|
import { cleanAreaChoices, cleanAreaCommand, mapLayerLegend, robotMapDefault, robotMapViews } from "../../utils/robot-maps";
|
|
339
|
+
import { siteFloorKey, siteFloorLabel, type TSiteFloor } from "../../utils/robot-floors";
|
|
339
340
|
import useRobotLive, { siteStatesFeed } from "../../composables/useRobotLive";
|
|
340
341
|
import {
|
|
341
342
|
attentionText,
|
|
@@ -435,6 +436,7 @@ const {
|
|
|
435
436
|
deleteRobot,
|
|
436
437
|
getRobotMaps,
|
|
437
438
|
getRobotMapImages,
|
|
439
|
+
getSiteFloors,
|
|
438
440
|
resolveRobotAlerts,
|
|
439
441
|
getPatrolHistory,
|
|
440
442
|
getSiteFaults,
|
|
@@ -626,7 +628,24 @@ async function loadSchedule() {
|
|
|
626
628
|
|
|
627
629
|
/* ---- the robot's own maps (PadBot: one per floor / area): a selector, opening where the robot is ---- */
|
|
628
630
|
const mapItems = ref<Array<Record<string, any>>>([]);
|
|
629
|
-
|
|
631
|
+
/** The site's floor names: this robot's map floors are shown as the site calls them (the PadBot's 3 = "34"). */
|
|
632
|
+
const siteFloors = ref<TSiteFloor[]>([]);
|
|
633
|
+
watch(
|
|
634
|
+
() => String(record.value?.site?._id ?? ""),
|
|
635
|
+
async (site) => {
|
|
636
|
+
siteFloors.value = [];
|
|
637
|
+
if (!site) return;
|
|
638
|
+
const res = await getSiteFloors(site).catch(() => null);
|
|
639
|
+
if (site === String(record.value?.site?._id ?? "")) siteFloors.value = res?.floors ?? [];
|
|
640
|
+
},
|
|
641
|
+
{ immediate: true },
|
|
642
|
+
);
|
|
643
|
+
const mapViews = computed(() =>
|
|
644
|
+
robotMapViews(mapItems.value, live.value?.mapId ?? null, patrol.value?.routes ?? [], {
|
|
645
|
+
key: (n) => siteFloorKey(siteFloors.value, props.robotId, n),
|
|
646
|
+
name: (key) => siteFloorLabel(siteFloors.value, key, live.value ? { [props.robotId]: live.value } : {}),
|
|
647
|
+
}),
|
|
648
|
+
);
|
|
630
649
|
const selectedMapId = ref<string | null>(null);
|
|
631
650
|
const selectedMap = computed(() => mapViews.value.find((m) => m.mapId === selectedMapId.value) ?? null);
|
|
632
651
|
const mapLegend = computed(() => mapLayerLegend(selectedMap.value));
|
|
@@ -63,12 +63,12 @@
|
|
|
63
63
|
import { computed, ref, watch } from "vue";
|
|
64
64
|
import { routeOverlay } from "../../utils/rmf-editor";
|
|
65
65
|
import { robotMapViews, type TRobotMapView } from "../../utils/robot-maps";
|
|
66
|
+
import { siteFloorKey, siteFloorLabel, type TSiteFloor } from "../../utils/robot-floors";
|
|
66
67
|
import RobotFloorPlan from "./FloorPlan.vue";
|
|
67
68
|
import RobotPatrolRoutes from "./PatrolRoutes.vue";
|
|
68
69
|
import RobotReconnecting from "./Reconnecting.vue";
|
|
69
70
|
import {
|
|
70
71
|
brandName,
|
|
71
|
-
floorLabel,
|
|
72
72
|
mapFloors,
|
|
73
73
|
mapRobots,
|
|
74
74
|
nameTagSide,
|
|
@@ -76,6 +76,7 @@ import {
|
|
|
76
76
|
positionGap,
|
|
77
77
|
positionGapText,
|
|
78
78
|
robotStatus,
|
|
79
|
+
siteFloorOf,
|
|
79
80
|
unplacedRobots,
|
|
80
81
|
unplacedText,
|
|
81
82
|
type TLiveRobotState,
|
|
@@ -100,8 +101,10 @@ const props = withDefaults(
|
|
|
100
101
|
map?: Record<string, any> | null;
|
|
101
102
|
/** Each robot's own maps (`/robots/:id/maps` items) by robot id - PadBot's map images and a map selector. */
|
|
102
103
|
robotMaps?: Record<string, Array<Record<string, any>>>;
|
|
104
|
+
/** The site's floor names (`/robots/sites/:site/floors`): robot maps and routes are put on the SITE floor (the PadBot's 3 = 34). */
|
|
105
|
+
siteFloors?: TSiteFloor[];
|
|
103
106
|
}>(),
|
|
104
|
-
{ type: "cleaning", routes: () => ({}), connected: true, brands: () => [], tickMs: 4000, map: null, robotMaps: () => ({}) },
|
|
107
|
+
{ type: "cleaning", routes: () => ({}), connected: true, brands: () => [], tickMs: 4000, map: null, robotMaps: () => ({}), siteFloors: () => [] },
|
|
105
108
|
);
|
|
106
109
|
const emit = defineEmits<{ open: [id: string] }>();
|
|
107
110
|
|
|
@@ -111,7 +114,8 @@ const ids = computed(() => props.rows.map((r) => String(r._id)));
|
|
|
111
114
|
const allMaps = computed(() => {
|
|
112
115
|
const seen = new Map<string, TRobotMapView>();
|
|
113
116
|
for (const id of ids.value) {
|
|
114
|
-
|
|
117
|
+
const site = { key: (n: number) => siteFloorKey(props.siteFloors, id, n), name: floorName };
|
|
118
|
+
for (const m of robotMapViews(props.robotMaps[id], props.states[id]?.mapId ?? null, props.routes[id] ?? [], site)) {
|
|
115
119
|
const had = seen.get(m.mapId);
|
|
116
120
|
seen.set(m.mapId, had ? { ...had, current: had.current || m.current } : m);
|
|
117
121
|
}
|
|
@@ -142,7 +146,9 @@ const robots = computed(() =>
|
|
|
142
146
|
const overlay = computed(() => (floor.value === null ? { lanes: [], waypoints: [], fiducials: [] } : routeOverlay(props.map, floor.value)));
|
|
143
147
|
const hasRoutes = computed(() => !!props.map && (props.map.rmf?.levels ?? props.map.levels ?? []).some((l: any) => (l.lanes ?? []).length));
|
|
144
148
|
const showRoutes = ref(true);
|
|
145
|
-
|
|
149
|
+
function floorName(f: number) {
|
|
150
|
+
return siteFloorLabel(props.siteFloors, f, props.states);
|
|
151
|
+
}
|
|
146
152
|
|
|
147
153
|
/** The routes walked on this floor, once each. A route with no floor is drawn on its robot's floor. */
|
|
148
154
|
const floorRoutes = computed(() => {
|
|
@@ -150,7 +156,7 @@ const floorRoutes = computed(() => {
|
|
|
150
156
|
for (const id of ids.value) {
|
|
151
157
|
const st = props.states[id];
|
|
152
158
|
for (const r of props.routes[id] ?? []) {
|
|
153
|
-
const f = r.floorNo
|
|
159
|
+
const f = r.floorNo !== null && r.floorNo !== undefined ? siteFloorKey(props.siteFloors, id, r.floorNo) : siteFloorOf(st);
|
|
154
160
|
const onMap = !shownMap.value || !r.mapId || r.mapId === shownMap.value.mapId;
|
|
155
161
|
if ((r.mapId && shownMap.value ? onMap : f === floor.value) && !seen.has(r.id)) seen.set(r.id, r);
|
|
156
162
|
}
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
:connected="connected"
|
|
82
82
|
:brands="brands"
|
|
83
83
|
:map="routeMap"
|
|
84
|
+
:site-floors="siteFloors"
|
|
84
85
|
@open="onMapOpen"
|
|
85
86
|
/>
|
|
86
87
|
</aside>
|
|
@@ -101,6 +102,7 @@ import RobotBattery from "./Battery.vue";
|
|
|
101
102
|
import RobotFloorLabel from "./FloorLabel.vue";
|
|
102
103
|
import RobotFaultBadge from "./FaultBadge.vue";
|
|
103
104
|
import RobotMiniMap from "./MiniMap.vue";
|
|
105
|
+
import type { TSiteFloor } from "../../utils/robot-floors";
|
|
104
106
|
import useRobotLive, { siteStatesFeed } from "../../composables/useRobotLive";
|
|
105
107
|
import {
|
|
106
108
|
agoText,
|
|
@@ -144,7 +146,18 @@ const emit = defineEmits<{
|
|
|
144
146
|
status: [status: { connected: boolean; faults: number; siteName: string; clientName: string }];
|
|
145
147
|
}>();
|
|
146
148
|
|
|
147
|
-
const { getSiteFleet, getSiteSummary, getRobotBrands, getPatrolRoutes, getRobotMaps, listSiteBuildingMaps, getBuildingMap, getFleetCoreBuilding } = useRobots();
|
|
149
|
+
const { getSiteFleet, getSiteSummary, getRobotBrands, getPatrolRoutes, getRobotMaps, listSiteBuildingMaps, getBuildingMap, getFleetCoreBuilding, getSiteFloors } = useRobots();
|
|
150
|
+
/** The site's floor names: the robots' own maps and routes go on the site floor (the PadBot's 3 = 34). None saved / not readable = robot numbers as before. */
|
|
151
|
+
const siteFloors = ref<TSiteFloor[]>([]);
|
|
152
|
+
watch(
|
|
153
|
+
() => props.site,
|
|
154
|
+
async (site) => {
|
|
155
|
+
siteFloors.value = [];
|
|
156
|
+
const res = await getSiteFloors(site).catch(() => null);
|
|
157
|
+
if (site === props.site) siteFloors.value = res?.floors ?? [];
|
|
158
|
+
},
|
|
159
|
+
{ immediate: true },
|
|
160
|
+
);
|
|
148
161
|
|
|
149
162
|
/**
|
|
150
163
|
* The site's building for the mini-map's robot lanes: the live HERO Fleet
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@7365admin1/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "4.97.1-staging.
|
|
5
|
+
"version": "4.97.1-staging.608",
|
|
6
6
|
"author": "7365admin1",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
8
8
|
"//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
|
package/utils/robot-display.ts
CHANGED
|
@@ -637,6 +637,10 @@ export type TLiveRobotState = {
|
|
|
637
637
|
batteryPercent?: number | null;
|
|
638
638
|
floorNo?: number | null;
|
|
639
639
|
floorKnown?: boolean;
|
|
640
|
+
/** The SITE's floor for `floorNo` (server, from the site's floor names): the PadBot's 3 is the 34th floor. */
|
|
641
|
+
siteFloorNo?: number | null;
|
|
642
|
+
/** The site's name for that floor (what the lift car display shows). */
|
|
643
|
+
floorName?: string | null;
|
|
640
644
|
/** The brand's id of the map the robot is on now (PadBot); absent / null when unknown. */
|
|
641
645
|
mapId?: string | null;
|
|
642
646
|
position?: { x: number; y: number; theta: number } | null;
|
|
@@ -852,6 +856,13 @@ export function headingDeg(state: { position?: { theta?: unknown } | null } | nu
|
|
|
852
856
|
return Math.round(((((-t * 180) / Math.PI) % 360) + 360) % 360);
|
|
853
857
|
}
|
|
854
858
|
|
|
859
|
+
/** The site floor a robot is on: the server's site floor, else its own number (no floor names saved). */
|
|
860
|
+
export function siteFloorOf(live: Partial<TLiveRobotState> | null | undefined): number | null {
|
|
861
|
+
if (!live || live.floorKnown !== true) return null;
|
|
862
|
+
if (finite(live.siteFloorNo) && Number.isInteger(live.siteFloorNo)) return live.siteFloorNo;
|
|
863
|
+
return finite(live.floorNo) ? live.floorNo : null;
|
|
864
|
+
}
|
|
865
|
+
|
|
855
866
|
/** Is the robot live on a known floor with a plan position - i.e. may it be drawn? */
|
|
856
867
|
function placeable(live: TLiveRobotState | null | undefined) {
|
|
857
868
|
return !!live?.ok && live.online !== false && live.floorKnown === true && finite(live.floorNo) && !!planPoint(live as any);
|
|
@@ -862,7 +873,8 @@ export function mapFloors(ids: string[], states: Record<string, TLiveRobotState>
|
|
|
862
873
|
const floors = new Set<number>();
|
|
863
874
|
for (const id of ids) {
|
|
864
875
|
const s = states[id];
|
|
865
|
-
|
|
876
|
+
const f = s?.ok ? siteFloorOf(s) : null;
|
|
877
|
+
if (f !== null) floors.add(f);
|
|
866
878
|
}
|
|
867
879
|
return [...floors].sort((a, b) => a - b);
|
|
868
880
|
}
|
|
@@ -877,7 +889,7 @@ export function pickFloor(floors: number[], chosen: unknown, ids: string[], stat
|
|
|
877
889
|
let best = floors[0]!;
|
|
878
890
|
let most = -1;
|
|
879
891
|
for (const f of floors) {
|
|
880
|
-
const n = ids.filter((id) => placeable(states[id]) && states[id]
|
|
892
|
+
const n = ids.filter((id) => placeable(states[id]) && siteFloorOf(states[id]) === f).length;
|
|
881
893
|
if (n > most) {
|
|
882
894
|
most = n;
|
|
883
895
|
best = f;
|
|
@@ -896,7 +908,7 @@ export function mapRobots(
|
|
|
896
908
|
return rows.flatMap((r) => {
|
|
897
909
|
const id = String(r?._id ?? "");
|
|
898
910
|
const live = states[id];
|
|
899
|
-
if (!placeable(live) || live
|
|
911
|
+
if (!placeable(live) || siteFloorOf(live) !== floor) return [];
|
|
900
912
|
const at = planPoint(live as any)!;
|
|
901
913
|
// A kept (stale) reading is drawn faded with "last seen N s ago" - never as live.
|
|
902
914
|
return [{ id, name: String(r.name ?? ""), mode: live!.mode, type: r.type as string, ...at, rot: `rotate(${headingDeg(live)}deg)`, stale: live!.stale === true, seen: staleText(live) }];
|
package/utils/robot-floors.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* arithmetic only.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { siteFloorOf, type TLiveRobotState } from "./robot-display.ts";
|
|
12
|
+
|
|
11
13
|
export type TFloorLink = { robotId: string; floorNo: number };
|
|
12
14
|
export type TSiteFloor = { key: number; name: string; vendor?: TFloorLink[]; fleetCoreLevel?: string };
|
|
13
15
|
|
|
@@ -93,3 +95,23 @@ export function floorsBody(floors: TSiteFloor[]): TSiteFloor[] {
|
|
|
93
95
|
export function floorNameByKey(floors: TSiteFloor[] | null | undefined, key: number): string {
|
|
94
96
|
return floors?.find((f) => f.key === key)?.name ?? defaultFloorName(key);
|
|
95
97
|
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A robot's own floor number as the site floor (same rule as core
|
|
101
|
+
* `siteFloorOf`): its link, else the floor with that number, else its own
|
|
102
|
+
* number unchanged. Used for a robot's maps and routes, which carry the
|
|
103
|
+
* robot's number (the PadBot's map says 3 on the 34th floor).
|
|
104
|
+
*/
|
|
105
|
+
export function siteFloorKey(floors: TSiteFloor[] | null | undefined, robotId: string, floorNo: number): number {
|
|
106
|
+
return floorKeyFor(floors ?? [], robotId, floorNo) ?? floorNo;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A site floor's name for the maps: the saved list's name, else the name the
|
|
111
|
+
* server sent with a robot on that floor, else the old label (34 -> "L34").
|
|
112
|
+
*/
|
|
113
|
+
export function siteFloorLabel(floors: TSiteFloor[] | null | undefined, key: number, states: Record<string, TLiveRobotState> = {}): string {
|
|
114
|
+
if (floors?.some((f) => f.key === key)) return floorNameByKey(floors, key);
|
|
115
|
+
const named = Object.values(states).find((s) => s?.ok && siteFloorOf(s) === key && typeof s.floorName === "string" && s.floorName.trim());
|
|
116
|
+
return named ? String(named.floorName).trim() : defaultFloorName(key);
|
|
117
|
+
}
|
package/utils/robot-maps.ts
CHANGED
|
@@ -62,7 +62,7 @@ export function mapLayerLegend(view: Pick<TRobotMapView, "zones" | "taughtRoute"
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const isNum = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v);
|
|
65
|
-
const
|
|
65
|
+
const defaultLevelName = (n: number) => (n > 0 ? `L${n}` : n < 0 ? `B${-n}` : "G");
|
|
66
66
|
const TYPE_WORDS: Readonly<Record<number, string>> = { 0: "Point", 3: "Lift", 4: "Loading", 6: "Access door", 10: "Charging" };
|
|
67
67
|
|
|
68
68
|
/**
|
|
@@ -91,10 +91,13 @@ export function robotMapViews(
|
|
|
91
91
|
items: Array<Record<string, any>> | null | undefined,
|
|
92
92
|
currentMapId: string | null | undefined,
|
|
93
93
|
routes: Array<{ name?: string; mapId?: string }> | null | undefined = [],
|
|
94
|
+
/** The site's floors for this robot: its map floor numbers become site floors, named as the lift shows them. */
|
|
95
|
+
site?: { key: (floorNo: number) => number; name: (key: number) => string },
|
|
94
96
|
): TRobotMapView[] {
|
|
95
97
|
const list = (Array.isArray(items) ? items : []).filter((m) => typeof m?.mapId === "string" && m.mapId && typeof m?.imageUrl === "string");
|
|
96
98
|
return list.map((m, i) => {
|
|
97
|
-
const floorNo = Number.isInteger(m.floor) ? (m.floor as number) : null;
|
|
99
|
+
const floorNo = Number.isInteger(m.floor) ? (site ? site.key(m.floor) : (m.floor as number)) : null;
|
|
100
|
+
const levelName = site ? site.name : defaultLevelName;
|
|
98
101
|
const routeNames = (routes ?? []).filter((r) => r?.mapId === m.mapId && r.name).map((r) => String(r.name));
|
|
99
102
|
const shown = typeof m.mapDisplayName === "string" ? m.mapDisplayName.trim() : "";
|
|
100
103
|
const hint = floorNo !== null ? levelName(floorNo) : routeNames[0]?.replace(/ \(default\)$/, "") ?? "";
|