@camstack/ui-library 1.2.380 → 1.2.381
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 +6 -0
- package/dist/composites/device-export-panel.d.ts +10 -5
- package/dist/index.cjs +123 -37
- package/dist/index.js +123 -37
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.381] - 2026-11-04
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- The device export panel shows each export instance on its own (one section per Home Assistant broker), writes to the instance you act on, and reads a disabled instance as disabled
|
|
15
|
+
|
|
10
16
|
## [1.2.380] - 2026-11-04
|
|
11
17
|
|
|
12
18
|
### Changed
|
|
@@ -28,15 +28,20 @@ export interface DeviceExportPanelProps {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* The exporter's error and Setup block on their own — for a page that shows
|
|
31
|
-
* them above its settings while the devices table sits at the bottom.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* them above its settings while the devices table sits at the bottom. One block
|
|
32
|
+
* per ENABLED instance (named when there are several); renders nothing while
|
|
33
|
+
* the instances are unanswered or when none reports an error or a setup. Shares
|
|
34
|
+
* `getStatus`'s query key with the panel, so mounting both costs one request.
|
|
34
35
|
*/
|
|
35
36
|
export declare function DeviceExportSetup({ addonId }: {
|
|
36
37
|
readonly addonId: string;
|
|
37
38
|
}): ReactElement | null;
|
|
38
39
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
40
|
+
* The export surface of one exporter addon, per export INSTANCE. The instances
|
|
41
|
+
* are asked of the provider (`listInstances`), never assumed: one instance
|
|
42
|
+
* renders the table alone, several render one named section each (one per
|
|
43
|
+
* Home Assistant broker), and every read and write names the instance it acts
|
|
44
|
+
* on. A disabled instance reads as disabled; an unanswered list reads as
|
|
45
|
+
* loading or unknown, never as "nothing exposed".
|
|
41
46
|
*/
|
|
42
47
|
export declare function DeviceExportPanel({ addonId, onOpenDevice, showSetup }: DeviceExportPanelProps): ReactElement;
|
package/dist/index.cjs
CHANGED
|
@@ -63460,16 +63460,26 @@ function parseStatus(data) {
|
|
|
63460
63460
|
const parsed = _camstack_types.DeviceExportStatusSchema.safeParse(data);
|
|
63461
63461
|
return parsed.success ? parsed.data : null;
|
|
63462
63462
|
}
|
|
63463
|
-
|
|
63464
|
-
|
|
63465
|
-
* them above its settings while the devices table sits at the bottom. Renders
|
|
63466
|
-
* nothing when the exporter reports neither. Shares `getStatus`'s query key
|
|
63467
|
-
* with the panel, so mounting both costs one request.
|
|
63468
|
-
*/
|
|
63469
|
-
function DeviceExportSetup({ addonId }) {
|
|
63470
|
-
const statusQuery = useDeviceExportGetStatus({
|
|
63463
|
+
function useExportInstances(addonId) {
|
|
63464
|
+
const query = useDeviceExportListInstances({
|
|
63471
63465
|
nodeId: "hub",
|
|
63472
63466
|
addonId
|
|
63467
|
+
}, { retry: false });
|
|
63468
|
+
return (0, react$1.useMemo)(() => {
|
|
63469
|
+
if (query.data === void 0) return query.isError ? { kind: "unknown" } : { kind: "pending" };
|
|
63470
|
+
const parsed = ExportInstanceArraySchema.safeParse(query.data);
|
|
63471
|
+
return parsed.success ? {
|
|
63472
|
+
kind: "answered",
|
|
63473
|
+
instances: parsed.data
|
|
63474
|
+
} : { kind: "unknown" };
|
|
63475
|
+
}, [query.data, query.isError]);
|
|
63476
|
+
}
|
|
63477
|
+
/** One instance's error and Setup block. Renders nothing when it reports neither. */
|
|
63478
|
+
function InstanceSetup({ addonId, instance, named }) {
|
|
63479
|
+
const statusQuery = useDeviceExportGetStatus({
|
|
63480
|
+
nodeId: "hub",
|
|
63481
|
+
addonId,
|
|
63482
|
+
instanceId: instance.instanceId
|
|
63473
63483
|
}, {
|
|
63474
63484
|
refetchInterval: STATUS_POLL_INTERVAL_MS,
|
|
63475
63485
|
retry: false
|
|
@@ -63478,12 +63488,37 @@ function DeviceExportSetup({ addonId }) {
|
|
|
63478
63488
|
if (!status?.error && !status?.setup) return null;
|
|
63479
63489
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
63480
63490
|
className: "rounded-lg border border-border bg-surface overflow-hidden",
|
|
63481
|
-
children: [
|
|
63482
|
-
|
|
63483
|
-
|
|
63484
|
-
|
|
63491
|
+
children: [
|
|
63492
|
+
named && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
63493
|
+
className: "px-2 sm:px-4 py-2 border-b border-border text-xs font-medium text-foreground",
|
|
63494
|
+
children: instance.name
|
|
63495
|
+
}),
|
|
63496
|
+
status.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
63497
|
+
className: "px-2 sm:px-4 py-3 border-b border-border",
|
|
63498
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBox, { message: status.error })
|
|
63499
|
+
}),
|
|
63500
|
+
status.setup && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SetupSection, { setup: status.setup })
|
|
63501
|
+
]
|
|
63485
63502
|
});
|
|
63486
63503
|
}
|
|
63504
|
+
/**
|
|
63505
|
+
* The exporter's error and Setup block on their own — for a page that shows
|
|
63506
|
+
* them above its settings while the devices table sits at the bottom. One block
|
|
63507
|
+
* per ENABLED instance (named when there are several); renders nothing while
|
|
63508
|
+
* the instances are unanswered or when none reports an error or a setup. Shares
|
|
63509
|
+
* `getStatus`'s query key with the panel, so mounting both costs one request.
|
|
63510
|
+
*/
|
|
63511
|
+
function DeviceExportSetup({ addonId }) {
|
|
63512
|
+
const state = useExportInstances(addonId);
|
|
63513
|
+
if (state.kind !== "answered") return null;
|
|
63514
|
+
const enabled = state.instances.filter((i) => i.enabled);
|
|
63515
|
+
if (enabled.length === 0) return null;
|
|
63516
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: enabled.map((instance) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstanceSetup, {
|
|
63517
|
+
addonId,
|
|
63518
|
+
instance,
|
|
63519
|
+
named: state.instances.length > 1
|
|
63520
|
+
}, instance.instanceId)) });
|
|
63521
|
+
}
|
|
63487
63522
|
function ExportSelectCell({ device, checked, onToggle }) {
|
|
63488
63523
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
63489
63524
|
type: "checkbox",
|
|
@@ -63579,30 +63614,30 @@ function BulkSummary({ outcome, onDismiss }) {
|
|
|
63579
63614
|
]
|
|
63580
63615
|
});
|
|
63581
63616
|
}
|
|
63582
|
-
|
|
63583
|
-
|
|
63584
|
-
* cap drives it.
|
|
63585
|
-
*/
|
|
63586
|
-
function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
63617
|
+
function DeviceExportInstancePanel({ addonId, instance, named, onOpenDevice, showSetup = true }) {
|
|
63618
|
+
const instanceId = instance.instanceId;
|
|
63587
63619
|
const t = useUiText();
|
|
63588
63620
|
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
63589
63621
|
const statusQuery = useDeviceExportGetStatus({
|
|
63590
63622
|
nodeId: "hub",
|
|
63591
|
-
addonId
|
|
63623
|
+
addonId,
|
|
63624
|
+
instanceId
|
|
63592
63625
|
}, {
|
|
63593
63626
|
refetchInterval: STATUS_POLL_INTERVAL_MS,
|
|
63594
63627
|
retry: false
|
|
63595
63628
|
});
|
|
63596
63629
|
const exposedQuery = useDeviceExportListExposedDevices({
|
|
63597
63630
|
nodeId: "hub",
|
|
63598
|
-
addonId
|
|
63631
|
+
addonId,
|
|
63632
|
+
instanceId
|
|
63599
63633
|
}, {
|
|
63600
63634
|
refetchInterval: STATUS_POLL_INTERVAL_MS,
|
|
63601
63635
|
retry: false
|
|
63602
63636
|
});
|
|
63603
63637
|
const kindsQuery = useDeviceExportListSupportedDeviceKinds({
|
|
63604
63638
|
nodeId: "hub",
|
|
63605
|
-
addonId
|
|
63639
|
+
addonId,
|
|
63640
|
+
instanceId
|
|
63606
63641
|
}, { retry: false });
|
|
63607
63642
|
const devicesQuery = useDeviceManagerListAll({ projection: "slim" }, { refetchInterval: FLEET_POLL_INTERVAL_MS });
|
|
63608
63643
|
const integrationsQuery = useIntegrationsList(void 0);
|
|
@@ -63617,7 +63652,8 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
63617
63652
|
const entityKinds = (0, react$1.useMemo)(() => supportedEntityKinds(entityKindsQuery.data), [entityKindsQuery.data]);
|
|
63618
63653
|
const exposedEntitiesQuery = useDeviceExportListExposedEntities({
|
|
63619
63654
|
nodeId: "hub",
|
|
63620
|
-
addonId
|
|
63655
|
+
addonId,
|
|
63656
|
+
instanceId
|
|
63621
63657
|
}, {
|
|
63622
63658
|
refetchInterval: STATUS_POLL_INTERVAL_MS,
|
|
63623
63659
|
retry: false,
|
|
@@ -63634,15 +63670,6 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
63634
63670
|
const invalidate = (0, react$1.useCallback)(() => {
|
|
63635
63671
|
queryClient.invalidateQueries({ queryKey: [["deviceExport"]] });
|
|
63636
63672
|
}, [queryClient]);
|
|
63637
|
-
const instancesQuery = useDeviceExportListInstances({
|
|
63638
|
-
nodeId: "hub",
|
|
63639
|
-
addonId
|
|
63640
|
-
}, { retry: false });
|
|
63641
|
-
const targetInstanceIds = (0, react$1.useMemo)(() => {
|
|
63642
|
-
const parsed = ExportInstanceArraySchema.safeParse(instancesQuery.data);
|
|
63643
|
-
if (!parsed.success) return null;
|
|
63644
|
-
return parsed.data.filter((i) => i.enabled).map((i) => i.instanceId);
|
|
63645
|
-
}, [instancesQuery.data]);
|
|
63646
63673
|
const exposeMutation = useDeviceExportExposeDevice({ onSuccess: invalidate });
|
|
63647
63674
|
const unexposeMutation = useDeviceExportUnexposeDevice({ onSuccess: invalidate });
|
|
63648
63675
|
const exposeEntityMutation = useDeviceExportExposeEntity({ onSuccess: invalidate });
|
|
@@ -63739,8 +63766,7 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
63739
63766
|
for (const deviceId of deviceIds) {
|
|
63740
63767
|
const label = labelFor(deviceId);
|
|
63741
63768
|
try {
|
|
63742
|
-
|
|
63743
|
-
for (const instanceId of targetInstanceIds) await mutation.mutateAsync({
|
|
63769
|
+
await mutation.mutateAsync({
|
|
63744
63770
|
instanceId,
|
|
63745
63771
|
deviceId,
|
|
63746
63772
|
nodeId: "hub",
|
|
@@ -63774,7 +63800,7 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
63774
63800
|
unexposeMutation,
|
|
63775
63801
|
labelFor,
|
|
63776
63802
|
running,
|
|
63777
|
-
|
|
63803
|
+
instanceId
|
|
63778
63804
|
]);
|
|
63779
63805
|
/**
|
|
63780
63806
|
* Expose / unexpose ONE action or notification target. Same contract as the
|
|
@@ -63789,8 +63815,7 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
63789
63815
|
const succeeded = [];
|
|
63790
63816
|
const failed = [];
|
|
63791
63817
|
try {
|
|
63792
|
-
|
|
63793
|
-
for (const instanceId of targetInstanceIds) await mutation.mutateAsync({
|
|
63818
|
+
await mutation.mutateAsync({
|
|
63794
63819
|
instanceId,
|
|
63795
63820
|
entity: row.ref,
|
|
63796
63821
|
nodeId: "hub",
|
|
@@ -63821,7 +63846,7 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
63821
63846
|
exposeEntityMutation,
|
|
63822
63847
|
unexposeEntityMutation,
|
|
63823
63848
|
running,
|
|
63824
|
-
|
|
63849
|
+
instanceId
|
|
63825
63850
|
]);
|
|
63826
63851
|
const [bulkFilters, setBulkFilters] = (0, react$1.useState)(NO_EXPORT_FILTERS);
|
|
63827
63852
|
const roleOptions = (0, react$1.useMemo)(() => roleFilterOptions(candidateDevices, (id) => id === "(none)" ? t("uiLibrary.deviceExport.noRole", "No role") : deviceRoleMetaOf(id, t).label).map((o) => ({
|
|
@@ -64049,6 +64074,10 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
64049
64074
|
className: "text-[11px] font-semibold text-foreground-subtle uppercase tracking-wide",
|
|
64050
64075
|
children: t("uiLibrary.deviceExport.title", "Exported devices")
|
|
64051
64076
|
}),
|
|
64077
|
+
named && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
64078
|
+
className: "text-xs font-medium text-foreground truncate",
|
|
64079
|
+
children: instance.name
|
|
64080
|
+
}),
|
|
64052
64081
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
64053
64082
|
variant: statusVariant(linkState),
|
|
64054
64083
|
children: capitaliseLinkState(t, linkState)
|
|
@@ -64103,6 +64132,63 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
64103
64132
|
]
|
|
64104
64133
|
});
|
|
64105
64134
|
}
|
|
64135
|
+
/** A switched-off instance: named, marked disabled, and asked nothing. */
|
|
64136
|
+
function DisabledInstanceCard({ instance }) {
|
|
64137
|
+
const t = useUiText();
|
|
64138
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
64139
|
+
className: "rounded-lg border border-border bg-surface overflow-hidden",
|
|
64140
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
64141
|
+
className: "flex items-center gap-2 px-2 sm:px-4 py-2.5 bg-surface-hover/20",
|
|
64142
|
+
children: [
|
|
64143
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Share2, { className: "h-3.5 w-3.5 text-foreground-subtle shrink-0" }),
|
|
64144
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
64145
|
+
className: "text-xs font-medium text-foreground truncate",
|
|
64146
|
+
children: instance.name
|
|
64147
|
+
}),
|
|
64148
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
64149
|
+
variant: "neutral",
|
|
64150
|
+
children: t("uiLibrary.deviceExport.instanceDisabled", "Disabled")
|
|
64151
|
+
})
|
|
64152
|
+
]
|
|
64153
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
64154
|
+
className: "px-2 sm:px-4 py-3 text-xs text-foreground-subtle border-t border-border",
|
|
64155
|
+
children: t("uiLibrary.deviceExport.instanceDisabledHint", "This instance is switched off. Enable it to expose devices through it.")
|
|
64156
|
+
})]
|
|
64157
|
+
});
|
|
64158
|
+
}
|
|
64159
|
+
/**
|
|
64160
|
+
* The export surface of one exporter addon, per export INSTANCE. The instances
|
|
64161
|
+
* are asked of the provider (`listInstances`), never assumed: one instance
|
|
64162
|
+
* renders the table alone, several render one named section each (one per
|
|
64163
|
+
* Home Assistant broker), and every read and write names the instance it acts
|
|
64164
|
+
* on. A disabled instance reads as disabled; an unanswered list reads as
|
|
64165
|
+
* loading or unknown, never as "nothing exposed".
|
|
64166
|
+
*/
|
|
64167
|
+
function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
64168
|
+
const t = useUiText();
|
|
64169
|
+
const state = useExportInstances(addonId);
|
|
64170
|
+
if (state.kind === "pending") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
64171
|
+
className: "flex items-center gap-2 px-1 py-6 text-xs text-foreground-subtle",
|
|
64172
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), t("uiLibrary.deviceExport.instancesLoading", "Loading export instances…")]
|
|
64173
|
+
});
|
|
64174
|
+
if (state.kind === "unknown") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBox, { message: t("uiLibrary.deviceExport.instancesUnknown", "The export instances could not be read. Try again in a moment.") });
|
|
64175
|
+
if (state.instances.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {
|
|
64176
|
+
icon: Share2,
|
|
64177
|
+
title: t("uiLibrary.deviceExport.emptyTitle", "No exportable devices"),
|
|
64178
|
+
description: t("uiLibrary.deviceExport.instancesNone", "\"{{addon}}\" has no export instance.", { addon: addonId })
|
|
64179
|
+
});
|
|
64180
|
+
const named = state.instances.length > 1;
|
|
64181
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
64182
|
+
className: "flex flex-col gap-3",
|
|
64183
|
+
children: state.instances.map((instance) => instance.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceExportInstancePanel, {
|
|
64184
|
+
addonId,
|
|
64185
|
+
instance,
|
|
64186
|
+
named,
|
|
64187
|
+
onOpenDevice,
|
|
64188
|
+
showSetup
|
|
64189
|
+
}, instance.instanceId) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledInstanceCard, { instance }, instance.instanceId))
|
|
64190
|
+
});
|
|
64191
|
+
}
|
|
64106
64192
|
//#endregion
|
|
64107
64193
|
//#region src/composites/device-grid.tsx
|
|
64108
64194
|
function DeviceGrid({ children, minCardWidth = 220, gap = 3, className }) {
|
|
@@ -73716,7 +73802,7 @@ function TimelineSlider({ markers, bands = [], selectedId = null, step = 5, aria
|
|
|
73716
73802
|
}
|
|
73717
73803
|
//#endregion
|
|
73718
73804
|
//#region src/ui-library-version.ts
|
|
73719
|
-
var uiLibraryVersion = "1.2.
|
|
73805
|
+
var uiLibraryVersion = "1.2.381";
|
|
73720
73806
|
//#endregion
|
|
73721
73807
|
//#region src/hooks/use-account-unit-preferences.ts
|
|
73722
73808
|
/**
|
package/dist/index.js
CHANGED
|
@@ -63436,16 +63436,26 @@ function parseStatus(data) {
|
|
|
63436
63436
|
const parsed = DeviceExportStatusSchema.safeParse(data);
|
|
63437
63437
|
return parsed.success ? parsed.data : null;
|
|
63438
63438
|
}
|
|
63439
|
-
|
|
63440
|
-
|
|
63441
|
-
* them above its settings while the devices table sits at the bottom. Renders
|
|
63442
|
-
* nothing when the exporter reports neither. Shares `getStatus`'s query key
|
|
63443
|
-
* with the panel, so mounting both costs one request.
|
|
63444
|
-
*/
|
|
63445
|
-
function DeviceExportSetup({ addonId }) {
|
|
63446
|
-
const statusQuery = useDeviceExportGetStatus({
|
|
63439
|
+
function useExportInstances(addonId) {
|
|
63440
|
+
const query = useDeviceExportListInstances({
|
|
63447
63441
|
nodeId: "hub",
|
|
63448
63442
|
addonId
|
|
63443
|
+
}, { retry: false });
|
|
63444
|
+
return useMemo(() => {
|
|
63445
|
+
if (query.data === void 0) return query.isError ? { kind: "unknown" } : { kind: "pending" };
|
|
63446
|
+
const parsed = ExportInstanceArraySchema.safeParse(query.data);
|
|
63447
|
+
return parsed.success ? {
|
|
63448
|
+
kind: "answered",
|
|
63449
|
+
instances: parsed.data
|
|
63450
|
+
} : { kind: "unknown" };
|
|
63451
|
+
}, [query.data, query.isError]);
|
|
63452
|
+
}
|
|
63453
|
+
/** One instance's error and Setup block. Renders nothing when it reports neither. */
|
|
63454
|
+
function InstanceSetup({ addonId, instance, named }) {
|
|
63455
|
+
const statusQuery = useDeviceExportGetStatus({
|
|
63456
|
+
nodeId: "hub",
|
|
63457
|
+
addonId,
|
|
63458
|
+
instanceId: instance.instanceId
|
|
63449
63459
|
}, {
|
|
63450
63460
|
refetchInterval: STATUS_POLL_INTERVAL_MS,
|
|
63451
63461
|
retry: false
|
|
@@ -63454,12 +63464,37 @@ function DeviceExportSetup({ addonId }) {
|
|
|
63454
63464
|
if (!status?.error && !status?.setup) return null;
|
|
63455
63465
|
return /* @__PURE__ */ jsxs("div", {
|
|
63456
63466
|
className: "rounded-lg border border-border bg-surface overflow-hidden",
|
|
63457
|
-
children: [
|
|
63458
|
-
|
|
63459
|
-
|
|
63460
|
-
|
|
63467
|
+
children: [
|
|
63468
|
+
named && /* @__PURE__ */ jsx("div", {
|
|
63469
|
+
className: "px-2 sm:px-4 py-2 border-b border-border text-xs font-medium text-foreground",
|
|
63470
|
+
children: instance.name
|
|
63471
|
+
}),
|
|
63472
|
+
status.error && /* @__PURE__ */ jsx("div", {
|
|
63473
|
+
className: "px-2 sm:px-4 py-3 border-b border-border",
|
|
63474
|
+
children: /* @__PURE__ */ jsx(ErrorBox, { message: status.error })
|
|
63475
|
+
}),
|
|
63476
|
+
status.setup && /* @__PURE__ */ jsx(SetupSection, { setup: status.setup })
|
|
63477
|
+
]
|
|
63461
63478
|
});
|
|
63462
63479
|
}
|
|
63480
|
+
/**
|
|
63481
|
+
* The exporter's error and Setup block on their own — for a page that shows
|
|
63482
|
+
* them above its settings while the devices table sits at the bottom. One block
|
|
63483
|
+
* per ENABLED instance (named when there are several); renders nothing while
|
|
63484
|
+
* the instances are unanswered or when none reports an error or a setup. Shares
|
|
63485
|
+
* `getStatus`'s query key with the panel, so mounting both costs one request.
|
|
63486
|
+
*/
|
|
63487
|
+
function DeviceExportSetup({ addonId }) {
|
|
63488
|
+
const state = useExportInstances(addonId);
|
|
63489
|
+
if (state.kind !== "answered") return null;
|
|
63490
|
+
const enabled = state.instances.filter((i) => i.enabled);
|
|
63491
|
+
if (enabled.length === 0) return null;
|
|
63492
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: enabled.map((instance) => /* @__PURE__ */ jsx(InstanceSetup, {
|
|
63493
|
+
addonId,
|
|
63494
|
+
instance,
|
|
63495
|
+
named: state.instances.length > 1
|
|
63496
|
+
}, instance.instanceId)) });
|
|
63497
|
+
}
|
|
63463
63498
|
function ExportSelectCell({ device, checked, onToggle }) {
|
|
63464
63499
|
return /* @__PURE__ */ jsx("input", {
|
|
63465
63500
|
type: "checkbox",
|
|
@@ -63555,30 +63590,30 @@ function BulkSummary({ outcome, onDismiss }) {
|
|
|
63555
63590
|
]
|
|
63556
63591
|
});
|
|
63557
63592
|
}
|
|
63558
|
-
|
|
63559
|
-
|
|
63560
|
-
* cap drives it.
|
|
63561
|
-
*/
|
|
63562
|
-
function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
63593
|
+
function DeviceExportInstancePanel({ addonId, instance, named, onOpenDevice, showSetup = true }) {
|
|
63594
|
+
const instanceId = instance.instanceId;
|
|
63563
63595
|
const t = useUiText();
|
|
63564
63596
|
const queryClient = useQueryClient();
|
|
63565
63597
|
const statusQuery = useDeviceExportGetStatus({
|
|
63566
63598
|
nodeId: "hub",
|
|
63567
|
-
addonId
|
|
63599
|
+
addonId,
|
|
63600
|
+
instanceId
|
|
63568
63601
|
}, {
|
|
63569
63602
|
refetchInterval: STATUS_POLL_INTERVAL_MS,
|
|
63570
63603
|
retry: false
|
|
63571
63604
|
});
|
|
63572
63605
|
const exposedQuery = useDeviceExportListExposedDevices({
|
|
63573
63606
|
nodeId: "hub",
|
|
63574
|
-
addonId
|
|
63607
|
+
addonId,
|
|
63608
|
+
instanceId
|
|
63575
63609
|
}, {
|
|
63576
63610
|
refetchInterval: STATUS_POLL_INTERVAL_MS,
|
|
63577
63611
|
retry: false
|
|
63578
63612
|
});
|
|
63579
63613
|
const kindsQuery = useDeviceExportListSupportedDeviceKinds({
|
|
63580
63614
|
nodeId: "hub",
|
|
63581
|
-
addonId
|
|
63615
|
+
addonId,
|
|
63616
|
+
instanceId
|
|
63582
63617
|
}, { retry: false });
|
|
63583
63618
|
const devicesQuery = useDeviceManagerListAll({ projection: "slim" }, { refetchInterval: FLEET_POLL_INTERVAL_MS });
|
|
63584
63619
|
const integrationsQuery = useIntegrationsList(void 0);
|
|
@@ -63593,7 +63628,8 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
63593
63628
|
const entityKinds = useMemo(() => supportedEntityKinds(entityKindsQuery.data), [entityKindsQuery.data]);
|
|
63594
63629
|
const exposedEntitiesQuery = useDeviceExportListExposedEntities({
|
|
63595
63630
|
nodeId: "hub",
|
|
63596
|
-
addonId
|
|
63631
|
+
addonId,
|
|
63632
|
+
instanceId
|
|
63597
63633
|
}, {
|
|
63598
63634
|
refetchInterval: STATUS_POLL_INTERVAL_MS,
|
|
63599
63635
|
retry: false,
|
|
@@ -63610,15 +63646,6 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
63610
63646
|
const invalidate = useCallback(() => {
|
|
63611
63647
|
queryClient.invalidateQueries({ queryKey: [["deviceExport"]] });
|
|
63612
63648
|
}, [queryClient]);
|
|
63613
|
-
const instancesQuery = useDeviceExportListInstances({
|
|
63614
|
-
nodeId: "hub",
|
|
63615
|
-
addonId
|
|
63616
|
-
}, { retry: false });
|
|
63617
|
-
const targetInstanceIds = useMemo(() => {
|
|
63618
|
-
const parsed = ExportInstanceArraySchema.safeParse(instancesQuery.data);
|
|
63619
|
-
if (!parsed.success) return null;
|
|
63620
|
-
return parsed.data.filter((i) => i.enabled).map((i) => i.instanceId);
|
|
63621
|
-
}, [instancesQuery.data]);
|
|
63622
63649
|
const exposeMutation = useDeviceExportExposeDevice({ onSuccess: invalidate });
|
|
63623
63650
|
const unexposeMutation = useDeviceExportUnexposeDevice({ onSuccess: invalidate });
|
|
63624
63651
|
const exposeEntityMutation = useDeviceExportExposeEntity({ onSuccess: invalidate });
|
|
@@ -63715,8 +63742,7 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
63715
63742
|
for (const deviceId of deviceIds) {
|
|
63716
63743
|
const label = labelFor(deviceId);
|
|
63717
63744
|
try {
|
|
63718
|
-
|
|
63719
|
-
for (const instanceId of targetInstanceIds) await mutation.mutateAsync({
|
|
63745
|
+
await mutation.mutateAsync({
|
|
63720
63746
|
instanceId,
|
|
63721
63747
|
deviceId,
|
|
63722
63748
|
nodeId: "hub",
|
|
@@ -63750,7 +63776,7 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
63750
63776
|
unexposeMutation,
|
|
63751
63777
|
labelFor,
|
|
63752
63778
|
running,
|
|
63753
|
-
|
|
63779
|
+
instanceId
|
|
63754
63780
|
]);
|
|
63755
63781
|
/**
|
|
63756
63782
|
* Expose / unexpose ONE action or notification target. Same contract as the
|
|
@@ -63765,8 +63791,7 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
63765
63791
|
const succeeded = [];
|
|
63766
63792
|
const failed = [];
|
|
63767
63793
|
try {
|
|
63768
|
-
|
|
63769
|
-
for (const instanceId of targetInstanceIds) await mutation.mutateAsync({
|
|
63794
|
+
await mutation.mutateAsync({
|
|
63770
63795
|
instanceId,
|
|
63771
63796
|
entity: row.ref,
|
|
63772
63797
|
nodeId: "hub",
|
|
@@ -63797,7 +63822,7 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
63797
63822
|
exposeEntityMutation,
|
|
63798
63823
|
unexposeEntityMutation,
|
|
63799
63824
|
running,
|
|
63800
|
-
|
|
63825
|
+
instanceId
|
|
63801
63826
|
]);
|
|
63802
63827
|
const [bulkFilters, setBulkFilters] = useState(NO_EXPORT_FILTERS);
|
|
63803
63828
|
const roleOptions = useMemo(() => roleFilterOptions(candidateDevices, (id) => id === "(none)" ? t("uiLibrary.deviceExport.noRole", "No role") : deviceRoleMetaOf(id, t).label).map((o) => ({
|
|
@@ -64025,6 +64050,10 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
64025
64050
|
className: "text-[11px] font-semibold text-foreground-subtle uppercase tracking-wide",
|
|
64026
64051
|
children: t("uiLibrary.deviceExport.title", "Exported devices")
|
|
64027
64052
|
}),
|
|
64053
|
+
named && /* @__PURE__ */ jsx("span", {
|
|
64054
|
+
className: "text-xs font-medium text-foreground truncate",
|
|
64055
|
+
children: instance.name
|
|
64056
|
+
}),
|
|
64028
64057
|
/* @__PURE__ */ jsx(Badge, {
|
|
64029
64058
|
variant: statusVariant(linkState),
|
|
64030
64059
|
children: capitaliseLinkState(t, linkState)
|
|
@@ -64079,6 +64108,63 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
|
64079
64108
|
]
|
|
64080
64109
|
});
|
|
64081
64110
|
}
|
|
64111
|
+
/** A switched-off instance: named, marked disabled, and asked nothing. */
|
|
64112
|
+
function DisabledInstanceCard({ instance }) {
|
|
64113
|
+
const t = useUiText();
|
|
64114
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
64115
|
+
className: "rounded-lg border border-border bg-surface overflow-hidden",
|
|
64116
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
64117
|
+
className: "flex items-center gap-2 px-2 sm:px-4 py-2.5 bg-surface-hover/20",
|
|
64118
|
+
children: [
|
|
64119
|
+
/* @__PURE__ */ jsx(Share2, { className: "h-3.5 w-3.5 text-foreground-subtle shrink-0" }),
|
|
64120
|
+
/* @__PURE__ */ jsx("span", {
|
|
64121
|
+
className: "text-xs font-medium text-foreground truncate",
|
|
64122
|
+
children: instance.name
|
|
64123
|
+
}),
|
|
64124
|
+
/* @__PURE__ */ jsx(Badge, {
|
|
64125
|
+
variant: "neutral",
|
|
64126
|
+
children: t("uiLibrary.deviceExport.instanceDisabled", "Disabled")
|
|
64127
|
+
})
|
|
64128
|
+
]
|
|
64129
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
64130
|
+
className: "px-2 sm:px-4 py-3 text-xs text-foreground-subtle border-t border-border",
|
|
64131
|
+
children: t("uiLibrary.deviceExport.instanceDisabledHint", "This instance is switched off. Enable it to expose devices through it.")
|
|
64132
|
+
})]
|
|
64133
|
+
});
|
|
64134
|
+
}
|
|
64135
|
+
/**
|
|
64136
|
+
* The export surface of one exporter addon, per export INSTANCE. The instances
|
|
64137
|
+
* are asked of the provider (`listInstances`), never assumed: one instance
|
|
64138
|
+
* renders the table alone, several render one named section each (one per
|
|
64139
|
+
* Home Assistant broker), and every read and write names the instance it acts
|
|
64140
|
+
* on. A disabled instance reads as disabled; an unanswered list reads as
|
|
64141
|
+
* loading or unknown, never as "nothing exposed".
|
|
64142
|
+
*/
|
|
64143
|
+
function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
64144
|
+
const t = useUiText();
|
|
64145
|
+
const state = useExportInstances(addonId);
|
|
64146
|
+
if (state.kind === "pending") return /* @__PURE__ */ jsxs("div", {
|
|
64147
|
+
className: "flex items-center gap-2 px-1 py-6 text-xs text-foreground-subtle",
|
|
64148
|
+
children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), t("uiLibrary.deviceExport.instancesLoading", "Loading export instances…")]
|
|
64149
|
+
});
|
|
64150
|
+
if (state.kind === "unknown") return /* @__PURE__ */ jsx(ErrorBox, { message: t("uiLibrary.deviceExport.instancesUnknown", "The export instances could not be read. Try again in a moment.") });
|
|
64151
|
+
if (state.instances.length === 0) return /* @__PURE__ */ jsx(EmptyState, {
|
|
64152
|
+
icon: Share2,
|
|
64153
|
+
title: t("uiLibrary.deviceExport.emptyTitle", "No exportable devices"),
|
|
64154
|
+
description: t("uiLibrary.deviceExport.instancesNone", "\"{{addon}}\" has no export instance.", { addon: addonId })
|
|
64155
|
+
});
|
|
64156
|
+
const named = state.instances.length > 1;
|
|
64157
|
+
return /* @__PURE__ */ jsx("div", {
|
|
64158
|
+
className: "flex flex-col gap-3",
|
|
64159
|
+
children: state.instances.map((instance) => instance.enabled ? /* @__PURE__ */ jsx(DeviceExportInstancePanel, {
|
|
64160
|
+
addonId,
|
|
64161
|
+
instance,
|
|
64162
|
+
named,
|
|
64163
|
+
onOpenDevice,
|
|
64164
|
+
showSetup
|
|
64165
|
+
}, instance.instanceId) : /* @__PURE__ */ jsx(DisabledInstanceCard, { instance }, instance.instanceId))
|
|
64166
|
+
});
|
|
64167
|
+
}
|
|
64082
64168
|
//#endregion
|
|
64083
64169
|
//#region src/composites/device-grid.tsx
|
|
64084
64170
|
function DeviceGrid({ children, minCardWidth = 220, gap = 3, className }) {
|
|
@@ -73692,7 +73778,7 @@ function TimelineSlider({ markers, bands = [], selectedId = null, step = 5, aria
|
|
|
73692
73778
|
}
|
|
73693
73779
|
//#endregion
|
|
73694
73780
|
//#region src/ui-library-version.ts
|
|
73695
|
-
var uiLibraryVersion = "1.2.
|
|
73781
|
+
var uiLibraryVersion = "1.2.381";
|
|
73696
73782
|
//#endregion
|
|
73697
73783
|
//#region src/hooks/use-account-unit-preferences.ts
|
|
73698
73784
|
/**
|