@7365admin1/layer-common 4.122.2 → 4.122.3

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 4.122.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 6d9f5ad: Lift queue: when no robot ride is being drawn and the lift's read-only gateway is connected, the car in the shaft now follows the real lift - it moves to the lift's current floor, the doors open and close with the lift's door state, the car shows an up/down arrow while moving, and it turns the alert colour in fire/emergency modes. Before, the card text updated but the car only ever moved for robot rides.
8
+
3
9
  ## 4.122.2
4
10
 
5
11
  ### Patch Changes
@@ -3240,11 +3246,11 @@ Monthly report: a month that had patrols opens the list of that month's patrol r
3240
3246
  `utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
3241
3247
  own rule from two endpoints the console already calls, unprojected:
3242
3248
 
3243
- GET /api/members/user/:user/app/admin the Seven365 staff membership
3244
- GET /api/roles/id/:role that membership's role document
3249
+ GET /api/members/user/:user/app/admin the Seven365 staff membership
3250
+ GET /api/roles/id/:role that membership's role document
3245
3251
 
3246
- owner = member.type === "admin" && role.type === "admin" && role.default === true
3247
- staff = member.type === "admin" && role.type === "admin"
3252
+ owner = member.type === "admin" && role.type === "admin" && role.default === true
3253
+ staff = member.type === "admin" && role.type === "admin"
3248
3254
 
3249
3255
  `role.default` is the marker because it is the only property of a platform
3250
3256
  staff role no API caller can set - `role.controller.ts` validates create and
@@ -85,10 +85,13 @@
85
85
  <div
86
86
  class="hero-lift__carbox"
87
87
  :class="`hero-lift__carbox--${l.carTone}`"
88
- :style="{ height: `${l.carH}px`, top: `${l.fromTop}px`, '--from': `${l.fromTop}px`, '--to': `${l.toTop}px`, animation: l.riding ? 'heroLift 6s ease-in-out infinite alternate' : 'none' }"
88
+ :style="{ height: `${l.carH}px`, top: `${l.fromTop}px`, '--from': `${l.fromTop}px`, '--to': `${l.toTop}px`, animation: l.riding ? 'heroLift 6s ease-in-out infinite alternate' : 'none', transition: l.live ? 'top 1.8s linear' : 'none' }"
89
89
  >
90
90
  <span class="hero-lift__carlabel">{{ l.carLabel }}</span>
91
- <div class="hero-lift__doors" :style="{ animation: l.riding ? 'heroDoors 6s ease-in-out infinite' : 'none' }" />
91
+ <div
92
+ class="hero-lift__doors"
93
+ :style="l.live ? { transform: `scaleX(${1 - l.doors})`, transition: 'transform 0.8s ease' } : { animation: l.riding ? 'heroDoors 6s ease-in-out infinite' : 'none' }"
94
+ />
92
95
  </div>
93
96
  </div>
94
97
  </div>
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.122.2",
5
+ "version": "4.122.3",
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.",
@@ -1460,6 +1460,21 @@ export function liftQueueRows(lift: Record<string, any>, now = Date.now()): TLif
1460
1460
  const ROW_H = 44;
1461
1461
  const CAR_H = ROW_H - 14;
1462
1462
 
1463
+ /**
1464
+ * A floor as the real lift shows it ("B1", "L34", "34", "G") -> the car's y on the rails. Between two rails
1465
+ * (a floor the map has no rail for) the car sits proportionally between them; past the ends it sits on the end.
1466
+ */
1467
+ export function liftFloorTop(display: unknown, floors: number[], topOf: (f: number) => number): number | null {
1468
+ const parsed = newFloorNo(String(display ?? "").replace(/\s+/g, ""), []);
1469
+ if (!("floorNo" in parsed) || !floors.length) return null;
1470
+ const v = parsed.floorNo;
1471
+ if (v <= floors[0]!) return topOf(floors[0]!);
1472
+ if (v >= floors.at(-1)!) return topOf(floors.at(-1)!);
1473
+ const i = floors.findIndex((f) => f >= v);
1474
+ const lo = floors[i - 1]!, hi = floors[i]!;
1475
+ return topOf(lo) + ((v - lo) / (hi - lo)) * (topOf(hi) - topOf(lo));
1476
+ }
1477
+
1463
1478
  /** The whole card: header words, car status, shaft geometry, rows (README §5). */
1464
1479
  export function liftCard(lift: Record<string, any>, now = Date.now()) {
1465
1480
  const rows = liftQueueRows(lift, now);
@@ -1472,23 +1487,37 @@ export function liftCard(lift: Record<string, any>, now = Date.now()) {
1472
1487
  const rest = floors.includes(idleFloor) ? idleFloor : floors[0] ?? 1;
1473
1488
  const from = riding ? riding.fromNo : rest;
1474
1489
  const to = riding ? riding.toNo : rest;
1490
+ // The real lift (read-only gateway), when no robot ride is drawn: the car follows its floor, doors and direction.
1491
+ const gw = lift?.gateway;
1492
+ const real = !riding && gw?.status === "connected" && gw.state ? gw.state : null;
1493
+ const realTop = real ? liftFloorTop(real.currentFloor, floors, topOf) : null;
1494
+ const live = realTop != null;
1495
+ const realAlarm = live && [3, 4, 5].includes(Number(real.currentMode));
1496
+ const arrow = live ? (real.motionState === 1 ? "▲" : real.motionState === 2 ? "▼" : "") : "";
1475
1497
  return {
1476
1498
  name: String(lift?.liftName ?? "Lift"),
1477
1499
  sub: rows.length ? `${riding ? 1 : 0} riding · ${waiting.length} waiting` : "idle",
1478
- carText: riding ? `Car · ${riding.robot} · ${riding.from} → ${riding.to}` : "Car idle",
1479
- carLabel: riding ? riding.robot.replace(/^HERO-/, "") : "",
1480
- carTone: riding ? "ink" : rows.some((r) => r.state === "timed out") ? "accent" : "neutral",
1500
+ carText: riding
1501
+ ? `Car · ${riding.robot} · ${riding.from} → ${riding.to}`
1502
+ : live
1503
+ ? `Car · floor ${real.currentFloor}${MOTION_TEXT[real.motionState] ? ` · ${MOTION_TEXT[real.motionState]!.toLowerCase()}` : ""}`
1504
+ : "Car idle",
1505
+ carLabel: riding ? riding.robot.replace(/^HERO-/, "") : arrow,
1506
+ carTone: riding ? "ink" : realAlarm || rows.some((r) => r.state === "timed out") ? "accent" : live ? "ink" : "neutral",
1507
+ /** Real lift drawn live: the car glides between readings; doors open (1) / half (0.5, moving) / shut (0). */
1508
+ live,
1509
+ doors: live ? (real.doorState === 2 ? 1 : real.doorState === 1 ? 0.5 : 0) : 0,
1481
1510
  riding: !!riding,
1482
1511
  empty: !rows.length,
1483
1512
  shaftH: (n - 1) * ROW_H + 24,
1484
1513
  carH: CAR_H,
1485
- fromTop: topOf(from) - CAR_H / 2,
1486
- toTop: topOf(to) - CAR_H / 2,
1514
+ fromTop: (live ? realTop! : topOf(from)) - CAR_H / 2,
1515
+ toTop: (live ? realTop! : topOf(to)) - CAR_H / 2,
1487
1516
  rails: [...floors].reverse().map((f) => ({
1488
1517
  floorNo: f,
1489
1518
  name: floorName(f),
1490
1519
  top: topOf(f),
1491
- lit: !!riding && (f === riding.fromNo || f === riding.toNo),
1520
+ lit: riding ? f === riding.fromNo || f === riding.toNo : live && realTop === topOf(f),
1492
1521
  waiting: waiting.filter((w) => w.fromNo === f).map((w) => w.robot),
1493
1522
  })),
1494
1523
  rows,