@7365admin1/layer-common 4.49.0 → 4.51.0
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/CHANGELOG.md +16 -4
- package/assets/css/robot-hero.css +71 -6
- package/components/MemberMain.vue +61 -3
- package/components/Robot/BuildingMap.vue +78 -26
- package/components/Robot/MapEditor.vue +890 -175
- package/components/Robot/MiniMap.vue +16 -1
- package/components/Robot/SiteFleet.vue +23 -1
- package/composables/useMember.ts +3 -0
- package/composables/useRobots.ts +15 -1
- package/package.json +2 -2
- package/utils/rmf-editor.ts +756 -0
- package/utils/robot-building.ts +692 -170
- package/utils/robot-floorplan.ts +9 -8
- package/utils/role.ts +13 -0
|
@@ -14,12 +14,19 @@
|
|
|
14
14
|
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" />
|
|
15
15
|
</svg>
|
|
16
16
|
</v-btn>
|
|
17
|
+
<button v-if="hasRoutes" type="button" class="hero-mini__toggle" :aria-pressed="showRoutes" title="Show the robots' lanes and waypoints" @click="showRoutes = !showRoutes">Routes</button>
|
|
17
18
|
<select v-if="floors.length" :value="floor" class="hero-input hero-select hero-select--sm" aria-label="Floor" @change="chosen = Number(($event.target as HTMLSelectElement).value)">
|
|
18
19
|
<option v-for="f in floors" :key="f" :value="f">{{ floorName(f) }}</option>
|
|
19
20
|
</select>
|
|
20
21
|
</div>
|
|
21
22
|
|
|
22
23
|
<RobotFloorPlan ref="plan">
|
|
24
|
+
<template v-if="showRoutes && overlay.lanes.length + overlay.waypoints.length">
|
|
25
|
+
<svg class="hero-plan__svg" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
|
|
26
|
+
<line v-for="(l, i) in overlay.lanes" :key="`l${i}`" class="hero-mini__lane" :class="{ 'hero-mini__lane--oneway': l.oneWay }" :x1="l.a.x" :y1="l.a.y" :x2="l.b.x" :y2="l.b.y" vector-effect="non-scaling-stroke" />
|
|
27
|
+
</svg>
|
|
28
|
+
<span v-for="(w, i) in overlay.waypoints" :key="`w${i}`" class="hero-mini__wp" :class="`hero-mini__wp--${w.kind}`" :style="{ left: `${w.x}%`, top: `${w.y}%` }" :title="w.name" aria-hidden="true" />
|
|
29
|
+
</template>
|
|
23
30
|
<RobotPatrolRoutes v-if="type === 'security' && floorRoutes.length" :routes="floorRoutes" />
|
|
24
31
|
<button
|
|
25
32
|
v-for="r in robots"
|
|
@@ -40,6 +47,7 @@
|
|
|
40
47
|
<div class="hero-mini__foot">
|
|
41
48
|
<span>Floor plan from robot (placeholder)</span>
|
|
42
49
|
<span v-if="type === 'security'"><span class="hero-mini__legend" aria-hidden="true" />Patrol routes</span>
|
|
50
|
+
<span v-if="showRoutes && hasRoutes"><span class="hero-mini__legend hero-mini__legend--lane" aria-hidden="true" />Robot lanes</span>
|
|
43
51
|
<span v-if="floor !== null" class="hero-mini__count">{{ robots.length }} on {{ floorName(floor) }}</span>
|
|
44
52
|
</div>
|
|
45
53
|
<div v-if="unplaced" class="hero-notice">{{ unplaced }}</div>
|
|
@@ -48,6 +56,7 @@
|
|
|
48
56
|
|
|
49
57
|
<script setup lang="ts">
|
|
50
58
|
import { computed, ref } from "vue";
|
|
59
|
+
import { routeOverlay } from "../../utils/rmf-editor";
|
|
51
60
|
import RobotFloorPlan from "./FloorPlan.vue";
|
|
52
61
|
import RobotPatrolRoutes from "./PatrolRoutes.vue";
|
|
53
62
|
import RobotReconnecting from "./Reconnecting.vue";
|
|
@@ -79,8 +88,10 @@ const props = withDefaults(
|
|
|
79
88
|
brands?: Array<{ brand?: unknown; displayName?: unknown }>;
|
|
80
89
|
/** One telemetry tick; positions glide across it. */
|
|
81
90
|
tickMs?: number;
|
|
91
|
+
/** The site's building (saved map or live HERO Fleet Core building) - its robot lanes and waypoints can be shown. */
|
|
92
|
+
map?: Record<string, any> | null;
|
|
82
93
|
}>(),
|
|
83
|
-
{ type: "cleaning", routes: () => ({}), connected: true, brands: () => [], tickMs: 4000 },
|
|
94
|
+
{ type: "cleaning", routes: () => ({}), connected: true, brands: () => [], tickMs: 4000, map: null },
|
|
84
95
|
);
|
|
85
96
|
const emit = defineEmits<{ open: [id: string] }>();
|
|
86
97
|
|
|
@@ -90,6 +101,10 @@ const floors = computed(() => mapFloors(ids.value, props.states));
|
|
|
90
101
|
const chosen = ref<number | null>(null);
|
|
91
102
|
const floor = computed(() => pickFloor(floors.value, chosen.value, ids.value, props.states));
|
|
92
103
|
const robots = computed(() => mapRobots(props.rows, props.states, floor.value));
|
|
104
|
+
/** The robots' lanes and waypoints on the floor shown (toggle; on by default once the building has any). */
|
|
105
|
+
const overlay = computed(() => (floor.value === null ? { lanes: [], waypoints: [], fiducials: [] } : routeOverlay(props.map, floor.value)));
|
|
106
|
+
const hasRoutes = computed(() => !!props.map && (props.map.rmf?.levels ?? props.map.levels ?? []).some((l: any) => (l.lanes ?? []).length));
|
|
107
|
+
const showRoutes = ref(true);
|
|
93
108
|
const floorName = (f: number) => floorLabel({ floorNo: f, floorKnown: true });
|
|
94
109
|
|
|
95
110
|
/** The routes walked on this floor, once each. A route with no floor is drawn on its robot's floor. */
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
:routes="routes"
|
|
73
73
|
:connected="connected"
|
|
74
74
|
:brands="brands"
|
|
75
|
+
:map="routeMap"
|
|
75
76
|
@open="onMapOpen"
|
|
76
77
|
/>
|
|
77
78
|
</aside>
|
|
@@ -126,7 +127,26 @@ const emit = defineEmits<{
|
|
|
126
127
|
status: [status: { connected: boolean; faults: number; siteName: string; clientName: string }];
|
|
127
128
|
}>();
|
|
128
129
|
|
|
129
|
-
const { getSiteFleet, getSiteSummary, getRobotBrands, getPatrolRoutes } = useRobots();
|
|
130
|
+
const { getSiteFleet, getSiteSummary, getRobotBrands, getPatrolRoutes, listSiteBuildingMaps, getBuildingMap, getFleetCoreBuilding } = useRobots();
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The site's building for the mini-map's robot lanes: the live HERO Fleet
|
|
134
|
+
* Core building at a fleet site, else the newest saved map. Read once per
|
|
135
|
+
* site (lanes do not move); a failure just leaves the lanes off.
|
|
136
|
+
*/
|
|
137
|
+
const routeMap = ref<Record<string, any> | null>(null);
|
|
138
|
+
async function loadRouteMap() {
|
|
139
|
+
const site = props.site;
|
|
140
|
+
try {
|
|
141
|
+
const fleetSite = rows.value.some((r) => r?.brand === "fleet-core");
|
|
142
|
+
const found = fleetSite
|
|
143
|
+
? await getFleetCoreBuilding(site).catch(() => null)
|
|
144
|
+
: await listSiteBuildingMaps(site, { limit: 1 }).then((res: any) => (res?.items?.[0]?._id ? getBuildingMap(String(res.items[0]._id)) : null));
|
|
145
|
+
if (site === props.site) routeMap.value = found ?? null;
|
|
146
|
+
} catch {
|
|
147
|
+
if (site === props.site) routeMap.value = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
130
150
|
|
|
131
151
|
const headers = [
|
|
132
152
|
{ title: "Name", key: "name", sortable: false },
|
|
@@ -253,6 +273,8 @@ watch(
|
|
|
253
273
|
{ immediate: true },
|
|
254
274
|
);
|
|
255
275
|
watch(ids, loadRoutes);
|
|
276
|
+
// The mini-map's lanes: once per site, again if the site turns out to have fleet robots.
|
|
277
|
+
watch([() => props.site, () => rows.value.some((r) => r?.brand === "fleet-core")], loadRouteMap, { immediate: true });
|
|
256
278
|
getRobotBrands().then(
|
|
257
279
|
(res) => (brands.value = res?.items ?? []),
|
|
258
280
|
() => undefined,
|
package/composables/useMember.ts
CHANGED
|
@@ -69,10 +69,13 @@ export default function useMember() {
|
|
|
69
69
|
type = "",
|
|
70
70
|
status = "active",
|
|
71
71
|
siteId = "",
|
|
72
|
+
roleState = "",
|
|
72
73
|
} = {}) {
|
|
73
74
|
const query: Record<string, any> = { page, limit, type, status };
|
|
74
75
|
|
|
75
76
|
if (search) query.search = search;
|
|
77
|
+
// "other-site": only members holding a role pinned to another site.
|
|
78
|
+
if (roleState) query.roleState = roleState;
|
|
76
79
|
if (user) query.user = user;
|
|
77
80
|
if (org) query.org = org;
|
|
78
81
|
if (siteId) query.siteId = siteId;
|
package/composables/useRobots.ts
CHANGED
|
@@ -385,6 +385,19 @@ export default function useRobots() {
|
|
|
385
385
|
});
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Read a building file (traffic-editor / rmf_site building.yaml) or one
|
|
390
|
+
* route (nav graph) file into the editor. Nothing is saved: the result is
|
|
391
|
+
* the map's `rmf` part, which the editor shows until it is saved.
|
|
392
|
+
*/
|
|
393
|
+
function importBuildingFile(body: { format: "building" | "nav"; text: string; graph?: number }) {
|
|
394
|
+
return useNuxtApp().$api<Record<string, any>>("/api/robots/building-maps/import", {
|
|
395
|
+
method: "POST",
|
|
396
|
+
query: { appSlug },
|
|
397
|
+
body,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
388
401
|
/**
|
|
389
402
|
* One floor's plan image. A Blob, not a URL: the image sits behind the
|
|
390
403
|
* session like the map itself, and an <img src> cannot carry the header.
|
|
@@ -400,7 +413,7 @@ export default function useRobots() {
|
|
|
400
413
|
function saveFloorPlan(
|
|
401
414
|
mapId: string,
|
|
402
415
|
floorNo: number,
|
|
403
|
-
body: { version: number; image?: string; widthPx: number; heightPx: number; metersPerPixel: number; tx: number; ty: number },
|
|
416
|
+
body: { version: number; image?: string; widthPx: number; heightPx: number; metersPerPixel: number; tx: number; ty: number; yawRad?: number; space?: "world" },
|
|
404
417
|
) {
|
|
405
418
|
return useNuxtApp().$api<Record<string, any>>(`/api/robots/building-maps/${mapId}/floor-plans/${floorNo}`, {
|
|
406
419
|
method: "PUT",
|
|
@@ -467,6 +480,7 @@ export default function useRobots() {
|
|
|
467
480
|
deleteBuildingMap,
|
|
468
481
|
getFleetCoreBuildingData,
|
|
469
482
|
exportBuildingMap,
|
|
483
|
+
importBuildingFile,
|
|
470
484
|
getFloorPlanImage,
|
|
471
485
|
saveFloorPlan,
|
|
472
486
|
removeFloorPlan,
|
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.
|
|
5
|
+
"version": "4.51.0",
|
|
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.",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"build": "nuxt build .playground",
|
|
37
37
|
"generate": "nuxt generate .playground",
|
|
38
38
|
"preview": "nuxt preview .playground",
|
|
39
|
-
"test": "esbuild composables/useANPRSettings.ts --format=esm --outfile=test/.build/useANPRSettings.mjs --log-level=error && esbuild composables/useVisitorSocket.ts --format=esm --outfile=test/.build/useVisitorSocket.mjs --log-level=error && esbuild composables/usePlateToastWindow.ts --format=esm --outfile=test/.build/usePlateToastWindow.mjs --log-level=error && esbuild utils/client-invite-modules.ts --bundle --platform=node --format=esm --outfile=test/.build/client-invite-modules.mjs --log-level=error && esbuild utils/robot-display.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-display.mjs --log-level=error && esbuild utils/building-map.ts --bundle --platform=node --format=esm --outfile=test/.build/building-map.mjs --log-level=error && esbuild utils/robot-building.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-building.mjs --log-level=error && esbuild utils/robot-fleet-core.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-fleet-core.mjs --log-level=error && node --experimental-strip-types --test \"test/*.test.mjs\" && yarn test:units",
|
|
39
|
+
"test": "esbuild composables/useANPRSettings.ts --format=esm --outfile=test/.build/useANPRSettings.mjs --log-level=error && esbuild composables/useVisitorSocket.ts --format=esm --outfile=test/.build/useVisitorSocket.mjs --log-level=error && esbuild composables/usePlateToastWindow.ts --format=esm --outfile=test/.build/usePlateToastWindow.mjs --log-level=error && esbuild utils/client-invite-modules.ts --bundle --platform=node --format=esm --outfile=test/.build/client-invite-modules.mjs --log-level=error && esbuild utils/robot-display.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-display.mjs --log-level=error && esbuild utils/building-map.ts --bundle --platform=node --format=esm --outfile=test/.build/building-map.mjs --log-level=error && esbuild utils/robot-building.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-building.mjs --log-level=error && esbuild utils/robot-fleet-core.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-fleet-core.mjs --log-level=error && esbuild utils/rmf-editor.ts --bundle --platform=node --format=esm --outfile=test/.build/rmf-editor.mjs --log-level=error && node --experimental-strip-types --test \"test/*.test.mjs\" && yarn test:units",
|
|
40
40
|
"test:units": "node --experimental-strip-types --test \"utils/*.test.ts\"",
|
|
41
41
|
"release": "yarn run build && changeset publish"
|
|
42
42
|
},
|