@camstack/ui-library 1.1.7 → 1.1.8
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/dist/composites/device-controls/image-control.d.ts +11 -0
- package/dist/generated/system-hooks.d.ts +2 -0
- package/dist/index.cjs +426 -198
- package/dist/index.js +426 -199
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -17210,6 +17210,8 @@ var useDeviceManagerAdoptionRefresh = trpc.deviceManager.adoptionRefresh.useMuta
|
|
|
17210
17210
|
var useDeviceManagerAdoptionAdopt = trpc.deviceManager.adoptionAdopt.useMutation;
|
|
17211
17211
|
/** Generated alias around `trpc.deviceManager.adoptionRelease.useMutation`. */
|
|
17212
17212
|
var useDeviceManagerAdoptionRelease = trpc.deviceManager.adoptionRelease.useMutation;
|
|
17213
|
+
/** Generated alias around `trpc.deviceManager.adoptionResync.useMutation`. */
|
|
17214
|
+
var useDeviceManagerAdoptionResync = trpc.deviceManager.adoptionResync.useMutation;
|
|
17213
17215
|
/** Generated alias around `trpc.deviceManager.testField.useMutation`. */
|
|
17214
17216
|
var useDeviceManagerTestField = trpc.deviceManager.testField.useMutation;
|
|
17215
17217
|
/** Generated alias around `trpc.deviceManager.getDeviceStatusAggregate.useQuery`. */
|
|
@@ -18257,6 +18259,31 @@ var ROLE_DESCRIPTOR = {
|
|
|
18257
18259
|
field: "value",
|
|
18258
18260
|
getSliceHandle: (s) => s.numericSensor
|
|
18259
18261
|
},
|
|
18262
|
+
"wind-sensor": {
|
|
18263
|
+
kind: "numeric",
|
|
18264
|
+
field: "value",
|
|
18265
|
+
getSliceHandle: (s) => s.numericSensor
|
|
18266
|
+
},
|
|
18267
|
+
"rain-sensor": {
|
|
18268
|
+
kind: "numeric",
|
|
18269
|
+
field: "value",
|
|
18270
|
+
getSliceHandle: (s) => s.numericSensor
|
|
18271
|
+
},
|
|
18272
|
+
"uv-sensor": {
|
|
18273
|
+
kind: "numeric",
|
|
18274
|
+
field: "value",
|
|
18275
|
+
getSliceHandle: (s) => s.numericSensor
|
|
18276
|
+
},
|
|
18277
|
+
"solar-radiation-sensor": {
|
|
18278
|
+
kind: "numeric",
|
|
18279
|
+
field: "value",
|
|
18280
|
+
getSliceHandle: (s) => s.numericSensor
|
|
18281
|
+
},
|
|
18282
|
+
"soil-moisture-sensor": {
|
|
18283
|
+
kind: "numeric",
|
|
18284
|
+
field: "value",
|
|
18285
|
+
getSliceHandle: (s) => s.numericSensor
|
|
18286
|
+
},
|
|
18260
18287
|
"power-sensor": {
|
|
18261
18288
|
kind: "numeric",
|
|
18262
18289
|
field: "watts",
|
|
@@ -18770,6 +18797,10 @@ function vacuumStateMeta(state) {
|
|
|
18770
18797
|
label: "Docked",
|
|
18771
18798
|
tone: "text-success"
|
|
18772
18799
|
};
|
|
18800
|
+
case "drying": return {
|
|
18801
|
+
label: "Drying",
|
|
18802
|
+
tone: "text-warning"
|
|
18803
|
+
};
|
|
18773
18804
|
case "idle": return {
|
|
18774
18805
|
label: "Idle",
|
|
18775
18806
|
tone: "text-foreground-subtle"
|
|
@@ -18826,6 +18857,12 @@ function vacuumGraphicTint(state) {
|
|
|
18826
18857
|
docked: true,
|
|
18827
18858
|
active: false
|
|
18828
18859
|
};
|
|
18860
|
+
case "drying": return {
|
|
18861
|
+
glow: "bg-warning/10",
|
|
18862
|
+
accent: "text-warning",
|
|
18863
|
+
docked: true,
|
|
18864
|
+
active: false
|
|
18865
|
+
};
|
|
18829
18866
|
case "paused": return {
|
|
18830
18867
|
glow: "bg-warning/10",
|
|
18831
18868
|
accent: "text-warning",
|
|
@@ -18996,8 +19033,10 @@ function tankMeta(kind) {
|
|
|
18996
19033
|
/**
|
|
18997
19034
|
* TankIndicator — one tank row: label + glyph, then a fill bar (numeric
|
|
18998
19035
|
* level) or a status pill (discrete), tinted by `tankAlert`. Renders
|
|
18999
|
-
* nothing when the tank is absent (null)
|
|
19000
|
-
* (both fields null)
|
|
19036
|
+
* nothing only when the tank is absent (null). A PRESENT tank whose
|
|
19037
|
+
* reading is unknown (both fields null) still renders a neutral row with
|
|
19038
|
+
* an em-dash placeholder, so the user always sees which tanks the robot
|
|
19039
|
+
* has (Dreame reports presence but no fill telemetry).
|
|
19001
19040
|
*/
|
|
19002
19041
|
function TankIndicator({ kind, tank }) {
|
|
19003
19042
|
if (tank === null) return null;
|
|
@@ -19033,15 +19072,22 @@ function TankIndicator({ kind, tank }) {
|
|
|
19033
19072
|
children: tank.status
|
|
19034
19073
|
})]
|
|
19035
19074
|
});
|
|
19036
|
-
return
|
|
19075
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19076
|
+
className: "flex items-center gap-2 text-xs",
|
|
19077
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
19078
|
+
className: "inline-flex items-center gap-1 w-24 shrink-0 text-foreground-subtle",
|
|
19079
|
+
children: [meta.icon, meta.label]
|
|
19080
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
19081
|
+
className: "inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium bg-surface-hover text-foreground-subtle",
|
|
19082
|
+
children: "—"
|
|
19083
|
+
})]
|
|
19084
|
+
});
|
|
19037
19085
|
}
|
|
19038
19086
|
/**
|
|
19039
19087
|
* TanksRow — compact 2-column grid of the present tanks for the vacuum
|
|
19040
|
-
* hero. Absent tanks (null) are skipped;
|
|
19041
|
-
*
|
|
19042
|
-
*
|
|
19043
|
-
* (`{level:null,status:null}`) renders nothing, so a hero of only such
|
|
19044
|
-
* tanks would otherwise show an empty bordered box.
|
|
19088
|
+
* hero. Absent tanks (null) are skipped; every PRESENT tank is shown, even
|
|
19089
|
+
* when its reading is unknown (`{level:null,status:null}`) — it renders a
|
|
19090
|
+
* neutral em-dash row. The row renders iff the robot has at least one tank.
|
|
19045
19091
|
*/
|
|
19046
19092
|
function TanksRow({ cleanWater, dirtyWater, detergent, dustBin }) {
|
|
19047
19093
|
const tanks = [
|
|
@@ -19050,7 +19096,7 @@ function TanksRow({ cleanWater, dirtyWater, detergent, dustBin }) {
|
|
|
19050
19096
|
["detergent", detergent],
|
|
19051
19097
|
["dustBin", dustBin]
|
|
19052
19098
|
];
|
|
19053
|
-
if (!tanks.some(([, t]) => t !== null
|
|
19099
|
+
if (!tanks.some(([, t]) => t !== null)) return null;
|
|
19054
19100
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19055
19101
|
className: "grid grid-cols-1 gap-x-6 gap-y-2 rounded-md border border-border bg-surface-hover/40 p-3 sm:grid-cols-2",
|
|
19056
19102
|
children: tanks.map(([kind, tank]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TankIndicator, {
|
|
@@ -19144,52 +19190,72 @@ function VacuumHeroCard({ trpc, deviceId }) {
|
|
|
19144
19190
|
className: "flex items-center gap-6",
|
|
19145
19191
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumGraphic, { state }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19146
19192
|
className: "flex flex-col gap-3 flex-1",
|
|
19147
|
-
children: [
|
|
19148
|
-
|
|
19149
|
-
|
|
19150
|
-
|
|
19151
|
-
|
|
19152
|
-
|
|
19153
|
-
|
|
19154
|
-
|
|
19155
|
-
|
|
19156
|
-
|
|
19157
|
-
|
|
19158
|
-
|
|
19159
|
-
|
|
19160
|
-
|
|
19161
|
-
|
|
19162
|
-
|
|
19163
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
|
|
19167
|
-
|
|
19168
|
-
|
|
19169
|
-
|
|
19170
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
19171
|
-
|
|
19172
|
-
|
|
19173
|
-
|
|
19174
|
-
|
|
19175
|
-
|
|
19176
|
-
|
|
19177
|
-
|
|
19178
|
-
|
|
19179
|
-
|
|
19180
|
-
|
|
19181
|
-
|
|
19182
|
-
|
|
19183
|
-
|
|
19184
|
-
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
|
|
19188
|
-
|
|
19189
|
-
|
|
19190
|
-
|
|
19191
|
-
|
|
19192
|
-
|
|
19193
|
+
children: [
|
|
19194
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19195
|
+
className: "flex items-center justify-between",
|
|
19196
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
19197
|
+
className: cn("text-3xl font-semibold", meta.tone),
|
|
19198
|
+
children: meta.label
|
|
19199
|
+
}), slice?.batteryLevel != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBadge$2, { level: slice.batteryLevel })]
|
|
19200
|
+
}),
|
|
19201
|
+
slice?.progressPercent != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19202
|
+
className: "flex items-center gap-2",
|
|
19203
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19204
|
+
className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
|
|
19205
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19206
|
+
className: "h-full bg-primary transition-[width] duration-500",
|
|
19207
|
+
style: { width: `${Math.max(0, Math.min(100, slice.progressPercent))}%` }
|
|
19208
|
+
})
|
|
19209
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
19210
|
+
className: "tabular-nums text-xs text-foreground-subtle",
|
|
19211
|
+
children: [Math.round(slice.progressPercent), "%"]
|
|
19212
|
+
})]
|
|
19213
|
+
}),
|
|
19214
|
+
slice?.errorCode != null && slice.errorCode !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19215
|
+
className: "inline-flex items-center gap-1 text-xs text-destructive",
|
|
19216
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TriangleAlert, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: slice.errorLabel ?? `Error ${slice.errorCode}` })]
|
|
19217
|
+
}),
|
|
19218
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19219
|
+
className: "flex items-center gap-2",
|
|
19220
|
+
children: [
|
|
19221
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
19222
|
+
label: "Start",
|
|
19223
|
+
busy,
|
|
19224
|
+
active: state === "cleaning",
|
|
19225
|
+
onClick: () => press(cap.start),
|
|
19226
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-5 w-5" })
|
|
19227
|
+
}),
|
|
19228
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
19229
|
+
label: "Pause",
|
|
19230
|
+
busy,
|
|
19231
|
+
active: state === "paused",
|
|
19232
|
+
onClick: () => press(cap.pause),
|
|
19233
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-5 w-5" })
|
|
19234
|
+
}),
|
|
19235
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
19236
|
+
label: "Stop",
|
|
19237
|
+
busy,
|
|
19238
|
+
active: false,
|
|
19239
|
+
onClick: () => press(cap.stop),
|
|
19240
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Square, { className: "h-4 w-4" })
|
|
19241
|
+
}),
|
|
19242
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
19243
|
+
label: "Return to base",
|
|
19244
|
+
busy,
|
|
19245
|
+
active: state === "returning",
|
|
19246
|
+
onClick: () => press(cap.returnToBase),
|
|
19247
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(House, { className: "h-5 w-5" })
|
|
19248
|
+
}),
|
|
19249
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
19250
|
+
label: "Locate",
|
|
19251
|
+
busy,
|
|
19252
|
+
active: false,
|
|
19253
|
+
onClick: () => press(cap.locate),
|
|
19254
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Megaphone, { className: "h-5 w-5" })
|
|
19255
|
+
})
|
|
19256
|
+
]
|
|
19257
|
+
})
|
|
19258
|
+
]
|
|
19193
19259
|
})]
|
|
19194
19260
|
}),
|
|
19195
19261
|
fanSpeeds.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -19417,6 +19483,37 @@ function LawnMowerGraphic({ activity }) {
|
|
|
19417
19483
|
})]
|
|
19418
19484
|
});
|
|
19419
19485
|
}
|
|
19486
|
+
/** Tailwind text tone for a device-code severity. */
|
|
19487
|
+
function severityTone(severity) {
|
|
19488
|
+
switch (severity) {
|
|
19489
|
+
case "error": return "text-destructive";
|
|
19490
|
+
case "warning": return "text-warning";
|
|
19491
|
+
case "info": return "text-foreground-subtle";
|
|
19492
|
+
}
|
|
19493
|
+
}
|
|
19494
|
+
/** Mowing-progress bar + % — shown only when a progress value is present. */
|
|
19495
|
+
function ProgressBar({ percent }) {
|
|
19496
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19497
|
+
className: "flex items-center gap-2",
|
|
19498
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19499
|
+
className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
|
|
19500
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19501
|
+
className: "h-full bg-primary transition-[width] duration-500",
|
|
19502
|
+
style: { width: `${Math.max(0, Math.min(100, percent))}%` }
|
|
19503
|
+
})
|
|
19504
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
19505
|
+
className: "tabular-nums text-xs text-foreground-subtle",
|
|
19506
|
+
children: [Math.round(percent), "%"]
|
|
19507
|
+
})]
|
|
19508
|
+
});
|
|
19509
|
+
}
|
|
19510
|
+
/** Current device/event code line — tinted by severity; an icon for warn/error. */
|
|
19511
|
+
function CodeLine({ code, label, severity }) {
|
|
19512
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19513
|
+
className: cn("inline-flex items-center gap-1 text-xs", severityTone(severity)),
|
|
19514
|
+
children: [severity !== "info" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TriangleAlert, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label ?? `Code ${code}` })]
|
|
19515
|
+
});
|
|
19516
|
+
}
|
|
19420
19517
|
/** Battery % badge — shared between inline + hero. */
|
|
19421
19518
|
function BatteryBadge$1({ level }) {
|
|
19422
19519
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -19520,38 +19617,47 @@ function LawnMowerHeroCard({ trpc, deviceId }) {
|
|
|
19520
19617
|
className: "flex items-center gap-6",
|
|
19521
19618
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LawnMowerGraphic, { activity }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19522
19619
|
className: "flex flex-col gap-3 flex-1",
|
|
19523
|
-
children: [
|
|
19524
|
-
|
|
19525
|
-
|
|
19526
|
-
|
|
19527
|
-
|
|
19528
|
-
|
|
19529
|
-
|
|
19530
|
-
|
|
19531
|
-
|
|
19532
|
-
|
|
19533
|
-
|
|
19534
|
-
|
|
19535
|
-
|
|
19536
|
-
|
|
19537
|
-
|
|
19538
|
-
|
|
19539
|
-
|
|
19540
|
-
|
|
19541
|
-
|
|
19542
|
-
|
|
19543
|
-
|
|
19544
|
-
|
|
19545
|
-
|
|
19546
|
-
|
|
19547
|
-
|
|
19548
|
-
|
|
19549
|
-
|
|
19550
|
-
|
|
19551
|
-
|
|
19552
|
-
|
|
19553
|
-
|
|
19554
|
-
|
|
19620
|
+
children: [
|
|
19621
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19622
|
+
className: "flex items-center justify-between",
|
|
19623
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
19624
|
+
className: cn("text-3xl font-semibold", meta.tone),
|
|
19625
|
+
children: meta.label
|
|
19626
|
+
}), slice?.batteryLevel != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBadge$1, { level: slice.batteryLevel })]
|
|
19627
|
+
}),
|
|
19628
|
+
slice?.progressPercent != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProgressBar, { percent: slice.progressPercent }),
|
|
19629
|
+
slice?.currentCode != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodeLine, {
|
|
19630
|
+
code: slice.currentCode,
|
|
19631
|
+
label: slice.currentCodeLabel,
|
|
19632
|
+
severity: slice.severity
|
|
19633
|
+
}),
|
|
19634
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19635
|
+
className: "flex items-center gap-2",
|
|
19636
|
+
children: [
|
|
19637
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LawnMowerHeroButton, {
|
|
19638
|
+
label: "Start mowing",
|
|
19639
|
+
busy,
|
|
19640
|
+
active: activity === "mowing",
|
|
19641
|
+
onClick: () => press(cap.startMowing),
|
|
19642
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-5 w-5" })
|
|
19643
|
+
}),
|
|
19644
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LawnMowerHeroButton, {
|
|
19645
|
+
label: "Pause",
|
|
19646
|
+
busy,
|
|
19647
|
+
active: activity === "paused",
|
|
19648
|
+
onClick: () => press(cap.pause),
|
|
19649
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-5 w-5" })
|
|
19650
|
+
}),
|
|
19651
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LawnMowerHeroButton, {
|
|
19652
|
+
label: "Dock",
|
|
19653
|
+
busy,
|
|
19654
|
+
active: activity === "docked",
|
|
19655
|
+
onClick: () => press(cap.dock),
|
|
19656
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(House, { className: "h-5 w-5" })
|
|
19657
|
+
})
|
|
19658
|
+
]
|
|
19659
|
+
})
|
|
19660
|
+
]
|
|
19555
19661
|
})]
|
|
19556
19662
|
})
|
|
19557
19663
|
});
|
|
@@ -21285,103 +21391,6 @@ function WeatherHeroCard({ trpc, deviceId }) {
|
|
|
21285
21391
|
});
|
|
21286
21392
|
}
|
|
21287
21393
|
//#endregion
|
|
21288
|
-
//#region src/composites/hover-zoom-image.tsx
|
|
21289
|
-
/**
|
|
21290
|
-
* <HoverZoomImage> — a small thumbnail that expands into a floating
|
|
21291
|
-
* enlarged preview on hover/focus.
|
|
21292
|
-
*
|
|
21293
|
-
* One responsibility: render a compact thumbnail (image when `src` is
|
|
21294
|
-
* present, a muted `ImageOff` placeholder box when null) and, while the
|
|
21295
|
-
* thumbnail is hovered or focused, portal a larger preview to
|
|
21296
|
-
* `document.body` positioned with FLIP-ON-COLLISION:
|
|
21297
|
-
* - the position is computed from the thumbnail's `getBoundingClientRect`;
|
|
21298
|
-
* - placed ABOVE the thumbnail unless there is insufficient space (then
|
|
21299
|
-
* below); left-clamped to the viewport;
|
|
21300
|
-
* - `position:fixed`, `pointer-events:none`, `z-[9999]`.
|
|
21301
|
-
*
|
|
21302
|
-
* The portal is what lets this work inside the device grid, whose overflow
|
|
21303
|
-
* ancestors would otherwise clip a normally-positioned popover.
|
|
21304
|
-
*
|
|
21305
|
-
* Reused by BOTH the Image DeviceType inline control (signed image url) and
|
|
21306
|
-
* the camera-snapshot cell in device rows (snapshot blob/object url).
|
|
21307
|
-
*/
|
|
21308
|
-
var DEFAULT_EXPANDED_WIDTH = 288;
|
|
21309
|
-
var DEFAULT_EXPANDED_HEIGHT = 196;
|
|
21310
|
-
var DEFAULT_THUMB_CLASS = "h-7 w-7 rounded";
|
|
21311
|
-
var TRIGGER_GAP = 8;
|
|
21312
|
-
var VIEWPORT_MARGIN = 8;
|
|
21313
|
-
function HoverZoomImage({ src, alt, caption, placeholder = "No image", thumbClassName = DEFAULT_THUMB_CLASS, expandedWidth = DEFAULT_EXPANDED_WIDTH, expandedHeight = DEFAULT_EXPANDED_HEIGHT }) {
|
|
21314
|
-
const triggerRef = (0, react$1.useRef)(null);
|
|
21315
|
-
const [position, setPosition] = (0, react$1.useState)(null);
|
|
21316
|
-
const [errored, setErrored] = (0, react$1.useState)(false);
|
|
21317
|
-
(0, react$1.useEffect)(() => {
|
|
21318
|
-
setErrored(false);
|
|
21319
|
-
}, [src]);
|
|
21320
|
-
const hasImage = src !== null && !errored;
|
|
21321
|
-
const safeSrc = src ?? void 0;
|
|
21322
|
-
const open = (0, react$1.useCallback)(() => {
|
|
21323
|
-
const el = triggerRef.current;
|
|
21324
|
-
if (!el) return;
|
|
21325
|
-
const rect = el.getBoundingClientRect();
|
|
21326
|
-
const spaceAbove = rect.top;
|
|
21327
|
-
const spaceBelow = window.innerHeight - rect.bottom;
|
|
21328
|
-
const top = spaceAbove < expandedHeight + TRIGGER_GAP && spaceBelow > spaceAbove ? rect.bottom + TRIGGER_GAP : rect.top - expandedHeight - TRIGGER_GAP;
|
|
21329
|
-
const desiredLeft = rect.right - expandedWidth;
|
|
21330
|
-
setPosition({
|
|
21331
|
-
top,
|
|
21332
|
-
left: Math.max(VIEWPORT_MARGIN, Math.min(desiredLeft, window.innerWidth - expandedWidth - VIEWPORT_MARGIN))
|
|
21333
|
-
});
|
|
21334
|
-
}, [expandedWidth, expandedHeight]);
|
|
21335
|
-
const close = (0, react$1.useCallback)(() => setPosition(null), []);
|
|
21336
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
21337
|
-
ref: triggerRef,
|
|
21338
|
-
type: "button",
|
|
21339
|
-
onClick: (e) => e.stopPropagation(),
|
|
21340
|
-
onMouseEnter: open,
|
|
21341
|
-
onMouseLeave: close,
|
|
21342
|
-
onFocus: open,
|
|
21343
|
-
onBlur: close,
|
|
21344
|
-
"aria-label": alt,
|
|
21345
|
-
className: `flex flex-shrink-0 items-center justify-center overflow-hidden border border-border bg-background text-foreground-subtle transition-colors hover:bg-surface-hover ${thumbClassName}`,
|
|
21346
|
-
children: hasImage ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
21347
|
-
src: safeSrc,
|
|
21348
|
-
alt: "",
|
|
21349
|
-
onError: () => setErrored(true),
|
|
21350
|
-
className: "h-full w-full object-cover"
|
|
21351
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageOff, {
|
|
21352
|
-
className: "h-3.5 w-3.5",
|
|
21353
|
-
"aria-hidden": "true"
|
|
21354
|
-
})
|
|
21355
|
-
}), position && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
21356
|
-
"aria-hidden": "true",
|
|
21357
|
-
style: {
|
|
21358
|
-
position: "fixed",
|
|
21359
|
-
top: position.top,
|
|
21360
|
-
left: position.left,
|
|
21361
|
-
width: expandedWidth,
|
|
21362
|
-
pointerEvents: "none"
|
|
21363
|
-
},
|
|
21364
|
-
className: "z-[9999] overflow-hidden rounded-lg border border-border bg-surface shadow-lg shadow-black/30",
|
|
21365
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21366
|
-
className: "flex h-40 items-center justify-center bg-background",
|
|
21367
|
-
children: hasImage ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
21368
|
-
src: safeSrc,
|
|
21369
|
-
alt,
|
|
21370
|
-
className: "h-full w-full object-cover"
|
|
21371
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
21372
|
-
className: "text-xs text-foreground-subtle",
|
|
21373
|
-
children: placeholder
|
|
21374
|
-
})
|
|
21375
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21376
|
-
className: "px-3 py-2",
|
|
21377
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
21378
|
-
className: "truncate text-xs font-semibold text-foreground",
|
|
21379
|
-
children: caption ?? alt
|
|
21380
|
-
})
|
|
21381
|
-
})]
|
|
21382
|
-
}), document.body)] });
|
|
21383
|
-
}
|
|
21384
|
-
//#endregion
|
|
21385
21394
|
//#region src/composites/device-controls/image-control.tsx
|
|
21386
21395
|
/**
|
|
21387
21396
|
* Image controls for a `DeviceType.Image` device.
|
|
@@ -21405,6 +21414,91 @@ function useImage(trpc, deviceId) {
|
|
|
21405
21414
|
slice: (d) => d.state.image
|
|
21406
21415
|
});
|
|
21407
21416
|
}
|
|
21417
|
+
var ZOOM_MIN = 1;
|
|
21418
|
+
var ZOOM_MAX = 6;
|
|
21419
|
+
var ZOOM_STEP = .5;
|
|
21420
|
+
/**
|
|
21421
|
+
* ImageDetailModal — a full-view image dialog with zoom in/out/reset and pan
|
|
21422
|
+
* (scroll when zoomed). Generous padding frames the image. Opened by clicking
|
|
21423
|
+
* the inline thumbnail or the hero image. Controlled via `open`/`onOpenChange`.
|
|
21424
|
+
*/
|
|
21425
|
+
function ImageDetailModal({ url, alt, open, onOpenChange }) {
|
|
21426
|
+
const [scale, setScale] = (0, react$1.useState)(1);
|
|
21427
|
+
(0, react$1.useEffect)(() => {
|
|
21428
|
+
if (open) setScale(1);
|
|
21429
|
+
}, [open, url]);
|
|
21430
|
+
const zoomIn = (0, react$1.useCallback)(() => setScale((s) => Math.min(ZOOM_MAX, s + ZOOM_STEP)), []);
|
|
21431
|
+
const zoomOut = (0, react$1.useCallback)(() => setScale((s) => Math.max(ZOOM_MIN, s - ZOOM_STEP)), []);
|
|
21432
|
+
const reset = (0, react$1.useCallback)(() => setScale(1), []);
|
|
21433
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
|
|
21434
|
+
open,
|
|
21435
|
+
onOpenChange,
|
|
21436
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogContent, {
|
|
21437
|
+
width: "lg",
|
|
21438
|
+
className: "max-w-5xl p-6",
|
|
21439
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
21440
|
+
className: "flex items-center justify-between gap-2 mb-3",
|
|
21441
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
21442
|
+
className: "truncate text-sm font-semibold text-foreground",
|
|
21443
|
+
children: alt
|
|
21444
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
21445
|
+
className: "flex items-center gap-1",
|
|
21446
|
+
children: [
|
|
21447
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModalIconButton, {
|
|
21448
|
+
label: "Zoom out",
|
|
21449
|
+
disabled: scale <= ZOOM_MIN,
|
|
21450
|
+
onClick: zoomOut,
|
|
21451
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomOut, { className: "h-4 w-4" })
|
|
21452
|
+
}),
|
|
21453
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
21454
|
+
className: "tabular-nums text-xs text-foreground-subtle w-10 text-center",
|
|
21455
|
+
children: [Math.round(scale * 100), "%"]
|
|
21456
|
+
}),
|
|
21457
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModalIconButton, {
|
|
21458
|
+
label: "Zoom in",
|
|
21459
|
+
disabled: scale >= ZOOM_MAX,
|
|
21460
|
+
onClick: zoomIn,
|
|
21461
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomIn, { className: "h-4 w-4" })
|
|
21462
|
+
}),
|
|
21463
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModalIconButton, {
|
|
21464
|
+
label: "Reset zoom",
|
|
21465
|
+
disabled: scale === 1,
|
|
21466
|
+
onClick: reset,
|
|
21467
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RotateCcw, { className: "h-4 w-4" })
|
|
21468
|
+
}),
|
|
21469
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModalIconButton, {
|
|
21470
|
+
label: "Close",
|
|
21471
|
+
onClick: () => onOpenChange(false),
|
|
21472
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(X, { className: "h-4 w-4" })
|
|
21473
|
+
})
|
|
21474
|
+
]
|
|
21475
|
+
})]
|
|
21476
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21477
|
+
className: "flex max-h-[75vh] items-center justify-center overflow-auto rounded border border-border bg-gradient-to-b from-surface-hover to-surface p-6",
|
|
21478
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
21479
|
+
src: url,
|
|
21480
|
+
alt,
|
|
21481
|
+
style: {
|
|
21482
|
+
transform: `scale(${scale})`,
|
|
21483
|
+
transformOrigin: "center"
|
|
21484
|
+
},
|
|
21485
|
+
className: "max-h-[65vh] w-auto object-contain transition-transform duration-150"
|
|
21486
|
+
})
|
|
21487
|
+
})]
|
|
21488
|
+
})
|
|
21489
|
+
});
|
|
21490
|
+
}
|
|
21491
|
+
function ModalIconButton({ label, disabled, onClick, children }) {
|
|
21492
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
21493
|
+
type: "button",
|
|
21494
|
+
"aria-label": label,
|
|
21495
|
+
title: label,
|
|
21496
|
+
disabled,
|
|
21497
|
+
onClick,
|
|
21498
|
+
className: "inline-flex h-7 w-7 items-center justify-center rounded text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
|
|
21499
|
+
children
|
|
21500
|
+
});
|
|
21501
|
+
}
|
|
21408
21502
|
/**
|
|
21409
21503
|
* ImageInlineControl — compact thumbnail for a device-list row that expands
|
|
21410
21504
|
* into a floating preview on hover/focus (shared `HoverZoomImage`). Shows a
|
|
@@ -21415,13 +21509,36 @@ function useImage(trpc, deviceId) {
|
|
|
21415
21509
|
*/
|
|
21416
21510
|
function ImageInlineControl({ trpc, deviceId }) {
|
|
21417
21511
|
const { slice } = useImage(trpc, deviceId);
|
|
21512
|
+
const [open, setOpen] = (0, react$1.useState)(false);
|
|
21418
21513
|
if (slice === void 0) return null;
|
|
21419
|
-
|
|
21420
|
-
|
|
21421
|
-
|
|
21422
|
-
|
|
21423
|
-
|
|
21514
|
+
const url = slice.url;
|
|
21515
|
+
if (url === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
21516
|
+
className: "inline-flex h-7 w-7 items-center justify-center rounded bg-surface-hover text-foreground-subtle",
|
|
21517
|
+
title: "No image",
|
|
21518
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageOff, {
|
|
21519
|
+
className: "h-4 w-4",
|
|
21520
|
+
"aria-hidden": "true"
|
|
21521
|
+
})
|
|
21424
21522
|
});
|
|
21523
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
21524
|
+
type: "button",
|
|
21525
|
+
title: "View image",
|
|
21526
|
+
onClick: (e) => {
|
|
21527
|
+
e.stopPropagation();
|
|
21528
|
+
setOpen(true);
|
|
21529
|
+
},
|
|
21530
|
+
className: "inline-flex items-center justify-center rounded ring-offset-1 hover:ring-2 hover:ring-primary/50 transition",
|
|
21531
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
21532
|
+
src: url,
|
|
21533
|
+
alt: "Image",
|
|
21534
|
+
className: "h-7 w-7 rounded object-cover"
|
|
21535
|
+
})
|
|
21536
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageDetailModal, {
|
|
21537
|
+
url,
|
|
21538
|
+
alt: "Image",
|
|
21539
|
+
open,
|
|
21540
|
+
onOpenChange: setOpen
|
|
21541
|
+
})] });
|
|
21425
21542
|
}
|
|
21426
21543
|
/**
|
|
21427
21544
|
* ImageHeroCard — read-only framed image for the device-detail hero.
|
|
@@ -21434,19 +21551,27 @@ function ImageInlineControl({ trpc, deviceId }) {
|
|
|
21434
21551
|
function ImageHeroCard({ trpc, deviceId, deviceName }) {
|
|
21435
21552
|
const { slice } = useImage(trpc, deviceId);
|
|
21436
21553
|
const [errored, setErrored] = (0, react$1.useState)(false);
|
|
21554
|
+
const [open, setOpen] = (0, react$1.useState)(false);
|
|
21555
|
+
const url = slice?.url ?? null;
|
|
21437
21556
|
(0, react$1.useEffect)(() => {
|
|
21438
21557
|
setErrored(false);
|
|
21439
|
-
}, [
|
|
21558
|
+
}, [url]);
|
|
21440
21559
|
if (slice === void 0) return null;
|
|
21441
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
21560
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
21442
21561
|
className: "flex flex-col items-stretch gap-3 rounded-lg border border-border bg-surface p-4",
|
|
21443
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21562
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21444
21563
|
className: "flex w-full items-center justify-center overflow-hidden rounded border-2 border-border bg-gradient-to-b from-surface-hover to-surface",
|
|
21445
|
-
children: slice.url !== null && !errored ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
21446
|
-
|
|
21447
|
-
|
|
21448
|
-
|
|
21449
|
-
className: "
|
|
21564
|
+
children: slice.url !== null && !errored ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
21565
|
+
type: "button",
|
|
21566
|
+
title: "View image",
|
|
21567
|
+
onClick: () => setOpen(true),
|
|
21568
|
+
className: "flex w-full cursor-zoom-in items-center justify-center",
|
|
21569
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
21570
|
+
src: slice.url,
|
|
21571
|
+
alt: deviceName ?? "Image",
|
|
21572
|
+
onError: () => setErrored(true),
|
|
21573
|
+
className: "max-h-72 w-full object-contain"
|
|
21574
|
+
})
|
|
21450
21575
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
21451
21576
|
"data-testid": "image-placeholder",
|
|
21452
21577
|
className: "flex h-48 w-full flex-col items-center justify-center gap-2 text-foreground-subtle",
|
|
@@ -21458,7 +21583,12 @@ function ImageHeroCard({ trpc, deviceId, deviceName }) {
|
|
|
21458
21583
|
children: "No image"
|
|
21459
21584
|
})]
|
|
21460
21585
|
})
|
|
21461
|
-
})
|
|
21586
|
+
}), url !== null && !errored && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageDetailModal, {
|
|
21587
|
+
url,
|
|
21588
|
+
alt: deviceName ?? "Image",
|
|
21589
|
+
open,
|
|
21590
|
+
onOpenChange: setOpen
|
|
21591
|
+
})]
|
|
21462
21592
|
});
|
|
21463
21593
|
}
|
|
21464
21594
|
//#endregion
|
|
@@ -25017,6 +25147,103 @@ function BatteryBadge({ status, variant = "compact", className }) {
|
|
|
25017
25147
|
});
|
|
25018
25148
|
}
|
|
25019
25149
|
//#endregion
|
|
25150
|
+
//#region src/composites/hover-zoom-image.tsx
|
|
25151
|
+
/**
|
|
25152
|
+
* <HoverZoomImage> — a small thumbnail that expands into a floating
|
|
25153
|
+
* enlarged preview on hover/focus.
|
|
25154
|
+
*
|
|
25155
|
+
* One responsibility: render a compact thumbnail (image when `src` is
|
|
25156
|
+
* present, a muted `ImageOff` placeholder box when null) and, while the
|
|
25157
|
+
* thumbnail is hovered or focused, portal a larger preview to
|
|
25158
|
+
* `document.body` positioned with FLIP-ON-COLLISION:
|
|
25159
|
+
* - the position is computed from the thumbnail's `getBoundingClientRect`;
|
|
25160
|
+
* - placed ABOVE the thumbnail unless there is insufficient space (then
|
|
25161
|
+
* below); left-clamped to the viewport;
|
|
25162
|
+
* - `position:fixed`, `pointer-events:none`, `z-[9999]`.
|
|
25163
|
+
*
|
|
25164
|
+
* The portal is what lets this work inside the device grid, whose overflow
|
|
25165
|
+
* ancestors would otherwise clip a normally-positioned popover.
|
|
25166
|
+
*
|
|
25167
|
+
* Reused by BOTH the Image DeviceType inline control (signed image url) and
|
|
25168
|
+
* the camera-snapshot cell in device rows (snapshot blob/object url).
|
|
25169
|
+
*/
|
|
25170
|
+
var DEFAULT_EXPANDED_WIDTH = 288;
|
|
25171
|
+
var DEFAULT_EXPANDED_HEIGHT = 196;
|
|
25172
|
+
var DEFAULT_THUMB_CLASS = "h-7 w-7 rounded";
|
|
25173
|
+
var TRIGGER_GAP = 8;
|
|
25174
|
+
var VIEWPORT_MARGIN = 8;
|
|
25175
|
+
function HoverZoomImage({ src, alt, caption, placeholder = "No image", thumbClassName = DEFAULT_THUMB_CLASS, expandedWidth = DEFAULT_EXPANDED_WIDTH, expandedHeight = DEFAULT_EXPANDED_HEIGHT }) {
|
|
25176
|
+
const triggerRef = (0, react$1.useRef)(null);
|
|
25177
|
+
const [position, setPosition] = (0, react$1.useState)(null);
|
|
25178
|
+
const [errored, setErrored] = (0, react$1.useState)(false);
|
|
25179
|
+
(0, react$1.useEffect)(() => {
|
|
25180
|
+
setErrored(false);
|
|
25181
|
+
}, [src]);
|
|
25182
|
+
const hasImage = src !== null && !errored;
|
|
25183
|
+
const safeSrc = src ?? void 0;
|
|
25184
|
+
const open = (0, react$1.useCallback)(() => {
|
|
25185
|
+
const el = triggerRef.current;
|
|
25186
|
+
if (!el) return;
|
|
25187
|
+
const rect = el.getBoundingClientRect();
|
|
25188
|
+
const spaceAbove = rect.top;
|
|
25189
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
25190
|
+
const top = spaceAbove < expandedHeight + TRIGGER_GAP && spaceBelow > spaceAbove ? rect.bottom + TRIGGER_GAP : rect.top - expandedHeight - TRIGGER_GAP;
|
|
25191
|
+
const desiredLeft = rect.right - expandedWidth;
|
|
25192
|
+
setPosition({
|
|
25193
|
+
top,
|
|
25194
|
+
left: Math.max(VIEWPORT_MARGIN, Math.min(desiredLeft, window.innerWidth - expandedWidth - VIEWPORT_MARGIN))
|
|
25195
|
+
});
|
|
25196
|
+
}, [expandedWidth, expandedHeight]);
|
|
25197
|
+
const close = (0, react$1.useCallback)(() => setPosition(null), []);
|
|
25198
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
25199
|
+
ref: triggerRef,
|
|
25200
|
+
type: "button",
|
|
25201
|
+
onClick: (e) => e.stopPropagation(),
|
|
25202
|
+
onMouseEnter: open,
|
|
25203
|
+
onMouseLeave: close,
|
|
25204
|
+
onFocus: open,
|
|
25205
|
+
onBlur: close,
|
|
25206
|
+
"aria-label": alt,
|
|
25207
|
+
className: `flex flex-shrink-0 items-center justify-center overflow-hidden border border-border bg-background text-foreground-subtle transition-colors hover:bg-surface-hover ${thumbClassName}`,
|
|
25208
|
+
children: hasImage ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
25209
|
+
src: safeSrc,
|
|
25210
|
+
alt: "",
|
|
25211
|
+
onError: () => setErrored(true),
|
|
25212
|
+
className: "h-full w-full object-cover"
|
|
25213
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageOff, {
|
|
25214
|
+
className: "h-3.5 w-3.5",
|
|
25215
|
+
"aria-hidden": "true"
|
|
25216
|
+
})
|
|
25217
|
+
}), position && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
25218
|
+
"aria-hidden": "true",
|
|
25219
|
+
style: {
|
|
25220
|
+
position: "fixed",
|
|
25221
|
+
top: position.top,
|
|
25222
|
+
left: position.left,
|
|
25223
|
+
width: expandedWidth,
|
|
25224
|
+
pointerEvents: "none"
|
|
25225
|
+
},
|
|
25226
|
+
className: "z-[9999] overflow-hidden rounded-lg border border-border bg-surface shadow-lg shadow-black/30",
|
|
25227
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
25228
|
+
className: "flex h-40 items-center justify-center bg-background",
|
|
25229
|
+
children: hasImage ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
25230
|
+
src: safeSrc,
|
|
25231
|
+
alt,
|
|
25232
|
+
className: "h-full w-full object-cover"
|
|
25233
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
25234
|
+
className: "text-xs text-foreground-subtle",
|
|
25235
|
+
children: placeholder
|
|
25236
|
+
})
|
|
25237
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
25238
|
+
className: "px-3 py-2",
|
|
25239
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
25240
|
+
className: "truncate text-xs font-semibold text-foreground",
|
|
25241
|
+
children: caption ?? alt
|
|
25242
|
+
})
|
|
25243
|
+
})]
|
|
25244
|
+
}), document.body)] });
|
|
25245
|
+
}
|
|
25246
|
+
//#endregion
|
|
25020
25247
|
//#region src/composites/device-item/preview.tsx
|
|
25021
25248
|
function DeviceItemPreview({ trpc, device, status, enabled, showStatusPills = false }) {
|
|
25022
25249
|
const snapshot = useDeviceSnapshotImage(trpc, enabled && device.online ? device.id : null);
|
|
@@ -42847,6 +43074,7 @@ exports.useDeviceManagerAdoptionAdopt = useDeviceManagerAdoptionAdopt;
|
|
|
42847
43074
|
exports.useDeviceManagerAdoptionListCandidates = useDeviceManagerAdoptionListCandidates;
|
|
42848
43075
|
exports.useDeviceManagerAdoptionRefresh = useDeviceManagerAdoptionRefresh;
|
|
42849
43076
|
exports.useDeviceManagerAdoptionRelease = useDeviceManagerAdoptionRelease;
|
|
43077
|
+
exports.useDeviceManagerAdoptionResync = useDeviceManagerAdoptionResync;
|
|
42850
43078
|
exports.useDeviceManagerAllocateDeviceId = useDeviceManagerAllocateDeviceId;
|
|
42851
43079
|
exports.useDeviceManagerApplyInitialMeta = useDeviceManagerApplyInitialMeta;
|
|
42852
43080
|
exports.useDeviceManagerCreateDevice = useDeviceManagerCreateDevice;
|