@camstack/ui-library 1.2.248 → 1.2.250
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/config-icon-field.d.ts +11 -0
- package/dist/composites/config-secret-field.d.ts +22 -0
- package/dist/composites/device-export-panel.d.ts +19 -1
- package/dist/composites/device-list/generic-mode.d.ts +12 -0
- package/dist/composites/device-list/index.d.ts +2 -1
- package/dist/composites/device-list/page-size-select.d.ts +10 -0
- package/dist/composites/index.d.ts +5 -1
- package/dist/composites/training/send-frame-to-training.d.ts +6 -0
- package/dist/composites/training/training-collection-picker.d.ts +5 -0
- package/dist/composites/training/training-refusal-text.d.ts +10 -0
- package/dist/generated/system-hooks.d.ts +70 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/use-device-list-page-size.d.ts +2 -1
- package/dist/hooks/use-persisted-page-size.d.ts +18 -0
- package/dist/index.cjs +2499 -1768
- package/dist/index.js +2453 -1771
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -25,13 +25,13 @@ const require_theme_index = require("./theme/index.cjs");
|
|
|
25
25
|
let react = require("react");
|
|
26
26
|
let react$1 = __toESM(react, 1);
|
|
27
27
|
react = __toESM(react);
|
|
28
|
+
let _camstack_types = require("@camstack/types");
|
|
28
29
|
let _trpc_react_query = require("@trpc/react-query");
|
|
29
30
|
_trpc_react_query = __toESM(_trpc_react_query, 1);
|
|
30
31
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
31
32
|
react_jsx_runtime = __toESM(react_jsx_runtime, 1);
|
|
32
33
|
let react_dom = require("react-dom");
|
|
33
34
|
react_dom = __toESM(react_dom, 1);
|
|
34
|
-
let _camstack_types = require("@camstack/types");
|
|
35
35
|
let _tanstack_react_query = require("@tanstack/react-query");
|
|
36
36
|
_tanstack_react_query = __toESM(_tanstack_react_query, 1);
|
|
37
37
|
let react_dom_client = require("react-dom/client");
|
|
@@ -2454,6 +2454,26 @@ var Gauge = createLucideIcon("gauge", [["path", {
|
|
|
2454
2454
|
* This source code is licensed under the ISC license.
|
|
2455
2455
|
* See the LICENSE file in the root directory of this source tree.
|
|
2456
2456
|
*/
|
|
2457
|
+
var GraduationCap = createLucideIcon("graduation-cap", [
|
|
2458
|
+
["path", {
|
|
2459
|
+
d: "M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z",
|
|
2460
|
+
key: "j76jl0"
|
|
2461
|
+
}],
|
|
2462
|
+
["path", {
|
|
2463
|
+
d: "M22 10v6",
|
|
2464
|
+
key: "1lu8f3"
|
|
2465
|
+
}],
|
|
2466
|
+
["path", {
|
|
2467
|
+
d: "M6 12.5V16a6 3 0 0 0 12 0v-3.5",
|
|
2468
|
+
key: "1r8lef"
|
|
2469
|
+
}]
|
|
2470
|
+
]);
|
|
2471
|
+
/**
|
|
2472
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2473
|
+
*
|
|
2474
|
+
* This source code is licensed under the ISC license.
|
|
2475
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2476
|
+
*/
|
|
2457
2477
|
var Grid2x2 = createLucideIcon("grid-2x2", [
|
|
2458
2478
|
["path", {
|
|
2459
2479
|
d: "M12 3v18",
|
|
@@ -6466,138 +6486,1061 @@ function useClusterNodes() {
|
|
|
6466
6486
|
};
|
|
6467
6487
|
}
|
|
6468
6488
|
//#endregion
|
|
6469
|
-
//#region src/
|
|
6489
|
+
//#region src/composites/device-meta.ts
|
|
6490
|
+
var DEVICE_TYPE_META = {
|
|
6491
|
+
[_camstack_types.DeviceType.Camera]: {
|
|
6492
|
+
icon: Camera,
|
|
6493
|
+
label: "Camera"
|
|
6494
|
+
},
|
|
6495
|
+
[_camstack_types.DeviceType.Hub]: {
|
|
6496
|
+
icon: Network,
|
|
6497
|
+
label: "Hub"
|
|
6498
|
+
},
|
|
6499
|
+
[_camstack_types.DeviceType.Light]: {
|
|
6500
|
+
icon: Lightbulb,
|
|
6501
|
+
label: "Light"
|
|
6502
|
+
},
|
|
6503
|
+
[_camstack_types.DeviceType.Siren]: {
|
|
6504
|
+
icon: Siren,
|
|
6505
|
+
label: "Siren"
|
|
6506
|
+
},
|
|
6507
|
+
[_camstack_types.DeviceType.Switch]: {
|
|
6508
|
+
icon: ToggleLeft,
|
|
6509
|
+
label: "Switch"
|
|
6510
|
+
},
|
|
6511
|
+
[_camstack_types.DeviceType.Sensor]: {
|
|
6512
|
+
icon: Activity,
|
|
6513
|
+
label: "Sensor"
|
|
6514
|
+
},
|
|
6515
|
+
[_camstack_types.DeviceType.Thermostat]: {
|
|
6516
|
+
icon: Thermometer,
|
|
6517
|
+
label: "Thermostat"
|
|
6518
|
+
},
|
|
6519
|
+
[_camstack_types.DeviceType.Climate]: {
|
|
6520
|
+
icon: AirVent,
|
|
6521
|
+
label: "Air conditioner"
|
|
6522
|
+
},
|
|
6523
|
+
[_camstack_types.DeviceType.Button]: {
|
|
6524
|
+
icon: CircleDot,
|
|
6525
|
+
label: "Button"
|
|
6526
|
+
},
|
|
6527
|
+
[_camstack_types.DeviceType.EventEmitter]: {
|
|
6528
|
+
icon: Zap,
|
|
6529
|
+
label: "Event Emitter"
|
|
6530
|
+
},
|
|
6531
|
+
[_camstack_types.DeviceType.Update]: {
|
|
6532
|
+
icon: RefreshCw,
|
|
6533
|
+
label: "Update"
|
|
6534
|
+
},
|
|
6535
|
+
[_camstack_types.DeviceType.Generic]: {
|
|
6536
|
+
icon: Box,
|
|
6537
|
+
label: "Generic"
|
|
6538
|
+
},
|
|
6539
|
+
[_camstack_types.DeviceType.Notifier]: {
|
|
6540
|
+
icon: Bell,
|
|
6541
|
+
label: "Notifier"
|
|
6542
|
+
},
|
|
6543
|
+
[_camstack_types.DeviceType.Script]: {
|
|
6544
|
+
icon: ScrollText,
|
|
6545
|
+
label: "Script"
|
|
6546
|
+
},
|
|
6547
|
+
[_camstack_types.DeviceType.Automation]: {
|
|
6548
|
+
icon: Workflow,
|
|
6549
|
+
label: "Automation"
|
|
6550
|
+
},
|
|
6551
|
+
[_camstack_types.DeviceType.Lock]: {
|
|
6552
|
+
icon: Lock,
|
|
6553
|
+
label: "Lock"
|
|
6554
|
+
},
|
|
6555
|
+
[_camstack_types.DeviceType.Cover]: {
|
|
6556
|
+
icon: Blinds,
|
|
6557
|
+
label: "Cover"
|
|
6558
|
+
},
|
|
6559
|
+
[_camstack_types.DeviceType.Valve]: {
|
|
6560
|
+
icon: Pipette,
|
|
6561
|
+
label: "Valve"
|
|
6562
|
+
},
|
|
6563
|
+
[_camstack_types.DeviceType.Humidifier]: {
|
|
6564
|
+
icon: Waves,
|
|
6565
|
+
label: "Humidifier"
|
|
6566
|
+
},
|
|
6567
|
+
[_camstack_types.DeviceType.WaterHeater]: {
|
|
6568
|
+
icon: Bath,
|
|
6569
|
+
label: "Water heater"
|
|
6570
|
+
},
|
|
6571
|
+
[_camstack_types.DeviceType.Fan]: {
|
|
6572
|
+
icon: Fan,
|
|
6573
|
+
label: "Fan"
|
|
6574
|
+
},
|
|
6575
|
+
[_camstack_types.DeviceType.MediaPlayer]: {
|
|
6576
|
+
icon: MonitorPlay,
|
|
6577
|
+
label: "Media player"
|
|
6578
|
+
},
|
|
6579
|
+
[_camstack_types.DeviceType.AlarmPanel]: {
|
|
6580
|
+
icon: ShieldAlert,
|
|
6581
|
+
label: "Alarm panel"
|
|
6582
|
+
},
|
|
6583
|
+
[_camstack_types.DeviceType.Control]: {
|
|
6584
|
+
icon: SlidersHorizontal,
|
|
6585
|
+
label: "Control"
|
|
6586
|
+
},
|
|
6587
|
+
[_camstack_types.DeviceType.Presence]: {
|
|
6588
|
+
icon: MapPin,
|
|
6589
|
+
label: "Presence"
|
|
6590
|
+
},
|
|
6591
|
+
[_camstack_types.DeviceType.Weather]: {
|
|
6592
|
+
icon: CloudSun,
|
|
6593
|
+
label: "Weather"
|
|
6594
|
+
},
|
|
6595
|
+
[_camstack_types.DeviceType.Vacuum]: {
|
|
6596
|
+
icon: Bot,
|
|
6597
|
+
label: "Vacuum"
|
|
6598
|
+
},
|
|
6599
|
+
[_camstack_types.DeviceType.LawnMower]: {
|
|
6600
|
+
icon: Tractor,
|
|
6601
|
+
label: "Lawn mower"
|
|
6602
|
+
},
|
|
6603
|
+
[_camstack_types.DeviceType.Container]: {
|
|
6604
|
+
icon: Box,
|
|
6605
|
+
label: "Container"
|
|
6606
|
+
},
|
|
6607
|
+
[_camstack_types.DeviceType.Image]: {
|
|
6608
|
+
icon: Image,
|
|
6609
|
+
label: "Image"
|
|
6610
|
+
},
|
|
6611
|
+
[_camstack_types.DeviceType.PetFeeder]: {
|
|
6612
|
+
icon: Bone,
|
|
6613
|
+
label: "Pet feeder"
|
|
6614
|
+
}
|
|
6615
|
+
};
|
|
6616
|
+
var DEVICE_ROLE_META = {
|
|
6617
|
+
[_camstack_types.DeviceRole.Siren]: {
|
|
6618
|
+
icon: Siren,
|
|
6619
|
+
label: "Siren"
|
|
6620
|
+
},
|
|
6621
|
+
[_camstack_types.DeviceRole.Floodlight]: {
|
|
6622
|
+
icon: Lightbulb,
|
|
6623
|
+
label: "Floodlight"
|
|
6624
|
+
},
|
|
6625
|
+
[_camstack_types.DeviceRole.Spotlight]: {
|
|
6626
|
+
icon: Flashlight,
|
|
6627
|
+
label: "Spotlight"
|
|
6628
|
+
},
|
|
6629
|
+
[_camstack_types.DeviceRole.PirSensor]: {
|
|
6630
|
+
icon: Radar,
|
|
6631
|
+
label: "PIR sensor"
|
|
6632
|
+
},
|
|
6633
|
+
[_camstack_types.DeviceRole.Chime]: {
|
|
6634
|
+
icon: Bell,
|
|
6635
|
+
label: "Chime"
|
|
6636
|
+
},
|
|
6637
|
+
[_camstack_types.DeviceRole.Autotrack]: {
|
|
6638
|
+
icon: Crosshair,
|
|
6639
|
+
label: "Auto-track"
|
|
6640
|
+
},
|
|
6641
|
+
[_camstack_types.DeviceRole.Nightvision]: {
|
|
6642
|
+
icon: Moon,
|
|
6643
|
+
label: "Night vision"
|
|
6644
|
+
},
|
|
6645
|
+
[_camstack_types.DeviceRole.PrivacyMask]: {
|
|
6646
|
+
icon: EyeOff,
|
|
6647
|
+
label: "Privacy mask"
|
|
6648
|
+
},
|
|
6649
|
+
[_camstack_types.DeviceRole.Doorbell]: {
|
|
6650
|
+
icon: DoorClosed,
|
|
6651
|
+
label: "Doorbell"
|
|
6652
|
+
},
|
|
6653
|
+
[_camstack_types.DeviceRole.BinaryHelper]: {
|
|
6654
|
+
icon: ToggleLeft,
|
|
6655
|
+
label: "Binary helper"
|
|
6656
|
+
},
|
|
6657
|
+
[_camstack_types.DeviceRole.MotionSensor]: {
|
|
6658
|
+
icon: Footprints,
|
|
6659
|
+
label: "Motion sensor"
|
|
6660
|
+
},
|
|
6661
|
+
[_camstack_types.DeviceRole.ContactSensor]: {
|
|
6662
|
+
icon: DoorOpen,
|
|
6663
|
+
label: "Contact sensor"
|
|
6664
|
+
},
|
|
6665
|
+
[_camstack_types.DeviceRole.LeakSensor]: {
|
|
6666
|
+
icon: Droplet,
|
|
6667
|
+
label: "Leak sensor"
|
|
6668
|
+
},
|
|
6669
|
+
[_camstack_types.DeviceRole.SmokeSensor]: {
|
|
6670
|
+
icon: AlarmSmoke,
|
|
6671
|
+
label: "Smoke sensor"
|
|
6672
|
+
},
|
|
6673
|
+
[_camstack_types.DeviceRole.COSensor]: {
|
|
6674
|
+
icon: Wind,
|
|
6675
|
+
label: "CO sensor"
|
|
6676
|
+
},
|
|
6677
|
+
[_camstack_types.DeviceRole.GasSensor]: {
|
|
6678
|
+
icon: Flame,
|
|
6679
|
+
label: "Gas sensor"
|
|
6680
|
+
},
|
|
6681
|
+
[_camstack_types.DeviceRole.TamperSensor]: {
|
|
6682
|
+
icon: ShieldAlert,
|
|
6683
|
+
label: "Tamper sensor"
|
|
6684
|
+
},
|
|
6685
|
+
[_camstack_types.DeviceRole.VibrationSensor]: {
|
|
6686
|
+
icon: Vibrate,
|
|
6687
|
+
label: "Vibration sensor"
|
|
6688
|
+
},
|
|
6689
|
+
[_camstack_types.DeviceRole.ConnectivitySensor]: {
|
|
6690
|
+
icon: Wifi,
|
|
6691
|
+
label: "Connectivity"
|
|
6692
|
+
},
|
|
6693
|
+
[_camstack_types.DeviceRole.SoundSensor]: {
|
|
6694
|
+
icon: Volume2,
|
|
6695
|
+
label: "Sound sensor"
|
|
6696
|
+
},
|
|
6697
|
+
[_camstack_types.DeviceRole.BinarySensor]: {
|
|
6698
|
+
icon: ToggleLeft,
|
|
6699
|
+
label: "Binary sensor"
|
|
6700
|
+
},
|
|
6701
|
+
[_camstack_types.DeviceRole.TemperatureSensor]: {
|
|
6702
|
+
icon: Thermometer,
|
|
6703
|
+
label: "Temperature"
|
|
6704
|
+
},
|
|
6705
|
+
[_camstack_types.DeviceRole.HumiditySensor]: {
|
|
6706
|
+
icon: Droplets,
|
|
6707
|
+
label: "Humidity"
|
|
6708
|
+
},
|
|
6709
|
+
[_camstack_types.DeviceRole.AmbientLightSensor]: {
|
|
6710
|
+
icon: Sun,
|
|
6711
|
+
label: "Ambient light"
|
|
6712
|
+
},
|
|
6713
|
+
[_camstack_types.DeviceRole.PressureSensor]: {
|
|
6714
|
+
icon: Gauge,
|
|
6715
|
+
label: "Pressure"
|
|
6716
|
+
},
|
|
6717
|
+
[_camstack_types.DeviceRole.WindSensor]: {
|
|
6718
|
+
icon: Wind,
|
|
6719
|
+
label: "Wind"
|
|
6720
|
+
},
|
|
6721
|
+
[_camstack_types.DeviceRole.RainSensor]: {
|
|
6722
|
+
icon: CloudRain,
|
|
6723
|
+
label: "Rain"
|
|
6724
|
+
},
|
|
6725
|
+
[_camstack_types.DeviceRole.UvSensor]: {
|
|
6726
|
+
icon: SunMedium,
|
|
6727
|
+
label: "UV index"
|
|
6728
|
+
},
|
|
6729
|
+
[_camstack_types.DeviceRole.SolarRadiationSensor]: {
|
|
6730
|
+
icon: Sunrise,
|
|
6731
|
+
label: "Solar radiation"
|
|
6732
|
+
},
|
|
6733
|
+
[_camstack_types.DeviceRole.SoilMoistureSensor]: {
|
|
6734
|
+
icon: Sprout,
|
|
6735
|
+
label: "Soil moisture"
|
|
6736
|
+
},
|
|
6737
|
+
[_camstack_types.DeviceRole.PowerSensor]: {
|
|
6738
|
+
icon: Zap,
|
|
6739
|
+
label: "Power"
|
|
6740
|
+
},
|
|
6741
|
+
[_camstack_types.DeviceRole.EnergySensor]: {
|
|
6742
|
+
icon: Zap,
|
|
6743
|
+
label: "Energy"
|
|
6744
|
+
},
|
|
6745
|
+
[_camstack_types.DeviceRole.VoltageSensor]: {
|
|
6746
|
+
icon: Zap,
|
|
6747
|
+
label: "Voltage"
|
|
6748
|
+
},
|
|
6749
|
+
[_camstack_types.DeviceRole.CurrentSensor]: {
|
|
6750
|
+
icon: Zap,
|
|
6751
|
+
label: "Current"
|
|
6752
|
+
},
|
|
6753
|
+
[_camstack_types.DeviceRole.AirQualitySensor]: {
|
|
6754
|
+
icon: Wind,
|
|
6755
|
+
label: "Air quality"
|
|
6756
|
+
},
|
|
6757
|
+
[_camstack_types.DeviceRole.BatterySensor]: {
|
|
6758
|
+
icon: Gauge,
|
|
6759
|
+
label: "Battery"
|
|
6760
|
+
},
|
|
6761
|
+
[_camstack_types.DeviceRole.NumericSensor]: {
|
|
6762
|
+
icon: Hash,
|
|
6763
|
+
label: "Numeric sensor"
|
|
6764
|
+
},
|
|
6765
|
+
[_camstack_types.DeviceRole.EnumSensor]: {
|
|
6766
|
+
icon: List,
|
|
6767
|
+
label: "Enum sensor"
|
|
6768
|
+
},
|
|
6769
|
+
[_camstack_types.DeviceRole.DateTimeSensor]: {
|
|
6770
|
+
icon: Clock,
|
|
6771
|
+
label: "Date / time"
|
|
6772
|
+
},
|
|
6773
|
+
[_camstack_types.DeviceRole.GenericSensor]: {
|
|
6774
|
+
icon: Gauge,
|
|
6775
|
+
label: "Sensor"
|
|
6776
|
+
},
|
|
6777
|
+
[_camstack_types.DeviceRole.NumericControl]: {
|
|
6778
|
+
icon: SlidersHorizontal,
|
|
6779
|
+
label: "Numeric control"
|
|
6780
|
+
},
|
|
6781
|
+
[_camstack_types.DeviceRole.SelectControl]: {
|
|
6782
|
+
icon: ListChecks,
|
|
6783
|
+
label: "Select control"
|
|
6784
|
+
},
|
|
6785
|
+
[_camstack_types.DeviceRole.TextControl]: {
|
|
6786
|
+
icon: Type,
|
|
6787
|
+
label: "Text control"
|
|
6788
|
+
},
|
|
6789
|
+
[_camstack_types.DeviceRole.DateTimeControl]: {
|
|
6790
|
+
icon: Clock,
|
|
6791
|
+
label: "Date / time control"
|
|
6792
|
+
},
|
|
6793
|
+
[_camstack_types.DeviceRole.MobilePushNotifier]: {
|
|
6794
|
+
icon: Smartphone,
|
|
6795
|
+
label: "Mobile push"
|
|
6796
|
+
},
|
|
6797
|
+
[_camstack_types.DeviceRole.MessagingNotifier]: {
|
|
6798
|
+
icon: MessageSquare,
|
|
6799
|
+
label: "Messaging"
|
|
6800
|
+
},
|
|
6801
|
+
[_camstack_types.DeviceRole.EmailNotifier]: {
|
|
6802
|
+
icon: Mail,
|
|
6803
|
+
label: "Email"
|
|
6804
|
+
},
|
|
6805
|
+
[_camstack_types.DeviceRole.GenericNotifier]: {
|
|
6806
|
+
icon: Bell,
|
|
6807
|
+
label: "Notifier"
|
|
6808
|
+
}
|
|
6809
|
+
};
|
|
6810
|
+
var DEVICE_TYPE_VALUES$1 = new Set(Object.values(_camstack_types.DeviceType));
|
|
6811
|
+
var DEVICE_ROLE_VALUES = new Set(Object.values(_camstack_types.DeviceRole));
|
|
6812
|
+
function isDeviceType(value) {
|
|
6813
|
+
return DEVICE_TYPE_VALUES$1.has(value);
|
|
6814
|
+
}
|
|
6815
|
+
function isDeviceRole(value) {
|
|
6816
|
+
return DEVICE_ROLE_VALUES.has(value);
|
|
6817
|
+
}
|
|
6818
|
+
/** Meta for a known `DeviceType`. */
|
|
6819
|
+
function deviceTypeMeta(type) {
|
|
6820
|
+
return DEVICE_TYPE_META[type];
|
|
6821
|
+
}
|
|
6822
|
+
/** Meta for a known `DeviceRole`. */
|
|
6823
|
+
function deviceRoleMeta(role) {
|
|
6824
|
+
return DEVICE_ROLE_META[role];
|
|
6825
|
+
}
|
|
6826
|
+
var FALLBACK_DEVICE_TYPE_META = DEVICE_TYPE_META[_camstack_types.DeviceType.Generic];
|
|
6827
|
+
var FALLBACK_DEVICE_ROLE_META = DEVICE_ROLE_META[_camstack_types.DeviceRole.GenericSensor];
|
|
6470
6828
|
/**
|
|
6471
|
-
*
|
|
6829
|
+
* Runtime-string-safe lookup: device data crosses process / network boundaries
|
|
6830
|
+
* as plain strings, so an unrecognised value falls back to the `Generic` meta
|
|
6831
|
+
* instead of throwing.
|
|
6472
6832
|
*
|
|
6473
|
-
*
|
|
6474
|
-
*
|
|
6475
|
-
*
|
|
6476
|
-
|
|
6477
|
-
|
|
6833
|
+
* This also guards mixed-version module-federation loads where the host
|
|
6834
|
+
* `@camstack/types` enum has a new member before the loaded ui-library bundle
|
|
6835
|
+
* has the matching `DEVICE_TYPE_META` entry.
|
|
6836
|
+
*/
|
|
6837
|
+
function deviceTypeMetaOf(type) {
|
|
6838
|
+
if (!isDeviceType(type)) return FALLBACK_DEVICE_TYPE_META;
|
|
6839
|
+
return DEVICE_TYPE_META[type] ?? FALLBACK_DEVICE_TYPE_META;
|
|
6840
|
+
}
|
|
6841
|
+
/** Runtime-string-safe role lookup; unknown roles fall back to `GenericSensor`. */
|
|
6842
|
+
function deviceRoleMetaOf(role) {
|
|
6843
|
+
if (!isDeviceRole(role)) return FALLBACK_DEVICE_ROLE_META;
|
|
6844
|
+
return DEVICE_ROLE_META[role] ?? FALLBACK_DEVICE_ROLE_META;
|
|
6845
|
+
}
|
|
6846
|
+
/**
|
|
6847
|
+
* Icons an operator may pick for a per-device `display.icon` override (and for
|
|
6848
|
+
* a per-role default), keyed by a stable kebab-case name. Named imports only —
|
|
6849
|
+
* no dynamic indexing into lucide, so the bundle carries exactly this set.
|
|
6850
|
+
* Unknown keys resolve to `undefined` (caller falls through to the next level).
|
|
6478
6851
|
*
|
|
6479
|
-
*
|
|
6480
|
-
*
|
|
6481
|
-
*
|
|
6482
|
-
*
|
|
6483
|
-
*
|
|
6484
|
-
*
|
|
6852
|
+
* The vocabulary is the one the VIEWER draws from too (its own icon module,
|
|
6853
|
+
* keyed by these same strings): a key added here that the phone cannot draw
|
|
6854
|
+
* falls through to the next resolution level there, which reads as "my change
|
|
6855
|
+
* did nothing". So the KEY is the contract — a stable kebab-case string, never
|
|
6856
|
+
* a glyph identity — and `device-meta.test.ts` pins that every key resolves
|
|
6857
|
+
* and that the set never loses a key an operator may already have persisted.
|
|
6858
|
+
*
|
|
6859
|
+
* Grouped by what an operator is naming: a device, a light or appliance, a
|
|
6860
|
+
* sensor or the weather it reads, a room, or what moves through it. `gauge2`
|
|
6861
|
+
* is a legacy alias kept for rows that persisted it.
|
|
6485
6862
|
*/
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6863
|
+
var DISPLAY_ICON_REGISTRY = {
|
|
6864
|
+
camera: Camera,
|
|
6865
|
+
network: Network,
|
|
6866
|
+
"toggle-left": ToggleLeft,
|
|
6867
|
+
"toggle-right": ToggleRight,
|
|
6868
|
+
"circle-dot": CircleDot,
|
|
6869
|
+
"refresh-cw": RefreshCw,
|
|
6870
|
+
box: Box,
|
|
6871
|
+
"scroll-text": ScrollText,
|
|
6872
|
+
workflow: Workflow,
|
|
6873
|
+
lock: Lock,
|
|
6874
|
+
"lock-open": LockOpen,
|
|
6875
|
+
blinds: Blinds,
|
|
6876
|
+
"air-vent": AirVent,
|
|
6877
|
+
"monitor-play": MonitorPlay,
|
|
6878
|
+
"sliders-horizontal": SlidersHorizontal,
|
|
6879
|
+
"map-pin": MapPin,
|
|
6880
|
+
bot: Bot,
|
|
6881
|
+
tractor: Tractor,
|
|
6882
|
+
image: Image,
|
|
6883
|
+
bone: Bone,
|
|
6884
|
+
"door-closed": DoorClosed,
|
|
6885
|
+
"door-open": DoorOpen,
|
|
6886
|
+
crosshair: Crosshair,
|
|
6887
|
+
eye: Eye,
|
|
6888
|
+
"eye-off": EyeOff,
|
|
6889
|
+
smartphone: Smartphone,
|
|
6890
|
+
phone: Phone,
|
|
6891
|
+
tablet: Tablet,
|
|
6892
|
+
laptop: Laptop,
|
|
6893
|
+
monitor: Monitor,
|
|
6894
|
+
tv: Tv,
|
|
6895
|
+
speaker: Speaker,
|
|
6896
|
+
printer: Printer,
|
|
6897
|
+
projector: Projector,
|
|
6898
|
+
"gamepad-2": Gamepad2,
|
|
6899
|
+
radio: Radio,
|
|
6900
|
+
router: Router,
|
|
6901
|
+
bluetooth: Bluetooth,
|
|
6902
|
+
cpu: Cpu,
|
|
6903
|
+
"hard-drive": HardDrive,
|
|
6904
|
+
server: Server,
|
|
6905
|
+
mic: Mic,
|
|
6906
|
+
"mic-off": MicOff,
|
|
6907
|
+
video: Video,
|
|
6908
|
+
"video-off": VideoOff,
|
|
6909
|
+
music: Music,
|
|
6910
|
+
plug: Plug,
|
|
6911
|
+
"plug-zap": PlugZap,
|
|
6912
|
+
power: Power,
|
|
6913
|
+
"power-off": PowerOff,
|
|
6914
|
+
cable: Cable,
|
|
6915
|
+
battery: Battery,
|
|
6916
|
+
"battery-charging": BatteryCharging,
|
|
6917
|
+
"battery-full": BatteryFull,
|
|
6918
|
+
"battery-low": BatteryLow,
|
|
6919
|
+
key: Key,
|
|
6920
|
+
"key-round": KeyRound,
|
|
6921
|
+
mailbox: Mailbox,
|
|
6922
|
+
package: Package,
|
|
6923
|
+
shield: Shield,
|
|
6924
|
+
"shield-check": ShieldCheck,
|
|
6925
|
+
"shield-off": ShieldOff,
|
|
6926
|
+
lightbulb: Lightbulb,
|
|
6927
|
+
lamp: Lamp,
|
|
6928
|
+
"lamp-ceiling": LampCeiling,
|
|
6929
|
+
"lamp-desk": LampDesk,
|
|
6930
|
+
"lamp-floor": LampFloor,
|
|
6931
|
+
flashlight: Flashlight,
|
|
6932
|
+
heater: Heater,
|
|
6933
|
+
snowflake: Snowflake,
|
|
6934
|
+
"thermometer-sun": ThermometerSun,
|
|
6935
|
+
"thermometer-snowflake": ThermometerSnowflake,
|
|
6936
|
+
refrigerator: Refrigerator,
|
|
6937
|
+
microwave: Microwave,
|
|
6938
|
+
"washing-machine": WashingMachine,
|
|
6939
|
+
"cooking-pot": CookingPot,
|
|
6940
|
+
utensils: Utensils,
|
|
6941
|
+
thermometer: Thermometer,
|
|
6942
|
+
droplets: Droplets,
|
|
6943
|
+
droplet: Droplet,
|
|
6944
|
+
gauge: Gauge,
|
|
6945
|
+
gauge2: Gauge,
|
|
6946
|
+
sun: Sun,
|
|
6947
|
+
"sun-medium": SunMedium,
|
|
6948
|
+
sunrise: Sunrise,
|
|
6949
|
+
moon: Moon,
|
|
6950
|
+
wind: Wind,
|
|
6951
|
+
cloud: Cloud,
|
|
6952
|
+
"cloud-rain": CloudRain,
|
|
6953
|
+
"cloud-sun": CloudSun,
|
|
6954
|
+
waves: Waves,
|
|
6955
|
+
sprout: Sprout,
|
|
6956
|
+
leaf: Leaf,
|
|
6957
|
+
flower: Flower,
|
|
6958
|
+
zap: Zap,
|
|
6959
|
+
flame: Flame,
|
|
6960
|
+
bell: Bell,
|
|
6961
|
+
"bell-ring": BellRing,
|
|
6962
|
+
"bell-off": BellOff,
|
|
6963
|
+
siren: Siren,
|
|
6964
|
+
radar: Radar,
|
|
6965
|
+
activity: Activity,
|
|
6966
|
+
hash: Hash,
|
|
6967
|
+
clock: Clock,
|
|
6968
|
+
timer: Timer,
|
|
6969
|
+
calendar: Calendar,
|
|
6970
|
+
pipette: Pipette,
|
|
6971
|
+
fan: Fan,
|
|
6972
|
+
bath: Bath,
|
|
6973
|
+
footprints: Footprints,
|
|
6974
|
+
"alarm-smoke": AlarmSmoke,
|
|
6975
|
+
"shield-alert": ShieldAlert,
|
|
6976
|
+
vibrate: Vibrate,
|
|
6977
|
+
wifi: Wifi,
|
|
6978
|
+
"wifi-off": WifiOff,
|
|
6979
|
+
"volume-2": Volume2,
|
|
6980
|
+
"volume-x": VolumeX,
|
|
6981
|
+
heart: Heart,
|
|
6982
|
+
"heart-pulse": HeartPulse,
|
|
6983
|
+
star: Star,
|
|
6984
|
+
sparkles: Sparkles,
|
|
6985
|
+
house: House,
|
|
6986
|
+
sofa: Sofa,
|
|
6987
|
+
"bed-double": BedDouble,
|
|
6988
|
+
bed: Bed,
|
|
6989
|
+
warehouse: Warehouse,
|
|
6990
|
+
trees: Trees,
|
|
6991
|
+
"tree-pine": TreePine,
|
|
6992
|
+
fence: Fence,
|
|
6993
|
+
store: Store,
|
|
6994
|
+
building: Building,
|
|
6995
|
+
"building-2": Building2,
|
|
6996
|
+
mountain: Mountain,
|
|
6997
|
+
umbrella: Umbrella,
|
|
6998
|
+
dumbbell: Dumbbell,
|
|
6999
|
+
wrench: Wrench,
|
|
7000
|
+
hammer: Hammer,
|
|
7001
|
+
drill: Drill,
|
|
7002
|
+
"waves-ladder": WavesLadder,
|
|
7003
|
+
"circle-parking": CircleParking,
|
|
7004
|
+
briefcase: Briefcase,
|
|
7005
|
+
route: Route,
|
|
7006
|
+
car: Car,
|
|
7007
|
+
bike: Bike,
|
|
7008
|
+
bus: Bus,
|
|
7009
|
+
truck: Truck,
|
|
7010
|
+
plane: Plane,
|
|
7011
|
+
"train-front": TrainFront,
|
|
7012
|
+
sailboat: Sailboat,
|
|
7013
|
+
dog: Dog,
|
|
7014
|
+
cat: Cat,
|
|
7015
|
+
bird: Bird,
|
|
7016
|
+
"paw-print": PawPrint,
|
|
7017
|
+
baby: Baby,
|
|
7018
|
+
user: User,
|
|
7019
|
+
users: Users,
|
|
7020
|
+
"person-standing": PersonStanding
|
|
7021
|
+
};
|
|
7022
|
+
/** Resolve a `display.icon` name to a lucide component, or `undefined` when the
|
|
7023
|
+
* name is not in the curated `DISPLAY_ICON_REGISTRY`. */
|
|
7024
|
+
function resolveDisplayIcon(name) {
|
|
7025
|
+
return Object.hasOwn(DISPLAY_ICON_REGISTRY, name) ? DISPLAY_ICON_REGISTRY[name] : void 0;
|
|
7026
|
+
}
|
|
7027
|
+
/**
|
|
7028
|
+
* EXHAUSTIVE device-type filter options — one entry per `DeviceType` member,
|
|
7029
|
+
* derived from `DEVICE_TYPE_META` so the set is always complete and a newly
|
|
7030
|
+
* added `DeviceType` automatically appears in every type filter (Devices page,
|
|
7031
|
+
* adoption modal, …) without touching any consumer.
|
|
7032
|
+
*
|
|
7033
|
+
* The previous per-consumer logic built options from the types PRESENT in the
|
|
7034
|
+
* list (or a hand-maintained subset), so recently-added types (vacuum, lawn
|
|
7035
|
+
* mower, valve, humidifier, water heater, weather, image, …) were silently
|
|
7036
|
+
* missing from the dropdown. This is the single source of truth instead.
|
|
7037
|
+
*
|
|
7038
|
+
* `id` is the raw `DeviceType` string (matches the filtering predicate, which
|
|
7039
|
+
* compares against `device.type`); `label` + `icon` come from the meta map.
|
|
7040
|
+
* Ordered by `DeviceType` declaration order for a stable, intentional layout.
|
|
7041
|
+
*/
|
|
7042
|
+
var ALL_DEVICE_TYPE_FILTER_OPTIONS = Object.values(_camstack_types.DeviceType).map((type) => {
|
|
7043
|
+
const meta = deviceTypeMetaOf(type);
|
|
7044
|
+
return {
|
|
7045
|
+
id: type,
|
|
7046
|
+
label: meta.label,
|
|
7047
|
+
icon: meta.icon
|
|
7048
|
+
};
|
|
7049
|
+
});
|
|
7050
|
+
function allDeviceTypeFilterOptions() {
|
|
7051
|
+
return ALL_DEVICE_TYPE_FILTER_OPTIONS;
|
|
7052
|
+
}
|
|
7053
|
+
//#endregion
|
|
7054
|
+
//#region src/composites/icon-picker.tsx
|
|
7055
|
+
/**
|
|
7056
|
+
* icon-picker — the ONE control for choosing an icon, wherever admin-ui asks.
|
|
7057
|
+
*
|
|
7058
|
+
* There were two: a device one over `DISPLAY_ICON_REGISTRY` and a room one
|
|
7059
|
+
* over `LOCATION_ICON_IDS`, identical apart from the vocabulary they walked.
|
|
7060
|
+
* That was tolerable while the device set was 56 glyphs in a flat grid. It is
|
|
7061
|
+
* not at 157: a picker that only lets the operator HUNT is a picker they stop
|
|
7062
|
+
* using, and the two copies were already drifting — only one of them had an
|
|
7063
|
+
* i18n'd label.
|
|
7064
|
+
*
|
|
7065
|
+
* So the vocabulary, the grouping and the search all come from
|
|
7066
|
+
* `@camstack/types`' `display-icon-vocabulary`, which is the same module the
|
|
7067
|
+
* viewer's sheet searches. What is left here is the web half: a popover, a
|
|
7068
|
+
* grid, and a text field.
|
|
7069
|
+
*
|
|
7070
|
+
* ## What it does NOT own
|
|
7071
|
+
*
|
|
7072
|
+
* - **The glyphs.** `resolveGlyph` is a prop, because this package's table is
|
|
7073
|
+
* `lucide-react` and the viewer's is `lucide-react-native`. A key this build
|
|
7074
|
+
* cannot draw is SKIPPED rather than rendered as a hole — an empty tile
|
|
7075
|
+
* reads as a rendering bug, not as a missing choice.
|
|
7076
|
+
* - **The words.** Every string is a prop. This package has no i18n (one file
|
|
7077
|
+
* in it imports react-i18next, and that is the exception), and a control
|
|
7078
|
+
* that reached for a translation would force it on every consumer.
|
|
7079
|
+
* - **The write.** `onPick` is called and that is all; whether it is a form
|
|
7080
|
+
* value or an immediate mutation is the caller's business, as it already was
|
|
7081
|
+
* in both pickers this replaces.
|
|
7082
|
+
*
|
|
7083
|
+
* ## The clear tile is never filtered away
|
|
7084
|
+
*
|
|
7085
|
+
* The "inherit" / "no icon" choice sits above the sections and ignores the
|
|
7086
|
+
* search. A query that hid it would strand the operator on a glyph they picked
|
|
7087
|
+
* by accident with no way back to the default — the one action a picker must
|
|
7088
|
+
* always offer.
|
|
7089
|
+
*/
|
|
7090
|
+
function IconPicker({ value, onPick, resolveGlyph, fallbackGlyph, labels, only, extraWords, clearGlyph, labelFor, disabled = false }) {
|
|
7091
|
+
const [open, setOpen] = (0, react$1.useState)(false);
|
|
7092
|
+
const [query, setQuery] = (0, react$1.useState)("");
|
|
7093
|
+
const allowed = (0, react$1.useMemo)(() => only === void 0 ? null : new Set(only), [only]);
|
|
7094
|
+
const sections = (0, react$1.useMemo)(() => {
|
|
7095
|
+
const hits = (0, _camstack_types.searchDisplayIcons)(query, extraWords);
|
|
7096
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
7097
|
+
for (const hit of hits) {
|
|
7098
|
+
const { key, group } = hit.entry;
|
|
7099
|
+
if (allowed !== null && !allowed.has(key)) continue;
|
|
7100
|
+
if (resolveGlyph(key) === void 0) continue;
|
|
7101
|
+
const bucket = byGroup.get(group);
|
|
7102
|
+
if (bucket === void 0) byGroup.set(group, [key]);
|
|
7103
|
+
else bucket.push(key);
|
|
7104
|
+
}
|
|
7105
|
+
return _camstack_types.DISPLAY_ICON_GROUPS.flatMap((group) => {
|
|
7106
|
+
const keys = byGroup.get(group);
|
|
7107
|
+
return keys === void 0 || keys.length === 0 ? [] : [{
|
|
7108
|
+
group,
|
|
7109
|
+
keys
|
|
7110
|
+
}];
|
|
7111
|
+
});
|
|
7112
|
+
}, [
|
|
7113
|
+
query,
|
|
7114
|
+
extraWords,
|
|
7115
|
+
allowed,
|
|
7116
|
+
resolveGlyph
|
|
7117
|
+
]);
|
|
7118
|
+
const Current = (value === null ? void 0 : resolveGlyph(value)) ?? fallbackGlyph;
|
|
7119
|
+
const choose = async (next) => {
|
|
7120
|
+
setOpen(false);
|
|
7121
|
+
setQuery("");
|
|
7122
|
+
await onPick(next);
|
|
7123
|
+
};
|
|
7124
|
+
const Clear = clearGlyph ?? fallbackGlyph;
|
|
7125
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7126
|
+
className: "relative flex shrink-0 items-center gap-2",
|
|
7127
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7128
|
+
type: "button",
|
|
7129
|
+
disabled,
|
|
7130
|
+
onClick: () => setOpen((o) => !o),
|
|
7131
|
+
"aria-expanded": open,
|
|
7132
|
+
"aria-label": labels.trigger,
|
|
7133
|
+
title: labels.trigger,
|
|
7134
|
+
className: "rounded border border-border p-1 text-foreground-muted hover:bg-surface-hover disabled:opacity-50",
|
|
7135
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Current, { className: "h-4 w-4" })
|
|
7136
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7137
|
+
type: "button",
|
|
7138
|
+
"aria-label": labels.close,
|
|
7139
|
+
onClick: () => setOpen(false),
|
|
7140
|
+
className: "fixed inset-0 z-10 cursor-default"
|
|
7141
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7142
|
+
role: "group",
|
|
7143
|
+
"aria-label": labels.panel,
|
|
7144
|
+
className: "absolute left-0 top-full z-20 mt-1 max-h-80 w-72 overflow-y-auto rounded-md border border-border bg-surface p-2 shadow-lg",
|
|
7145
|
+
children: [
|
|
7146
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7147
|
+
type: "search",
|
|
7148
|
+
value: query,
|
|
7149
|
+
autoFocus: true,
|
|
7150
|
+
onChange: (e) => setQuery(e.target.value),
|
|
7151
|
+
placeholder: labels.searchPlaceholder,
|
|
7152
|
+
"aria-label": labels.searchPlaceholder,
|
|
7153
|
+
className: "mb-2 w-full rounded border border-border bg-surface-raised px-2 py-1 text-xs text-foreground placeholder:text-foreground-subtle"
|
|
7154
|
+
}),
|
|
7155
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7156
|
+
type: "button",
|
|
7157
|
+
onClick: () => void choose(null),
|
|
7158
|
+
"aria-pressed": value === null,
|
|
7159
|
+
className: "mb-2 flex w-full items-center gap-1.5 rounded px-1.5 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover",
|
|
7160
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Clear, { className: "h-3.5 w-3.5" }), labels.clear]
|
|
7161
|
+
}),
|
|
7162
|
+
sections.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7163
|
+
className: "px-1.5 py-2 text-[11px] text-foreground-subtle",
|
|
7164
|
+
children: labels.noMatches
|
|
7165
|
+
}) : sections.map(({ group, keys }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
7166
|
+
className: "mb-2 last:mb-0",
|
|
7167
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
7168
|
+
className: "px-1.5 pb-1 text-[10px] uppercase tracking-wide text-foreground-subtle",
|
|
7169
|
+
children: labels.groups[group]
|
|
7170
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7171
|
+
className: "grid grid-cols-7 gap-1",
|
|
7172
|
+
children: keys.map((key) => {
|
|
7173
|
+
const Glyph = resolveGlyph(key);
|
|
7174
|
+
if (Glyph === void 0) return null;
|
|
7175
|
+
const selected = value === key;
|
|
7176
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7177
|
+
type: "button",
|
|
7178
|
+
onClick: () => void choose(key),
|
|
7179
|
+
"aria-pressed": selected,
|
|
7180
|
+
"aria-label": labelFor?.(key) ?? key,
|
|
7181
|
+
title: labelFor?.(key) ?? key,
|
|
7182
|
+
className: selected ? "flex items-center justify-center rounded border border-primary bg-primary/10 p-1.5 text-foreground" : "flex items-center justify-center rounded border border-transparent p-1.5 text-foreground-muted hover:bg-surface-hover",
|
|
7183
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, { className: "h-4 w-4" })
|
|
7184
|
+
}, key);
|
|
7185
|
+
})
|
|
7186
|
+
})]
|
|
7187
|
+
}, group))
|
|
7188
|
+
]
|
|
7189
|
+
})] })]
|
|
7190
|
+
});
|
|
7191
|
+
}
|
|
7192
|
+
//#endregion
|
|
7193
|
+
//#region src/composites/config-icon-field.tsx
|
|
7194
|
+
/**
|
|
7195
|
+
* The control behind a `type: 'icon'` settings field (D780).
|
|
7196
|
+
*
|
|
7197
|
+
* The shared `<IconPicker>` over the display-icon vocabulary, with the device
|
|
7198
|
+
* glyph table — the same control and the same keys the device and room
|
|
7199
|
+
* pickers use. An icon field always draws something, so clearing is "back to
|
|
7200
|
+
* the field's default", and a stored value the vocabulary does not know (a
|
|
7201
|
+
* free-text lucide name from before D780) is drawn as that default.
|
|
7202
|
+
*/
|
|
7203
|
+
function labelsFor(label) {
|
|
7204
|
+
return {
|
|
7205
|
+
trigger: `Choose an icon for ${label}`,
|
|
7206
|
+
panel: `${label} icon`,
|
|
7207
|
+
close: "Close",
|
|
7208
|
+
searchPlaceholder: "Search icons",
|
|
7209
|
+
clear: "Default icon",
|
|
7210
|
+
noMatches: "No icon matches",
|
|
7211
|
+
groups: {
|
|
7212
|
+
device: "Devices",
|
|
7213
|
+
lighting: "Lighting",
|
|
7214
|
+
sensing: "Sensing",
|
|
7215
|
+
place: "Places",
|
|
7216
|
+
life: "Life"
|
|
7217
|
+
}
|
|
7218
|
+
};
|
|
7219
|
+
}
|
|
7220
|
+
function ConfigIconFieldControl({ label, value, defaultKey, disabled, onChange }) {
|
|
7221
|
+
const key = (0, _camstack_types.resolveDisplayIconKey)(value, defaultKey);
|
|
7222
|
+
const DefaultGlyph = resolveDisplayIcon(defaultKey) ?? ShieldCheck;
|
|
7223
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7224
|
+
className: "flex items-center gap-2",
|
|
7225
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconPicker, {
|
|
7226
|
+
value: key === "" ? null : key,
|
|
7227
|
+
onPick: (next) => onChange(next ?? defaultKey),
|
|
7228
|
+
resolveGlyph: resolveDisplayIcon,
|
|
7229
|
+
fallbackGlyph: DefaultGlyph,
|
|
7230
|
+
labels: labelsFor(label),
|
|
7231
|
+
disabled
|
|
7232
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7233
|
+
className: "text-[11px] text-foreground-subtle font-mono",
|
|
7234
|
+
children: key
|
|
7235
|
+
})]
|
|
7236
|
+
});
|
|
7237
|
+
}
|
|
7238
|
+
//#endregion
|
|
7239
|
+
//#region src/composites/config-secret-field.tsx
|
|
7240
|
+
/**
|
|
7241
|
+
* A keychain-stored secret in a settings form (D787, D789).
|
|
7242
|
+
*
|
|
7243
|
+
* The form never holds the value. It receives the field's VIEW —
|
|
7244
|
+
* `{ $secret, v, set: true }` when something is stored, `null` when nothing
|
|
7245
|
+
* is — and sends back exactly one of three things:
|
|
7246
|
+
*
|
|
7247
|
+
* - the view, unchanged ........ KEEP (also what an untouched form saves);
|
|
7248
|
+
* - a non-empty typed string ... CHANGE (the owner stores it in the keychain);
|
|
7249
|
+
* - `{ $secretClear: true }` ... CLEAR (the only thing that removes it).
|
|
7250
|
+
*
|
|
7251
|
+
* `''` is never sent for "remove": the save path reads it as KEEP, because an
|
|
7252
|
+
* empty string is what a defaults fallback writes back.
|
|
7253
|
+
*
|
|
7254
|
+
* Reveal shows the stored value for a few seconds, through the host's audited
|
|
7255
|
+
* `keychain-admin.reveal` (provided with {@link SecretRevealProvider}); a host
|
|
7256
|
+
* that provides none has no Reveal button.
|
|
7257
|
+
*/
|
|
7258
|
+
/** How long a revealed value stays on screen. */
|
|
7259
|
+
var SECRET_REVEAL_MS = 1e4;
|
|
7260
|
+
var SecretRevealContext = createSharedContext("camstack:secret-reveal", null);
|
|
7261
|
+
function SecretRevealProvider({ reveal, children }) {
|
|
7262
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretRevealContext.Provider, {
|
|
7263
|
+
value: reveal,
|
|
7264
|
+
children
|
|
7265
|
+
});
|
|
7266
|
+
}
|
|
7267
|
+
/** Does this value belong to the keychain rendering (a view, or "not set")? */
|
|
7268
|
+
function isKeychainFieldValue(value) {
|
|
7269
|
+
return value === null || (0, _camstack_types.isSecretFieldView)(value) || (0, _camstack_types.isSecretClear)(value);
|
|
7270
|
+
}
|
|
7271
|
+
var BUTTON$1 = "inline-flex items-center gap-1 rounded border border-border bg-surface px-2 py-0.5 text-[11px] font-medium text-foreground hover:border-primary/40 hover:bg-primary/5 disabled:opacity-40 disabled:cursor-not-allowed";
|
|
7272
|
+
var INPUT = "w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none disabled:opacity-50";
|
|
7273
|
+
function SecretFieldControl({ field, value, onChange, disabled, multiline }) {
|
|
7274
|
+
const reveal = (0, react$1.useContext)(SecretRevealContext);
|
|
7275
|
+
const [original] = (0, react$1.useState)(() => (0, _camstack_types.isSecretFieldView)(value) ? value : null);
|
|
7276
|
+
const [mode, setMode] = (0, react$1.useState)(() => typeof value === "string" && value.length > 0 ? {
|
|
7277
|
+
kind: "editing",
|
|
7278
|
+
text: value
|
|
7279
|
+
} : (0, _camstack_types.isSecretClear)(value) ? { kind: "clearing" } : { kind: "idle" });
|
|
7280
|
+
const [shown, setShown] = (0, react$1.useState)(null);
|
|
7281
|
+
const [revealError, setRevealError] = (0, react$1.useState)(null);
|
|
7282
|
+
const [showTyped, setShowTyped] = (0, react$1.useState)(false);
|
|
7283
|
+
(0, react$1.useEffect)(() => {
|
|
7284
|
+
if (shown === null) return;
|
|
7285
|
+
const timer = setTimeout(() => setShown(null), SECRET_REVEAL_MS);
|
|
7286
|
+
return () => clearTimeout(timer);
|
|
7287
|
+
}, [shown]);
|
|
7288
|
+
const isSet = original !== null;
|
|
7289
|
+
const ref = original?.$secret ?? null;
|
|
7290
|
+
const keep = () => {
|
|
7291
|
+
setMode({ kind: "idle" });
|
|
7292
|
+
onChange(original);
|
|
7293
|
+
};
|
|
7294
|
+
const label = "label" in field ? field.label : "";
|
|
7295
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7296
|
+
className: "space-y-1",
|
|
7297
|
+
"data-secret-field": "key" in field ? field.key : void 0,
|
|
7298
|
+
children: [
|
|
7299
|
+
mode.kind === "editing" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7300
|
+
className: "flex items-start gap-1.5",
|
|
7301
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7302
|
+
className: "relative flex-1",
|
|
7303
|
+
children: [multiline ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
7304
|
+
className: `${INPUT} min-h-[60px] font-mono`,
|
|
7305
|
+
"aria-label": label,
|
|
7306
|
+
value: mode.text,
|
|
7307
|
+
autoComplete: "off",
|
|
7308
|
+
spellCheck: false,
|
|
7309
|
+
disabled,
|
|
7310
|
+
onChange: (e) => {
|
|
7311
|
+
setMode({
|
|
7312
|
+
kind: "editing",
|
|
7313
|
+
text: e.target.value
|
|
7314
|
+
});
|
|
7315
|
+
onChange(e.target.value.length > 0 ? e.target.value : original);
|
|
7316
|
+
}
|
|
7317
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7318
|
+
type: showTyped ? "text" : "password",
|
|
7319
|
+
className: `${INPUT} pr-8`,
|
|
7320
|
+
"aria-label": label,
|
|
7321
|
+
value: mode.text,
|
|
7322
|
+
autoComplete: "new-password",
|
|
7323
|
+
spellCheck: false,
|
|
7324
|
+
disabled,
|
|
7325
|
+
autoFocus: true,
|
|
7326
|
+
onChange: (e) => {
|
|
7327
|
+
setMode({
|
|
7328
|
+
kind: "editing",
|
|
7329
|
+
text: e.target.value
|
|
7330
|
+
});
|
|
7331
|
+
onChange(e.target.value.length > 0 ? e.target.value : original);
|
|
7332
|
+
}
|
|
7333
|
+
}), !multiline && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7334
|
+
type: "button",
|
|
7335
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 text-foreground-subtle hover:text-foreground",
|
|
7336
|
+
tabIndex: -1,
|
|
7337
|
+
"aria-label": showTyped ? "Hide" : "Show",
|
|
7338
|
+
onClick: () => setShowTyped((s) => !s),
|
|
7339
|
+
children: showTyped ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EyeOff, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Eye, { className: "h-3.5 w-3.5" })
|
|
7340
|
+
})]
|
|
7341
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7342
|
+
type: "button",
|
|
7343
|
+
className: BUTTON$1,
|
|
7344
|
+
onClick: keep,
|
|
7345
|
+
disabled,
|
|
7346
|
+
children: "Cancel"
|
|
7347
|
+
})]
|
|
7348
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7349
|
+
className: "flex flex-wrap items-center gap-1.5",
|
|
7350
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7351
|
+
className: `inline-flex items-center gap-1 rounded-md border px-2 py-1 text-xs ${mode.kind === "clearing" ? "border-danger/40 text-danger" : isSet ? "border-border text-foreground" : "border-dashed border-border text-foreground-subtle"}`,
|
|
7352
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyRound, { className: "h-3 w-3" }), mode.kind === "clearing" ? "will be removed on save" : shown !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7353
|
+
className: "font-mono break-all",
|
|
7354
|
+
children: shown
|
|
7355
|
+
}) : isSet ? "●●●●●● set" : "not set"]
|
|
7356
|
+
}), mode.kind === "clearing" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7357
|
+
type: "button",
|
|
7358
|
+
className: BUTTON$1,
|
|
7359
|
+
onClick: keep,
|
|
7360
|
+
disabled,
|
|
7361
|
+
children: "Undo"
|
|
7362
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
7363
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7364
|
+
type: "button",
|
|
7365
|
+
className: BUTTON$1,
|
|
7366
|
+
disabled,
|
|
7367
|
+
onClick: () => setMode({
|
|
7368
|
+
kind: "editing",
|
|
7369
|
+
text: ""
|
|
7370
|
+
}),
|
|
7371
|
+
children: isSet ? "Change" : "Set"
|
|
7372
|
+
}),
|
|
7373
|
+
isSet && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7374
|
+
type: "button",
|
|
7375
|
+
className: BUTTON$1,
|
|
7376
|
+
disabled,
|
|
7377
|
+
onClick: () => {
|
|
7378
|
+
setShown(null);
|
|
7379
|
+
setMode({ kind: "clearing" });
|
|
7380
|
+
onChange(_camstack_types.SECRET_CLEAR);
|
|
7381
|
+
},
|
|
7382
|
+
children: "Clear"
|
|
7383
|
+
}),
|
|
7384
|
+
isSet && ref !== null && reveal !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7385
|
+
type: "button",
|
|
7386
|
+
className: BUTTON$1,
|
|
7387
|
+
disabled,
|
|
7388
|
+
onClick: () => {
|
|
7389
|
+
if (shown !== null) {
|
|
7390
|
+
setShown(null);
|
|
7391
|
+
return;
|
|
7392
|
+
}
|
|
7393
|
+
setRevealError(null);
|
|
7394
|
+
reveal(ref).then(setShown, (err) => setRevealError(err instanceof Error ? err.message : String(err)));
|
|
7395
|
+
},
|
|
7396
|
+
children: shown !== null ? "Hide" : "Reveal"
|
|
7397
|
+
})
|
|
7398
|
+
] })]
|
|
7399
|
+
}),
|
|
7400
|
+
original?.inline === true && mode.kind === "idle" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7401
|
+
className: "text-[10px] text-foreground-subtle",
|
|
7402
|
+
children: "Stored before the keychain; it moves there the next time it is changed."
|
|
7403
|
+
}),
|
|
7404
|
+
revealError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7405
|
+
className: "text-[10px] text-danger",
|
|
7406
|
+
children: revealError
|
|
7407
|
+
})
|
|
7408
|
+
]
|
|
7409
|
+
});
|
|
7410
|
+
}
|
|
7411
|
+
//#endregion
|
|
7412
|
+
//#region src/generated/system-hooks.ts
|
|
7413
|
+
/**
|
|
7414
|
+
* Per-method React-Query hooks for every `scope: 'system'` capability.
|
|
7415
|
+
*
|
|
7416
|
+
* Each hook is a thin pass-through to `trpc.<cap>.<method>.useQuery` /
|
|
7417
|
+
* `.useMutation` / `.useSubscription` so the typed inference + RQ
|
|
7418
|
+
* cache/dedup keep working — the generator only contributes a consistent
|
|
7419
|
+
* name and stable export surface so admin-ui pages can write
|
|
7420
|
+
* `useAddonsList()` instead of `trpc.addons.list.useQuery()`.
|
|
7421
|
+
*
|
|
7422
|
+
* Push-subscription methods (`kind: 'subscription'`) emit a typeof
|
|
7423
|
+
* alias over `useSubscription`, so the call shape is
|
|
7424
|
+
* `useToastOnToast(input, { onData: cb })` — same as the underlying
|
|
7425
|
+
* tRPC hook. The eventBus-backed `useLiveEvent` (see
|
|
7426
|
+
* `../hooks/use-live-event.ts`) covers events that don't ride a tRPC
|
|
7427
|
+
* subscription; the two surfaces coexist by design.
|
|
7428
|
+
*/
|
|
7429
|
+
/** Generated alias around `trpc.accessories.setChildHidden.useMutation`. */
|
|
7430
|
+
var useAccessoriesSetChildHidden = trpc.accessories.setChildHidden.useMutation;
|
|
7431
|
+
/** Generated alias around `trpc.accessories.getStatus.useQuery`. */
|
|
7432
|
+
var useAccessoriesGetStatus = trpc.accessories.getStatus.useQuery;
|
|
7433
|
+
/** Generated alias around `trpc.addonPages.listPages.useQuery`. */
|
|
7434
|
+
var useAddonPagesListPages = trpc.addonPages.listPages.useQuery;
|
|
7435
|
+
/** Generated alias around `trpc.addons.list.useQuery`. */
|
|
7436
|
+
var useAddonsList = trpc.addons.list.useQuery;
|
|
7437
|
+
/** Generated alias around `trpc.addons.getLogs.useQuery`. */
|
|
7438
|
+
var useAddonsGetLogs = trpc.addons.getLogs.useQuery;
|
|
7439
|
+
/** Generated alias around `trpc.addons.listPackages.useQuery`. */
|
|
7440
|
+
var useAddonsListPackages = trpc.addons.listPackages.useQuery;
|
|
7441
|
+
/** Generated alias around `trpc.addons.installPackage.useMutation`. */
|
|
7442
|
+
var useAddonsInstallPackage = trpc.addons.installPackage.useMutation;
|
|
7443
|
+
/** Generated alias around `trpc.addons.installFromWorkspace.useMutation`. */
|
|
7444
|
+
var useAddonsInstallFromWorkspace = trpc.addons.installFromWorkspace.useMutation;
|
|
7445
|
+
/** Generated alias around `trpc.addons.isWorkspaceAvailable.useQuery`. */
|
|
7446
|
+
var useAddonsIsWorkspaceAvailable = trpc.addons.isWorkspaceAvailable.useQuery;
|
|
7447
|
+
/** Generated alias around `trpc.addons.listWorkspacePackages.useQuery`. */
|
|
7448
|
+
var useAddonsListWorkspacePackages = trpc.addons.listWorkspacePackages.useQuery;
|
|
7449
|
+
/** Generated alias around `trpc.addons.uninstallPackage.useMutation`. */
|
|
7450
|
+
var useAddonsUninstallPackage = trpc.addons.uninstallPackage.useMutation;
|
|
7451
|
+
/** Generated alias around `trpc.addons.listInstallations.useQuery`. */
|
|
7452
|
+
var useAddonsListInstallations = trpc.addons.listInstallations.useQuery;
|
|
7453
|
+
/** Generated alias around `trpc.addons.reloadPackages.useMutation`. */
|
|
7454
|
+
var useAddonsReloadPackages = trpc.addons.reloadPackages.useMutation;
|
|
7455
|
+
/** Generated alias around `trpc.addons.searchAvailable.useQuery`. */
|
|
7456
|
+
var useAddonsSearchAvailable = trpc.addons.searchAvailable.useQuery;
|
|
7457
|
+
/** Generated alias around `trpc.addons.listUpdates.useQuery`. */
|
|
7458
|
+
var useAddonsListUpdates = trpc.addons.listUpdates.useQuery;
|
|
7459
|
+
/** Generated alias around `trpc.addons.updatePackage.useMutation`. */
|
|
7460
|
+
var useAddonsUpdatePackage = trpc.addons.updatePackage.useMutation;
|
|
7461
|
+
/** Generated alias around `trpc.addons.rollbackPackage.useMutation`. */
|
|
7462
|
+
var useAddonsRollbackPackage = trpc.addons.rollbackPackage.useMutation;
|
|
7463
|
+
/** Generated alias around `trpc.addons.forceRefresh.useMutation`. */
|
|
7464
|
+
var useAddonsForceRefresh = trpc.addons.forceRefresh.useMutation;
|
|
7465
|
+
/** Generated alias around `trpc.addons.restartServer.useMutation`. */
|
|
7466
|
+
var useAddonsRestartServer = trpc.addons.restartServer.useMutation;
|
|
7467
|
+
/** Generated alias around `trpc.addons.getLastRestart.useQuery`. */
|
|
7468
|
+
var useAddonsGetLastRestart = trpc.addons.getLastRestart.useQuery;
|
|
7469
|
+
/** Generated alias around `trpc.addons.listFrameworkPackages.useQuery`. */
|
|
7470
|
+
var useAddonsListFrameworkPackages = trpc.addons.listFrameworkPackages.useQuery;
|
|
7471
|
+
/** Generated alias around `trpc.addons.listCapabilityProviders.useQuery`. */
|
|
7472
|
+
var useAddonsListCapabilityProviders = trpc.addons.listCapabilityProviders.useQuery;
|
|
7473
|
+
/** Generated alias around `trpc.addons.setCapabilityProviderEnabled.useMutation`. */
|
|
7474
|
+
var useAddonsSetCapabilityProviderEnabled = trpc.addons.setCapabilityProviderEnabled.useMutation;
|
|
7475
|
+
/** Generated alias around `trpc.addons.getVersions.useQuery`. */
|
|
7476
|
+
var useAddonsGetVersions = trpc.addons.getVersions.useQuery;
|
|
7477
|
+
/** Generated alias around `trpc.addons.restartAddon.useMutation`. */
|
|
7478
|
+
var useAddonsRestartAddon = trpc.addons.restartAddon.useMutation;
|
|
7479
|
+
/** Generated alias around `trpc.addons.retryLoad.useMutation`. */
|
|
7480
|
+
var useAddonsRetryLoad = trpc.addons.retryLoad.useMutation;
|
|
7481
|
+
/** Generated alias around `trpc.addons.getAutoUpdateSettings.useQuery`. */
|
|
7482
|
+
var useAddonsGetAutoUpdateSettings = trpc.addons.getAutoUpdateSettings.useQuery;
|
|
7483
|
+
/** Generated alias around `trpc.addons.setAutoUpdateSettings.useMutation`. */
|
|
7484
|
+
var useAddonsSetAutoUpdateSettings = trpc.addons.setAutoUpdateSettings.useMutation;
|
|
7485
|
+
/** Generated alias around `trpc.addons.getAddonAutoUpdate.useQuery`. */
|
|
7486
|
+
var useAddonsGetAddonAutoUpdate = trpc.addons.getAddonAutoUpdate.useQuery;
|
|
7487
|
+
/** Generated alias around `trpc.addons.setAddonAutoUpdate.useMutation`. */
|
|
7488
|
+
var useAddonsSetAddonAutoUpdate = trpc.addons.setAddonAutoUpdate.useMutation;
|
|
7489
|
+
/** Generated alias around `trpc.addons.applyAutoUpdateToAll.useMutation`. */
|
|
7490
|
+
var useAddonsApplyAutoUpdateToAll = trpc.addons.applyAutoUpdateToAll.useMutation;
|
|
7491
|
+
/** Generated alias around `trpc.addons.custom.useMutation`. */
|
|
7492
|
+
var useAddonsCustom = trpc.addons.custom.useMutation;
|
|
7493
|
+
/** Generated alias around `trpc.addons.startJob.useMutation`. */
|
|
7494
|
+
var useAddonsStartJob = trpc.addons.startJob.useMutation;
|
|
7495
|
+
/** Generated alias around `trpc.addons.getJob.useQuery`. */
|
|
7496
|
+
var useAddonsGetJob = trpc.addons.getJob.useQuery;
|
|
7497
|
+
/** Generated alias around `trpc.addons.listJobs.useQuery`. */
|
|
7498
|
+
var useAddonsListJobs = trpc.addons.listJobs.useQuery;
|
|
7499
|
+
/** Generated alias around `trpc.addons.cancelJob.useMutation`. */
|
|
7500
|
+
var useAddonsCancelJob = trpc.addons.cancelJob.useMutation;
|
|
7501
|
+
/** Generated alias around `trpc.addons.onAddonLogs.useSubscription`. */
|
|
7502
|
+
var useAddonsOnAddonLogs = trpc.addons.onAddonLogs.useSubscription;
|
|
7503
|
+
/** Generated alias around `trpc.addonSettings.getGlobalSettings.useQuery`. */
|
|
7504
|
+
var useAddonSettingsGetGlobalSettings = trpc.addonSettings.getGlobalSettings.useQuery;
|
|
7505
|
+
/** Generated alias around `trpc.addonSettings.updateGlobalSettings.useMutation`. */
|
|
7506
|
+
var useAddonSettingsUpdateGlobalSettings = trpc.addonSettings.updateGlobalSettings.useMutation;
|
|
7507
|
+
/** Generated alias around `trpc.addonSettings.getIntegrationSettings.useQuery`. */
|
|
7508
|
+
var useAddonSettingsGetIntegrationSettings = trpc.addonSettings.getIntegrationSettings.useQuery;
|
|
7509
|
+
/** Generated alias around `trpc.addonSettings.getDeviceSettings.useQuery`. */
|
|
7510
|
+
var useAddonSettingsGetDeviceSettings = trpc.addonSettings.getDeviceSettings.useQuery;
|
|
7511
|
+
/** Generated alias around `trpc.addonSettings.updateDeviceSettings.useMutation`. */
|
|
7512
|
+
var useAddonSettingsUpdateDeviceSettings = trpc.addonSettings.updateDeviceSettings.useMutation;
|
|
7513
|
+
/** Generated alias around `trpc.addonWidgets.listWidgets.useQuery`. */
|
|
7514
|
+
var useAddonWidgetsListWidgets = trpc.addonWidgets.listWidgets.useQuery;
|
|
7515
|
+
/** Generated alias around `trpc.airQualitySensor.getStatus.useQuery`. */
|
|
7516
|
+
var useAirQualitySensorGetStatus = trpc.airQualitySensor.getStatus.useQuery;
|
|
7517
|
+
/** Generated alias around `trpc.alarmPanel.arm.useMutation`. */
|
|
7518
|
+
var useAlarmPanelArm = trpc.alarmPanel.arm.useMutation;
|
|
7519
|
+
/** Generated alias around `trpc.alarmPanel.disarm.useMutation`. */
|
|
7520
|
+
var useAlarmPanelDisarm = trpc.alarmPanel.disarm.useMutation;
|
|
7521
|
+
/** Generated alias around `trpc.alarmPanel.trigger.useMutation`. */
|
|
7522
|
+
var useAlarmPanelTrigger = trpc.alarmPanel.trigger.useMutation;
|
|
7523
|
+
/** Generated alias around `trpc.alarmPanel.getStatus.useQuery`. */
|
|
7524
|
+
var useAlarmPanelGetStatus = trpc.alarmPanel.getStatus.useQuery;
|
|
7525
|
+
/** Generated alias around `trpc.alerts.emit.useMutation`. */
|
|
7526
|
+
var useAlertsEmit = trpc.alerts.emit.useMutation;
|
|
7527
|
+
/** Generated alias around `trpc.alerts.update.useMutation`. */
|
|
7528
|
+
var useAlertsUpdate = trpc.alerts.update.useMutation;
|
|
7529
|
+
/** Generated alias around `trpc.alerts.list.useQuery`. */
|
|
7530
|
+
var useAlertsList = trpc.alerts.list.useQuery;
|
|
7531
|
+
/** Generated alias around `trpc.alerts.getUnreadCount.useQuery`. */
|
|
7532
|
+
var useAlertsGetUnreadCount = trpc.alerts.getUnreadCount.useQuery;
|
|
7533
|
+
/** Generated alias around `trpc.alerts.markRead.useMutation`. */
|
|
7534
|
+
var useAlertsMarkRead = trpc.alerts.markRead.useMutation;
|
|
7535
|
+
/** Generated alias around `trpc.alerts.markAllRead.useMutation`. */
|
|
7536
|
+
var useAlertsMarkAllRead = trpc.alerts.markAllRead.useMutation;
|
|
7537
|
+
/** Generated alias around `trpc.alerts.dismiss.useMutation`. */
|
|
7538
|
+
var useAlertsDismiss = trpc.alerts.dismiss.useMutation;
|
|
7539
|
+
/** Generated alias around `trpc.ambientLightSensor.getStatus.useQuery`. */
|
|
7540
|
+
var useAmbientLightSensorGetStatus = trpc.ambientLightSensor.getStatus.useQuery;
|
|
7541
|
+
/** Generated alias around `trpc.audioAnalysis.resolveDeviceSettings.useQuery`. */
|
|
7542
|
+
var useAudioAnalysisResolveDeviceSettings = trpc.audioAnalysis.resolveDeviceSettings.useQuery;
|
|
7543
|
+
/** Generated alias around `trpc.audioAnalysis.getDeviceSettingsContribution.useQuery`. */
|
|
6601
7544
|
var useAudioAnalysisGetDeviceSettingsContribution = trpc.audioAnalysis.getDeviceSettingsContribution.useQuery;
|
|
6602
7545
|
/** Generated alias around `trpc.audioAnalysis.getDeviceLiveContribution.useQuery`. */
|
|
6603
7546
|
var useAudioAnalysisGetDeviceLiveContribution = trpc.audioAnalysis.getDeviceLiveContribution.useQuery;
|
|
@@ -7267,6 +8210,24 @@ var useIntercomPushTalkAudio = trpc.intercom.pushTalkAudio.useMutation;
|
|
|
7267
8210
|
var useIntercomEndTalkSession = trpc.intercom.endTalkSession.useMutation;
|
|
7268
8211
|
/** Generated alias around `trpc.intercom.getStatus.useQuery`. */
|
|
7269
8212
|
var useIntercomGetStatus = trpc.intercom.getStatus.useQuery;
|
|
8213
|
+
/** Generated alias around `trpc.keychainAdmin.status.useQuery`. */
|
|
8214
|
+
var useKeychainAdminStatus = trpc.keychainAdmin.status.useQuery;
|
|
8215
|
+
/** Generated alias around `trpc.keychainAdmin.listSecrets.useQuery`. */
|
|
8216
|
+
var useKeychainAdminListSecrets = trpc.keychainAdmin.listSecrets.useQuery;
|
|
8217
|
+
/** Generated alias around `trpc.keychainAdmin.listAudit.useQuery`. */
|
|
8218
|
+
var useKeychainAdminListAudit = trpc.keychainAdmin.listAudit.useQuery;
|
|
8219
|
+
/** Generated alias around `trpc.keychainAdmin.reveal.useMutation`. */
|
|
8220
|
+
var useKeychainAdminReveal = trpc.keychainAdmin.reveal.useMutation;
|
|
8221
|
+
/** Generated alias around `trpc.keychainAdmin.deleteOrphans.useMutation`. */
|
|
8222
|
+
var useKeychainAdminDeleteOrphans = trpc.keychainAdmin.deleteOrphans.useMutation;
|
|
8223
|
+
/** Generated alias around `trpc.keychainAdmin.rotateMasterKey.useMutation`. */
|
|
8224
|
+
var useKeychainAdminRotateMasterKey = trpc.keychainAdmin.rotateMasterKey.useMutation;
|
|
8225
|
+
/** Generated alias around `trpc.keychainAdmin.purgeRetiredKeys.useMutation`. */
|
|
8226
|
+
var useKeychainAdminPurgeRetiredKeys = trpc.keychainAdmin.purgeRetiredKeys.useMutation;
|
|
8227
|
+
/** Generated alias around `trpc.keychainAdmin.downloadRecoveryKey.useMutation`. */
|
|
8228
|
+
var useKeychainAdminDownloadRecoveryKey = trpc.keychainAdmin.downloadRecoveryKey.useMutation;
|
|
8229
|
+
/** Generated alias around `trpc.keychainAdmin.resetKeychain.useMutation`. */
|
|
8230
|
+
var useKeychainAdminResetKeychain = trpc.keychainAdmin.resetKeychain.useMutation;
|
|
7270
8231
|
/** Generated alias around `trpc.lawnMowerControl.startMowing.useMutation`. */
|
|
7271
8232
|
var useLawnMowerControlStartMowing = trpc.lawnMowerControl.startMowing.useMutation;
|
|
7272
8233
|
/** Generated alias around `trpc.lawnMowerControl.pause.useMutation`. */
|
|
@@ -7803,6 +8764,30 @@ var usePipelineAnalyticsCompleteRetrainTrack = trpc.pipelineAnalytics.completeRe
|
|
|
7803
8764
|
var usePipelineAnalyticsRestageRetrainTrack = trpc.pipelineAnalytics.restageRetrainTrack.useMutation;
|
|
7804
8765
|
/** Generated alias around `trpc.pipelineAnalytics.getRetrainExportUrl.useQuery`. */
|
|
7805
8766
|
var usePipelineAnalyticsGetRetrainExportUrl = trpc.pipelineAnalytics.getRetrainExportUrl.useQuery;
|
|
8767
|
+
/** Generated alias around `trpc.pipelineAnalytics.setRetrainFrameComplete.useMutation`. */
|
|
8768
|
+
var usePipelineAnalyticsSetRetrainFrameComplete = trpc.pipelineAnalytics.setRetrainFrameComplete.useMutation;
|
|
8769
|
+
/** Generated alias around `trpc.pipelineAnalytics.writeRetrainArchive.useMutation`. */
|
|
8770
|
+
var usePipelineAnalyticsWriteRetrainArchive = trpc.pipelineAnalytics.writeRetrainArchive.useMutation;
|
|
8771
|
+
/** Generated alias around `trpc.pipelineAnalytics.listRetrainAssistProfiles.useQuery`. */
|
|
8772
|
+
var usePipelineAnalyticsListRetrainAssistProfiles = trpc.pipelineAnalytics.listRetrainAssistProfiles.useQuery;
|
|
8773
|
+
/** Generated alias around `trpc.pipelineAnalytics.proposeRetrainAnnotationsWithLlm.useMutation`. */
|
|
8774
|
+
var usePipelineAnalyticsProposeRetrainAnnotationsWithLlm = trpc.pipelineAnalytics.proposeRetrainAnnotationsWithLlm.useMutation;
|
|
8775
|
+
/** Generated alias around `trpc.pipelineAnalytics.createRetrainCollection.useMutation`. */
|
|
8776
|
+
var usePipelineAnalyticsCreateRetrainCollection = trpc.pipelineAnalytics.createRetrainCollection.useMutation;
|
|
8777
|
+
/** Generated alias around `trpc.pipelineAnalytics.listRetrainCollections.useQuery`. */
|
|
8778
|
+
var usePipelineAnalyticsListRetrainCollections = trpc.pipelineAnalytics.listRetrainCollections.useQuery;
|
|
8779
|
+
/** Generated alias around `trpc.pipelineAnalytics.renameRetrainCollection.useMutation`. */
|
|
8780
|
+
var usePipelineAnalyticsRenameRetrainCollection = trpc.pipelineAnalytics.renameRetrainCollection.useMutation;
|
|
8781
|
+
/** Generated alias around `trpc.pipelineAnalytics.deleteRetrainCollection.useMutation`. */
|
|
8782
|
+
var usePipelineAnalyticsDeleteRetrainCollection = trpc.pipelineAnalytics.deleteRetrainCollection.useMutation;
|
|
8783
|
+
/** Generated alias around `trpc.pipelineAnalytics.addRetrainFrameFromRecording.useMutation`. */
|
|
8784
|
+
var usePipelineAnalyticsAddRetrainFrameFromRecording = trpc.pipelineAnalytics.addRetrainFrameFromRecording.useMutation;
|
|
8785
|
+
/** Generated alias around `trpc.pipelineAnalytics.addRetrainFramesToCollection.useMutation`. */
|
|
8786
|
+
var usePipelineAnalyticsAddRetrainFramesToCollection = trpc.pipelineAnalytics.addRetrainFramesToCollection.useMutation;
|
|
8787
|
+
/** Generated alias around `trpc.pipelineAnalytics.removeRetrainFrameFromCollection.useMutation`. */
|
|
8788
|
+
var usePipelineAnalyticsRemoveRetrainFrameFromCollection = trpc.pipelineAnalytics.removeRetrainFrameFromCollection.useMutation;
|
|
8789
|
+
/** Generated alias around `trpc.pipelineAnalytics.listRetrainCollectionFrames.useQuery`. */
|
|
8790
|
+
var usePipelineAnalyticsListRetrainCollectionFrames = trpc.pipelineAnalytics.listRetrainCollectionFrames.useQuery;
|
|
7806
8791
|
/** Generated alias around `trpc.pipelineAnalytics.getEventMedia.useQuery`. */
|
|
7807
8792
|
var usePipelineAnalyticsGetEventMedia = trpc.pipelineAnalytics.getEventMedia.useQuery;
|
|
7808
8793
|
/** Generated alias around `trpc.pipelineAnalytics.getTrackMedia.useQuery`. */
|
|
@@ -7975,6 +8960,20 @@ var usePipelineOrchestratorSuggestShares = trpc.pipelineOrchestrator.suggestShar
|
|
|
7975
8960
|
var usePipelineOrchestratorGetEffectiveShares = trpc.pipelineOrchestrator.getEffectiveShares.useQuery;
|
|
7976
8961
|
/** Generated alias around `trpc.pipelineOrchestrator.setShareSource.useMutation`. */
|
|
7977
8962
|
var usePipelineOrchestratorSetShareSource = trpc.pipelineOrchestrator.setShareSource.useMutation;
|
|
8963
|
+
/** Generated alias around `trpc.pipelineOrchestrator.getNodeBenchmarkPreflight.useQuery`. */
|
|
8964
|
+
var usePipelineOrchestratorGetNodeBenchmarkPreflight = trpc.pipelineOrchestrator.getNodeBenchmarkPreflight.useQuery;
|
|
8965
|
+
/** Generated alias around `trpc.pipelineOrchestrator.startNodeBenchmark.useMutation`. */
|
|
8966
|
+
var usePipelineOrchestratorStartNodeBenchmark = trpc.pipelineOrchestrator.startNodeBenchmark.useMutation;
|
|
8967
|
+
/** Generated alias around `trpc.pipelineOrchestrator.getNodeBenchmarkStatus.useQuery`. */
|
|
8968
|
+
var usePipelineOrchestratorGetNodeBenchmarkStatus = trpc.pipelineOrchestrator.getNodeBenchmarkStatus.useQuery;
|
|
8969
|
+
/** Generated alias around `trpc.pipelineOrchestrator.abortNodeBenchmark.useMutation`. */
|
|
8970
|
+
var usePipelineOrchestratorAbortNodeBenchmark = trpc.pipelineOrchestrator.abortNodeBenchmark.useMutation;
|
|
8971
|
+
/** Generated alias around `trpc.pipelineOrchestrator.getNodeBenchmarkCandidates.useQuery`. */
|
|
8972
|
+
var usePipelineOrchestratorGetNodeBenchmarkCandidates = trpc.pipelineOrchestrator.getNodeBenchmarkCandidates.useQuery;
|
|
8973
|
+
/** Generated alias around `trpc.pipelineOrchestrator.listNodeBenchmarkResults.useQuery`. */
|
|
8974
|
+
var usePipelineOrchestratorListNodeBenchmarkResults = trpc.pipelineOrchestrator.listNodeBenchmarkResults.useQuery;
|
|
8975
|
+
/** Generated alias around `trpc.pipelineOrchestrator.deleteNodeBenchmarkResult.useMutation`. */
|
|
8976
|
+
var usePipelineOrchestratorDeleteNodeBenchmarkResult = trpc.pipelineOrchestrator.deleteNodeBenchmarkResult.useMutation;
|
|
7978
8977
|
/** Generated alias around `trpc.pipelineOrchestrator.getCameraSettings.useQuery`. */
|
|
7979
8978
|
var usePipelineOrchestratorGetCameraSettings = trpc.pipelineOrchestrator.getCameraSettings.useQuery;
|
|
7980
8979
|
/** Generated alias around `trpc.pipelineOrchestrator.setCameraStepToggle.useMutation`. */
|
|
@@ -8039,6 +9038,12 @@ var usePipelineRunnerParkTrackFrame = trpc.pipelineRunner.parkTrackFrame.useMuta
|
|
|
8039
9038
|
var usePipelineRunnerGetParkedTrackFrame = trpc.pipelineRunner.getParkedTrackFrame.useQuery;
|
|
8040
9039
|
/** Generated alias around `trpc.pipelineRunner.releaseParkedTrackFrames.useMutation`. */
|
|
8041
9040
|
var usePipelineRunnerReleaseParkedTrackFrames = trpc.pipelineRunner.releaseParkedTrackFrames.useMutation;
|
|
9041
|
+
/** Generated alias around `trpc.pipelineRunner.startNodeBench.useMutation`. */
|
|
9042
|
+
var usePipelineRunnerStartNodeBench = trpc.pipelineRunner.startNodeBench.useMutation;
|
|
9043
|
+
/** Generated alias around `trpc.pipelineRunner.getNodeBenchStatus.useQuery`. */
|
|
9044
|
+
var usePipelineRunnerGetNodeBenchStatus = trpc.pipelineRunner.getNodeBenchStatus.useQuery;
|
|
9045
|
+
/** Generated alias around `trpc.pipelineRunner.abortNodeBench.useMutation`. */
|
|
9046
|
+
var usePipelineRunnerAbortNodeBench = trpc.pipelineRunner.abortNodeBench.useMutation;
|
|
8042
9047
|
/** Generated alias around `trpc.plateGallery.listPlates.useQuery`. */
|
|
8043
9048
|
var usePlateGalleryListPlates = trpc.plateGallery.listPlates.useQuery;
|
|
8044
9049
|
/** Generated alias around `trpc.plateGallery.getPlateByTrack.useQuery`. */
|
|
@@ -8591,6 +9596,14 @@ var useUserManagementOauthExchangeCode = trpc.userManagement.oauthExchangeCode.u
|
|
|
8591
9596
|
var useUserManagementOauthRefresh = trpc.userManagement.oauthRefresh.useMutation;
|
|
8592
9597
|
/** Generated alias around `trpc.userManagement.oauthVerifyAccessToken.useQuery`. */
|
|
8593
9598
|
var useUserManagementOauthVerifyAccessToken = trpc.userManagement.oauthVerifyAccessToken.useQuery;
|
|
9599
|
+
/** Generated alias around `trpc.userManagement.listExternalIdentities.useQuery`. */
|
|
9600
|
+
var useUserManagementListExternalIdentities = trpc.userManagement.listExternalIdentities.useQuery;
|
|
9601
|
+
/** Generated alias around `trpc.userManagement.findUserByExternalIdentity.useQuery`. */
|
|
9602
|
+
var useUserManagementFindUserByExternalIdentity = trpc.userManagement.findUserByExternalIdentity.useQuery;
|
|
9603
|
+
/** Generated alias around `trpc.userManagement.linkExternalIdentity.useMutation`. */
|
|
9604
|
+
var useUserManagementLinkExternalIdentity = trpc.userManagement.linkExternalIdentity.useMutation;
|
|
9605
|
+
/** Generated alias around `trpc.userManagement.unlinkExternalIdentity.useMutation`. */
|
|
9606
|
+
var useUserManagementUnlinkExternalIdentity = trpc.userManagement.unlinkExternalIdentity.useMutation;
|
|
8594
9607
|
/** Generated alias around `trpc.userManagement.listOauthSessions.useQuery`. */
|
|
8595
9608
|
var useUserManagementListOauthSessions = trpc.userManagement.listOauthSessions.useQuery;
|
|
8596
9609
|
/** Generated alias around `trpc.userManagement.revokeOauthSession.useMutation`. */
|
|
@@ -20876,7 +21889,7 @@ function formatDurationMs(ms) {
|
|
|
20876
21889
|
* firmware inventing a refusal reaches the operator rather than becoming a
|
|
20877
21890
|
* blank tile.
|
|
20878
21891
|
*/
|
|
20879
|
-
var REFUSAL_TEXT = {
|
|
21892
|
+
var REFUSAL_TEXT$1 = {
|
|
20880
21893
|
sleeping: () => "The camera is asleep — a still would cost a wake",
|
|
20881
21894
|
"camera-refused": (reason) => `The camera refused — ${reason}`,
|
|
20882
21895
|
"no-keyframe": () => "No decodable frame inside this window",
|
|
@@ -20888,7 +21901,7 @@ var REFUSAL_TEXT = {
|
|
|
20888
21901
|
"uid-missing": () => "The camera’s id is not known yet"
|
|
20889
21902
|
};
|
|
20890
21903
|
function wordsFor(code, reason) {
|
|
20891
|
-
const known = code === void 0 ? void 0 : REFUSAL_TEXT[code];
|
|
21904
|
+
const known = code === void 0 ? void 0 : REFUSAL_TEXT$1[code];
|
|
20892
21905
|
return known === void 0 ? reason : known(reason);
|
|
20893
21906
|
}
|
|
20894
21907
|
function refusal(reason, code) {
|
|
@@ -24471,6 +25484,426 @@ function useScrubController(sink, isPlayerLive = () => false) {
|
|
|
24471
25484
|
};
|
|
24472
25485
|
}
|
|
24473
25486
|
//#endregion
|
|
25487
|
+
//#region src/primitives/dialog.tsx
|
|
25488
|
+
var DialogContext = (0, react$1.createContext)(null);
|
|
25489
|
+
function useDialogContext() {
|
|
25490
|
+
const ctx = (0, react$1.useContext)(DialogContext);
|
|
25491
|
+
if (!ctx) throw new Error("Dialog compound components must be used within <Dialog>");
|
|
25492
|
+
return ctx;
|
|
25493
|
+
}
|
|
25494
|
+
function Dialog({ children, open: controlledOpen, onOpenChange }) {
|
|
25495
|
+
const [uncontrolledOpen, setUncontrolledOpen] = (0, react$1.useState)(false);
|
|
25496
|
+
const open = controlledOpen ?? uncontrolledOpen;
|
|
25497
|
+
const contentId = (0, react$1.useId)();
|
|
25498
|
+
const setOpen = (0, react$1.useCallback)((next) => {
|
|
25499
|
+
onOpenChange?.(next);
|
|
25500
|
+
if (controlledOpen === void 0) setUncontrolledOpen(next);
|
|
25501
|
+
}, [controlledOpen, onOpenChange]);
|
|
25502
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogContext.Provider, {
|
|
25503
|
+
value: {
|
|
25504
|
+
open,
|
|
25505
|
+
setOpen,
|
|
25506
|
+
contentId
|
|
25507
|
+
},
|
|
25508
|
+
children
|
|
25509
|
+
});
|
|
25510
|
+
}
|
|
25511
|
+
function DialogTrigger({ children, ...props }) {
|
|
25512
|
+
const { setOpen } = useDialogContext();
|
|
25513
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
25514
|
+
type: "button",
|
|
25515
|
+
onClick: () => setOpen(true),
|
|
25516
|
+
...props,
|
|
25517
|
+
children
|
|
25518
|
+
});
|
|
25519
|
+
}
|
|
25520
|
+
/**
|
|
25521
|
+
* A native <dialog> opened with showModal() is promoted to the top layer, so
|
|
25522
|
+
* ancestor transform/overflow can't clip it — but it relies on the UA
|
|
25523
|
+
* stylesheet's `dialog { margin: auto }` for modal centering. Tailwind's
|
|
25524
|
+
* preflight resets `margin: 0` on every element, which defeats that centering
|
|
25525
|
+
* and pins the modal to the top-left. We therefore center deterministically:
|
|
25526
|
+
* a top-layer dialog with `position: fixed` is viewport-relative, and a
|
|
25527
|
+
* self-transform is fine, so `fixed left-1/2 top-1/2 -translate-x-1/2
|
|
25528
|
+
* -translate-y-1/2 m-0` always centers regardless of any margin reset. Width
|
|
25529
|
+
* is capped to the viewport (`w-[calc(100vw-2rem)]`) rather than a bare
|
|
25530
|
+
* `w-full` (which would overflow/clip a centered dialog); the per-variant
|
|
25531
|
+
* `max-w-*` keeps small dialogs small while the cap prevents overflow.
|
|
25532
|
+
*
|
|
25533
|
+
* Height is capped against the SAFE viewport, not the raw one. A top-layer
|
|
25534
|
+
* `fixed` dialog is viewport-relative, and under `viewport-fit=cover` the
|
|
25535
|
+
* viewport extends behind the iOS notch/Dynamic Island and home indicator —
|
|
25536
|
+
* so a dialog tall enough to hit its `max-h` put its header under the clock.
|
|
25537
|
+
* Because the box is centred on the viewport (not on the safe area), the cap
|
|
25538
|
+
* has to subtract TWICE the LARGER of the two insets for the box to clear
|
|
25539
|
+
* both edges; using each inset once would still overflow the taller side
|
|
25540
|
+
* whenever they are asymmetric (they are: ~59px top vs ~34px bottom). Every
|
|
25541
|
+
* term collapses to the previous `100dvh - 2rem` where the insets are 0.
|
|
25542
|
+
*/
|
|
25543
|
+
var contentVariants = cva("fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 m-0 w-[calc(100vw-2rem)] bg-background-elevated text-foreground border border-border rounded-lg p-4 backdrop:bg-black/50 backdrop:backdrop-blur-sm max-h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))] [&:not([open])]:hidden", {
|
|
25544
|
+
variants: {
|
|
25545
|
+
width: {
|
|
25546
|
+
sm: "max-w-sm",
|
|
25547
|
+
md: "max-w-md",
|
|
25548
|
+
lg: "max-w-lg"
|
|
25549
|
+
},
|
|
25550
|
+
/**
|
|
25551
|
+
* WHICH box scrolls when the content is taller than the height cap.
|
|
25552
|
+
*
|
|
25553
|
+
* `content` (default) scrolls the whole dialog: the title, any tab strip
|
|
25554
|
+
* and the Save/Cancel row travel with the fields. That is fine for a
|
|
25555
|
+
* short form and wrong for a long one — the operator scrolls to the
|
|
25556
|
+
* bottom of a rule editor and the buttons are somewhere above him, the
|
|
25557
|
+
* tab strip is gone, and the only way back to either is to scroll the
|
|
25558
|
+
* form he was reading.
|
|
25559
|
+
*
|
|
25560
|
+
* `body` makes the dialog a flex column that does not scroll at all and
|
|
25561
|
+
* hands the scrolling to `<DialogBody>`. The chrome around the body —
|
|
25562
|
+
* header, tab strip, footer — stays put at every height. The dialog
|
|
25563
|
+
* keeps its own padding either way, so `sheet-on-mobile`'s
|
|
25564
|
+
* safe-area bottom padding still applies to the footer.
|
|
25565
|
+
*
|
|
25566
|
+
* `body` also takes a DEFINITE height below `md` — see the
|
|
25567
|
+
* `scroll: 'body'` compound variants. That is not a style choice; the
|
|
25568
|
+
* flex chain does not resolve without it on iOS Safari.
|
|
25569
|
+
*/
|
|
25570
|
+
scroll: {
|
|
25571
|
+
content: "overflow-y-auto",
|
|
25572
|
+
body: "flex flex-col overflow-hidden"
|
|
25573
|
+
},
|
|
25574
|
+
placement: {
|
|
25575
|
+
center: "",
|
|
25576
|
+
"sheet-on-mobile": "bottom-0 left-0 right-0 top-auto w-full max-w-full translate-x-0 translate-y-0 rounded-b-none max-h-[92dvh] pb-[calc(1rem+env(safe-area-inset-bottom))] md:left-1/2 md:top-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:rounded-lg md:bottom-auto md:pb-4 md:max-h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))]"
|
|
25577
|
+
}
|
|
25578
|
+
},
|
|
25579
|
+
compoundVariants: [
|
|
25580
|
+
{
|
|
25581
|
+
placement: "sheet-on-mobile",
|
|
25582
|
+
width: "sm",
|
|
25583
|
+
class: "md:w-[calc(100vw-2rem)] md:max-w-sm"
|
|
25584
|
+
},
|
|
25585
|
+
{
|
|
25586
|
+
placement: "sheet-on-mobile",
|
|
25587
|
+
width: "md",
|
|
25588
|
+
class: "md:w-[calc(100vw-2rem)] md:max-w-md"
|
|
25589
|
+
},
|
|
25590
|
+
{
|
|
25591
|
+
placement: "sheet-on-mobile",
|
|
25592
|
+
width: "lg",
|
|
25593
|
+
class: "md:w-[calc(100vw-2rem)] md:max-w-lg"
|
|
25594
|
+
},
|
|
25595
|
+
{
|
|
25596
|
+
scroll: "body",
|
|
25597
|
+
placement: "sheet-on-mobile",
|
|
25598
|
+
class: "h-[92dvh] md:h-auto"
|
|
25599
|
+
},
|
|
25600
|
+
{
|
|
25601
|
+
scroll: "body",
|
|
25602
|
+
placement: "center",
|
|
25603
|
+
class: "h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))] md:h-auto"
|
|
25604
|
+
}
|
|
25605
|
+
],
|
|
25606
|
+
defaultVariants: {
|
|
25607
|
+
width: "md",
|
|
25608
|
+
placement: "center",
|
|
25609
|
+
scroll: "content"
|
|
25610
|
+
}
|
|
25611
|
+
});
|
|
25612
|
+
var DialogContent = (0, react$1.forwardRef)(({ className, width, placement, scroll, children, ...props }, ref) => {
|
|
25613
|
+
const { open, setOpen, contentId } = useDialogContext();
|
|
25614
|
+
const innerRef = (0, react$1.useRef)(null);
|
|
25615
|
+
const dialogRef = ref ?? innerRef;
|
|
25616
|
+
(0, react$1.useEffect)(() => {
|
|
25617
|
+
const el = dialogRef.current;
|
|
25618
|
+
if (!el) return;
|
|
25619
|
+
if (open && !el.open) el.showModal();
|
|
25620
|
+
if (!open && el.open) el.close();
|
|
25621
|
+
}, [open, dialogRef]);
|
|
25622
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dialog", {
|
|
25623
|
+
ref: dialogRef,
|
|
25624
|
+
id: contentId,
|
|
25625
|
+
className: cn(contentVariants({
|
|
25626
|
+
width,
|
|
25627
|
+
placement,
|
|
25628
|
+
scroll
|
|
25629
|
+
}), className),
|
|
25630
|
+
onClose: () => setOpen(false),
|
|
25631
|
+
...props,
|
|
25632
|
+
children
|
|
25633
|
+
});
|
|
25634
|
+
});
|
|
25635
|
+
DialogContent.displayName = "DialogContent";
|
|
25636
|
+
/** `shrink-0` is inert outside a flex column and load-bearing inside one:
|
|
25637
|
+
* under `scroll="body"` the header must keep its height while the body gives
|
|
25638
|
+
* ground, or a long form squeezes its own title to nothing. */
|
|
25639
|
+
function DialogHeader({ className, ...props }) {
|
|
25640
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
25641
|
+
className: cn("flex flex-col gap-1 mb-3 shrink-0", className),
|
|
25642
|
+
...props
|
|
25643
|
+
});
|
|
25644
|
+
}
|
|
25645
|
+
/**
|
|
25646
|
+
* The scrolling middle of a `scroll="body"` dialog.
|
|
25647
|
+
*
|
|
25648
|
+
* `min-h-0` is what makes it work: a flex item's default `min-height: auto`
|
|
25649
|
+
* refuses to shrink below its content, so without it the body pushes the
|
|
25650
|
+
* footer out of the dialog instead of scrolling — the exact failure this
|
|
25651
|
+
* variant exists to remove. Anything sticky INSIDE a dialog body pins against
|
|
25652
|
+
* this element, since it is the scroll container.
|
|
25653
|
+
*
|
|
25654
|
+
* It only bounds a scroll area if every ancestor up to the dialog has a
|
|
25655
|
+
* resolved height. `DialogContent` supplies the top of that chain (see the
|
|
25656
|
+
* `scroll: 'body'` compound variants); an intermediate wrapper between them —
|
|
25657
|
+
* a `<Tabs>` that owns the tab strip, say — must carry `flex-1 min-h-0` too,
|
|
25658
|
+
* or it collapses and takes the body with it.
|
|
25659
|
+
*/
|
|
25660
|
+
function DialogBody({ className, ...props }) {
|
|
25661
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
25662
|
+
className: cn("flex-1 min-h-0 overflow-y-auto", className),
|
|
25663
|
+
...props
|
|
25664
|
+
});
|
|
25665
|
+
}
|
|
25666
|
+
function DialogFooter({ className, ...props }) {
|
|
25667
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
25668
|
+
className: cn("flex justify-end gap-2 mt-4 shrink-0", className),
|
|
25669
|
+
...props
|
|
25670
|
+
});
|
|
25671
|
+
}
|
|
25672
|
+
function DialogTitle({ className, ...props }) {
|
|
25673
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
25674
|
+
className: cn("text-sm font-semibold text-foreground", className),
|
|
25675
|
+
...props
|
|
25676
|
+
});
|
|
25677
|
+
}
|
|
25678
|
+
function DialogDescription({ className, ...props }) {
|
|
25679
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
25680
|
+
className: cn("text-xs text-foreground-muted", className),
|
|
25681
|
+
...props
|
|
25682
|
+
});
|
|
25683
|
+
}
|
|
25684
|
+
//#endregion
|
|
25685
|
+
//#region src/primitives/input.tsx
|
|
25686
|
+
/**
|
|
25687
|
+
* `disabled:opacity-50 disabled:cursor-not-allowed` for the same reason as
|
|
25688
|
+
* `Select` — an explicit `bg-surface` / `text-foreground` overrides the greying
|
|
25689
|
+
* a browser would otherwise apply, leaving an inert field indistinguishable
|
|
25690
|
+
* from a live one. See `disabled-affordance.test.tsx`.
|
|
25691
|
+
*/
|
|
25692
|
+
var inputVariants = cva("h-7 w-full px-2.5 text-xs bg-surface border rounded-md text-foreground placeholder:text-foreground-subtle focus:outline-none focus:ring-1 focus:ring-primary transition-colors disabled:opacity-50 disabled:cursor-not-allowed", {
|
|
25693
|
+
variants: { state: {
|
|
25694
|
+
default: "border-border",
|
|
25695
|
+
error: "border-danger"
|
|
25696
|
+
} },
|
|
25697
|
+
defaultVariants: { state: "default" }
|
|
25698
|
+
});
|
|
25699
|
+
var Input = (0, react$1.forwardRef)(({ className, state, leftSlot, rightSlot, ...props }, ref) => {
|
|
25700
|
+
if (leftSlot || rightSlot) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
25701
|
+
className: "relative flex items-center w-full",
|
|
25702
|
+
children: [
|
|
25703
|
+
leftSlot && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
25704
|
+
className: "absolute left-2.5 flex items-center pointer-events-none",
|
|
25705
|
+
children: leftSlot
|
|
25706
|
+
}),
|
|
25707
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
25708
|
+
ref,
|
|
25709
|
+
className: cn(inputVariants({ state }), leftSlot ? "pl-7" : "", rightSlot ? "pr-7" : "", className),
|
|
25710
|
+
...props
|
|
25711
|
+
}),
|
|
25712
|
+
rightSlot && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
25713
|
+
className: "absolute right-2.5 flex items-center pointer-events-none",
|
|
25714
|
+
children: rightSlot
|
|
25715
|
+
})
|
|
25716
|
+
]
|
|
25717
|
+
});
|
|
25718
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
25719
|
+
ref,
|
|
25720
|
+
className: cn(inputVariants({ state }), className),
|
|
25721
|
+
...props
|
|
25722
|
+
});
|
|
25723
|
+
});
|
|
25724
|
+
Input.displayName = "Input";
|
|
25725
|
+
//#endregion
|
|
25726
|
+
//#region src/composites/training/training-collection-picker.tsx
|
|
25727
|
+
/**
|
|
25728
|
+
* <TrainingCollectionPicker> — choose a named training collection, or create
|
|
25729
|
+
* one in place.
|
|
25730
|
+
*
|
|
25731
|
+
* A collection is a named set over the retrain corpus ("packages porch
|
|
25732
|
+
* night"). The picker lists them (newest first, with their frame counts) and
|
|
25733
|
+
* offers a name field for a new one; the hub refuses an empty or duplicate
|
|
25734
|
+
* name, and the refusal is shown here rather than swallowed.
|
|
25735
|
+
*/
|
|
25736
|
+
var CREATE_REFUSAL = {
|
|
25737
|
+
"empty-name": "Give the collection a name.",
|
|
25738
|
+
"duplicate-name": "A collection with that name already exists."
|
|
25739
|
+
};
|
|
25740
|
+
function TrainingCollectionPicker({ value, onChange }) {
|
|
25741
|
+
const collections = usePipelineAnalyticsListRetrainCollections({});
|
|
25742
|
+
const create = usePipelineAnalyticsCreateRetrainCollection();
|
|
25743
|
+
const [name, setName] = (0, react$1.useState)("");
|
|
25744
|
+
const [error, setError] = (0, react$1.useState)(null);
|
|
25745
|
+
const list = collections.data ?? [];
|
|
25746
|
+
const onCreate = async () => {
|
|
25747
|
+
setError(null);
|
|
25748
|
+
const result = await create.mutateAsync({ name });
|
|
25749
|
+
if (result.kind === "refused") {
|
|
25750
|
+
setError(CREATE_REFUSAL[result.reason]);
|
|
25751
|
+
return;
|
|
25752
|
+
}
|
|
25753
|
+
setName("");
|
|
25754
|
+
await collections.refetch();
|
|
25755
|
+
onChange(result.collection.id);
|
|
25756
|
+
};
|
|
25757
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
25758
|
+
className: "space-y-2",
|
|
25759
|
+
"data-testid": "training-collection-picker",
|
|
25760
|
+
children: [
|
|
25761
|
+
list.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Select, {
|
|
25762
|
+
"aria-label": "Collection",
|
|
25763
|
+
value: value ?? "",
|
|
25764
|
+
options: [{
|
|
25765
|
+
value: "",
|
|
25766
|
+
label: "Choose a collection…"
|
|
25767
|
+
}, ...list.map((c) => ({
|
|
25768
|
+
value: c.id,
|
|
25769
|
+
label: `${c.name} (${String(c.frameCount)})`
|
|
25770
|
+
}))],
|
|
25771
|
+
onChange: (event) => {
|
|
25772
|
+
if (event.target.value !== "") onChange(event.target.value);
|
|
25773
|
+
}
|
|
25774
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
25775
|
+
className: "text-xs text-foreground-subtle",
|
|
25776
|
+
children: collections.isLoading ? "Reading the collections…" : "No collection yet — create one."
|
|
25777
|
+
}),
|
|
25778
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
25779
|
+
className: "flex items-center gap-2",
|
|
25780
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
|
|
25781
|
+
className: "flex-1",
|
|
25782
|
+
"aria-label": "New collection name",
|
|
25783
|
+
placeholder: "New collection (e.g. missed at night)",
|
|
25784
|
+
value: name,
|
|
25785
|
+
onChange: (event) => {
|
|
25786
|
+
setName(event.target.value);
|
|
25787
|
+
}
|
|
25788
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
25789
|
+
type: "button",
|
|
25790
|
+
variant: "secondary",
|
|
25791
|
+
size: "sm",
|
|
25792
|
+
disabled: create.isPending || name.trim() === "",
|
|
25793
|
+
onClick: () => void onCreate(),
|
|
25794
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, { className: "h-3 w-3" }), "Create"]
|
|
25795
|
+
})]
|
|
25796
|
+
}),
|
|
25797
|
+
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
25798
|
+
className: "text-xs text-danger",
|
|
25799
|
+
children: error
|
|
25800
|
+
})
|
|
25801
|
+
]
|
|
25802
|
+
});
|
|
25803
|
+
}
|
|
25804
|
+
//#endregion
|
|
25805
|
+
//#region src/composites/training/training-refusal-text.ts
|
|
25806
|
+
var REFUSAL_TEXT = {
|
|
25807
|
+
"unknown-collection": "That collection no longer exists — pick another one.",
|
|
25808
|
+
"still-unavailable": "No frame could be taken from the recording at that moment.",
|
|
25809
|
+
"still-busy": "The recorder is busy decoding other frames. Try again in a moment.",
|
|
25810
|
+
"unreadable-image": "The recorder returned something that is not a picture.",
|
|
25811
|
+
"write-failed": "The frame could not be stored. Check the artifacts storage."
|
|
25812
|
+
};
|
|
25813
|
+
/** One sentence for a refusal, with the hub's own detail appended. */
|
|
25814
|
+
function trainingRefusalText(reason, detail) {
|
|
25815
|
+
const base = REFUSAL_TEXT[reason];
|
|
25816
|
+
return detail !== void 0 && detail !== "" ? `${base} (${detail})` : base;
|
|
25817
|
+
}
|
|
25818
|
+
//#endregion
|
|
25819
|
+
//#region src/composites/training/send-frame-to-training.tsx
|
|
25820
|
+
/**
|
|
25821
|
+
* <SendFrameToTrainingButton> — "Send frame to training" from a recording
|
|
25822
|
+
* playhead.
|
|
25823
|
+
*
|
|
25824
|
+
* The frame a detector most needs to learn from is often one where it saw
|
|
25825
|
+
* NOTHING — no event, no track, nothing to mark. From the recording, the
|
|
25826
|
+
* operator stops on that moment and sends it: the hub re-decodes the native
|
|
25827
|
+
* frame at the playhead instant and adds it to a named training collection,
|
|
25828
|
+
* where the Retrain page annotates it like any other frame.
|
|
25829
|
+
*
|
|
25830
|
+
* Draws NOTHING for a session that cannot curate training data: the
|
|
25831
|
+
* collection list is admin-only, and a button that 403s is worse than no
|
|
25832
|
+
* button. It is disabled, with the reason, while there is no playhead.
|
|
25833
|
+
*/
|
|
25834
|
+
function SendFrameToTrainingButton({ deviceId, epochMs }) {
|
|
25835
|
+
const collections = usePipelineAnalyticsListRetrainCollections({}, { retry: false });
|
|
25836
|
+
const send = usePipelineAnalyticsAddRetrainFrameFromRecording();
|
|
25837
|
+
const [open, setOpen] = (0, react$1.useState)(false);
|
|
25838
|
+
const [collectionId, setCollectionId] = (0, react$1.useState)(null);
|
|
25839
|
+
const [instant, setInstant] = (0, react$1.useState)(null);
|
|
25840
|
+
const [outcome, setOutcome] = (0, react$1.useState)(null);
|
|
25841
|
+
if (collections.isError) return null;
|
|
25842
|
+
const onSend = async () => {
|
|
25843
|
+
if (collectionId === null || instant === null) return;
|
|
25844
|
+
const result = await send.mutateAsync({
|
|
25845
|
+
collectionId,
|
|
25846
|
+
deviceId,
|
|
25847
|
+
epochMs: instant
|
|
25848
|
+
});
|
|
25849
|
+
const name = collections.data?.find((c) => c.id === collectionId)?.name ?? "the collection";
|
|
25850
|
+
setOutcome(result.kind === "added" ? {
|
|
25851
|
+
kind: "added",
|
|
25852
|
+
reused: result.reused,
|
|
25853
|
+
collection: name
|
|
25854
|
+
} : {
|
|
25855
|
+
kind: "refused",
|
|
25856
|
+
text: trainingRefusalText(result.reason, result.detail)
|
|
25857
|
+
});
|
|
25858
|
+
if (result.kind === "added") await collections.refetch();
|
|
25859
|
+
};
|
|
25860
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
25861
|
+
type: "button",
|
|
25862
|
+
variant: "secondary",
|
|
25863
|
+
size: "sm",
|
|
25864
|
+
"data-testid": "send-frame-to-training",
|
|
25865
|
+
disabled: epochMs === null,
|
|
25866
|
+
title: epochMs === null ? "Play back a recording first — the frame at the playhead is the one sent" : "Add the frame at the playhead to a training collection",
|
|
25867
|
+
onClick: () => {
|
|
25868
|
+
setInstant(epochMs);
|
|
25869
|
+
setOutcome(null);
|
|
25870
|
+
setOpen(true);
|
|
25871
|
+
},
|
|
25872
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GraduationCap, { className: "h-3.5 w-3.5" }), "Send frame to training"]
|
|
25873
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
|
|
25874
|
+
open,
|
|
25875
|
+
onOpenChange: (next) => !send.isPending && setOpen(next),
|
|
25876
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogContent, { children: [
|
|
25877
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogTitle, { children: "Send this frame to training" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogDescription, { children: instant !== null ? `The camera's frame at ${new Date(instant).toLocaleString()} is taken from the recording at full resolution and added to the collection. Annotate it on the Retrain page — including what the model missed.` : "" })] }),
|
|
25878
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrainingCollectionPicker, {
|
|
25879
|
+
value: collectionId,
|
|
25880
|
+
onChange: setCollectionId
|
|
25881
|
+
}),
|
|
25882
|
+
outcome !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
25883
|
+
className: `mt-2 text-xs ${outcome.kind === "added" ? "text-success" : "text-danger"}`,
|
|
25884
|
+
role: "status",
|
|
25885
|
+
children: outcome.kind === "added" ? outcome.reused ? `That moment was already in the dataset; it is now in ${outcome.collection}.` : `Added to ${outcome.collection}.` : outcome.text
|
|
25886
|
+
}),
|
|
25887
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
25888
|
+
type: "button",
|
|
25889
|
+
variant: "ghost",
|
|
25890
|
+
size: "sm",
|
|
25891
|
+
disabled: send.isPending,
|
|
25892
|
+
onClick: () => {
|
|
25893
|
+
setOpen(false);
|
|
25894
|
+
},
|
|
25895
|
+
children: outcome?.kind === "added" ? "Done" : "Cancel"
|
|
25896
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
25897
|
+
type: "button",
|
|
25898
|
+
size: "sm",
|
|
25899
|
+
disabled: collectionId === null || send.isPending || outcome?.kind === "added",
|
|
25900
|
+
onClick: () => void onSend(),
|
|
25901
|
+
children: [send.isPending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), "Send"]
|
|
25902
|
+
})] })
|
|
25903
|
+
] })
|
|
25904
|
+
})] });
|
|
25905
|
+
}
|
|
25906
|
+
//#endregion
|
|
24474
25907
|
//#region src/composites/cap-settings/CoverageTrack.tsx
|
|
24475
25908
|
function CoverageTrack({ windowFrom, windowTo, spans, playheadMs, onSeek, onScrubStart, onScrubMove, onScrubEnd }) {
|
|
24476
25909
|
const [hoverInfo, setHoverInfo] = (0, react$1.useState)(null);
|
|
@@ -25049,6 +26482,13 @@ function RecordingTimeline({ deviceId }) {
|
|
|
25049
26482
|
dayBounds: computedDayBounds,
|
|
25050
26483
|
onViewChange: handleViewChange
|
|
25051
26484
|
}),
|
|
26485
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
26486
|
+
className: "flex justify-end",
|
|
26487
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SendFrameToTrainingButton, {
|
|
26488
|
+
deviceId,
|
|
26489
|
+
epochMs: displayPlayheadMs
|
|
26490
|
+
})
|
|
26491
|
+
}),
|
|
25052
26492
|
recorded.channelOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
25053
26493
|
"data-testid": "frame-push-status-row",
|
|
25054
26494
|
className: "flex flex-wrap items-center gap-2 text-[10px]",
|
|
@@ -26646,6 +28086,32 @@ function FieldWrapper({ label, description, required, span, children, translatio
|
|
|
26646
28086
|
]
|
|
26647
28087
|
});
|
|
26648
28088
|
}
|
|
28089
|
+
/**
|
|
28090
|
+
* A keychain-stored secret (D787) arrives as its VIEW — `{ $secret, set }` —
|
|
28091
|
+
* or `null`, never as a value; it renders as "set · Change · Clear · Reveal".
|
|
28092
|
+
* Decided ONCE, at mount, from what the form was hydrated with: once the
|
|
28093
|
+
* operator types, the value is a plain string and the field must stay the
|
|
28094
|
+
* same control. A form that was not hydrated from a keychain-aware store (a
|
|
28095
|
+
* create dialog seeding `''`) keeps the plain input.
|
|
28096
|
+
*/
|
|
28097
|
+
function KeychainOr({ field, value, onChange, disabled, translationFn, multiline, children }) {
|
|
28098
|
+
const [keychain] = (0, react$1.useState)(() => (0, _camstack_types.isKeychainStoredField)(field) && isKeychainFieldValue(value));
|
|
28099
|
+
if (!keychain) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children });
|
|
28100
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldWrapper, {
|
|
28101
|
+
label: "label" in field ? field.label : void 0,
|
|
28102
|
+
description: "description" in field ? field.description : void 0,
|
|
28103
|
+
required: "required" in field ? field.required : void 0,
|
|
28104
|
+
span: "span" in field ? field.span : void 0,
|
|
28105
|
+
translationFn,
|
|
28106
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretFieldControl, {
|
|
28107
|
+
field,
|
|
28108
|
+
value,
|
|
28109
|
+
onChange,
|
|
28110
|
+
disabled,
|
|
28111
|
+
multiline
|
|
28112
|
+
})
|
|
28113
|
+
});
|
|
28114
|
+
}
|
|
26649
28115
|
function TextField({ field, value, onChange, disabled, translationFn }) {
|
|
26650
28116
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldWrapper, {
|
|
26651
28117
|
label: field.label,
|
|
@@ -27166,6 +28632,23 @@ function ColorField({ field, value, onChange, disabled, translationFn }) {
|
|
|
27166
28632
|
})]
|
|
27167
28633
|
});
|
|
27168
28634
|
}
|
|
28635
|
+
function IconField({ field, value, onChange, disabled, translationFn }) {
|
|
28636
|
+
const defaultKey = typeof field.default === "string" ? field.default : "";
|
|
28637
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldWrapper, {
|
|
28638
|
+
label: field.label,
|
|
28639
|
+
description: field.description,
|
|
28640
|
+
required: field.required,
|
|
28641
|
+
span: field.span,
|
|
28642
|
+
translationFn,
|
|
28643
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigIconFieldControl, {
|
|
28644
|
+
label: resolveLabel(field.label, translationFn) ?? field.label,
|
|
28645
|
+
value,
|
|
28646
|
+
defaultKey,
|
|
28647
|
+
disabled: disabled || field.disabled,
|
|
28648
|
+
onChange
|
|
28649
|
+
})
|
|
28650
|
+
});
|
|
28651
|
+
}
|
|
27169
28652
|
function ProbeField({ field, value, onChange, disabled, translationFn, onTestField, onAction, externalProbe }) {
|
|
27170
28653
|
const [localStatus, setLocalStatus] = (0, react$1.useState)("idle");
|
|
27171
28654
|
const [localResult, setLocalResult] = (0, react$1.useState)(null);
|
|
@@ -27701,7 +29184,7 @@ function resolveActionParams(field, values) {
|
|
|
27701
29184
|
const params = { ...field.paramsStatic };
|
|
27702
29185
|
if (field.paramsFromForm) for (const [paramKey, formKey] of Object.entries(field.paramsFromForm)) {
|
|
27703
29186
|
const v = values[formKey];
|
|
27704
|
-
if (v !== void 0 && v !== null && v !== "") params[paramKey] = v;
|
|
29187
|
+
if (v !== void 0 && v !== null && v !== "" && !(0, _camstack_types.isSecretClear)(v)) params[paramKey] = v;
|
|
27705
29188
|
}
|
|
27706
29189
|
return params;
|
|
27707
29190
|
}
|
|
@@ -27800,8 +29283,16 @@ function AddonActionButtonField({ field, values, disabled, onAction }) {
|
|
|
27800
29283
|
const [loading, setLoading] = (0, react$1.useState)(false);
|
|
27801
29284
|
const [status, setStatus] = (0, react$1.useState)(null);
|
|
27802
29285
|
const confirm = useConfirm();
|
|
29286
|
+
const prompt = usePrompt();
|
|
27803
29287
|
const deviceId = useDeviceId();
|
|
27804
29288
|
const handleClick = async () => {
|
|
29289
|
+
const asked = field.prompt ? await prompt({
|
|
29290
|
+
title: field.prompt.title ?? field.label,
|
|
29291
|
+
...field.prompt.message !== void 0 ? { message: field.prompt.message } : {},
|
|
29292
|
+
...field.prompt.placeholder !== void 0 ? { placeholder: field.prompt.placeholder } : {},
|
|
29293
|
+
confirmLabel: field.buttonLabel
|
|
29294
|
+
}) : null;
|
|
29295
|
+
if (field.prompt && asked === null) return;
|
|
27805
29296
|
if (field.confirmMessage) {
|
|
27806
29297
|
if (!await confirm({
|
|
27807
29298
|
title: field.label,
|
|
@@ -27812,6 +29303,7 @@ function AddonActionButtonField({ field, values, disabled, onAction }) {
|
|
|
27812
29303
|
}
|
|
27813
29304
|
if (!onAction) return;
|
|
27814
29305
|
const params = resolveActionParams(field, values);
|
|
29306
|
+
if (field.prompt && asked !== null) params[field.prompt.param] = asked;
|
|
27815
29307
|
if (field.includeDeviceId === true && deviceId !== null && deviceId !== void 0) params["deviceId"] = deviceId;
|
|
27816
29308
|
setLoading(true);
|
|
27817
29309
|
setStatus(null);
|
|
@@ -28188,12 +29680,19 @@ function FormField({ field, values, onChange, disabled, translationFn, onTestFie
|
|
|
28188
29680
|
const handleChange = (v) => onChange(field.key, v);
|
|
28189
29681
|
const customRenderer = useCustomFieldRenderer(field.type);
|
|
28190
29682
|
switch (field.type) {
|
|
28191
|
-
case "text": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
29683
|
+
case "text": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeychainOr, {
|
|
28192
29684
|
field,
|
|
28193
29685
|
value,
|
|
28194
29686
|
onChange: handleChange,
|
|
28195
29687
|
disabled,
|
|
28196
|
-
translationFn
|
|
29688
|
+
translationFn,
|
|
29689
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
29690
|
+
field,
|
|
29691
|
+
value,
|
|
29692
|
+
onChange: handleChange,
|
|
29693
|
+
disabled,
|
|
29694
|
+
translationFn
|
|
29695
|
+
})
|
|
28197
29696
|
});
|
|
28198
29697
|
case "number": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberField, {
|
|
28199
29698
|
field,
|
|
@@ -28237,19 +29736,34 @@ function FormField({ field, values, onChange, disabled, translationFn, onTestFie
|
|
|
28237
29736
|
disabled,
|
|
28238
29737
|
translationFn
|
|
28239
29738
|
});
|
|
28240
|
-
case "password": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
29739
|
+
case "password": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeychainOr, {
|
|
28241
29740
|
field,
|
|
28242
29741
|
value,
|
|
28243
29742
|
onChange: handleChange,
|
|
28244
29743
|
disabled,
|
|
28245
|
-
translationFn
|
|
29744
|
+
translationFn,
|
|
29745
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PasswordField, {
|
|
29746
|
+
field,
|
|
29747
|
+
value,
|
|
29748
|
+
onChange: handleChange,
|
|
29749
|
+
disabled,
|
|
29750
|
+
translationFn
|
|
29751
|
+
})
|
|
28246
29752
|
});
|
|
28247
|
-
case "textarea": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
29753
|
+
case "textarea": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeychainOr, {
|
|
28248
29754
|
field,
|
|
28249
29755
|
value,
|
|
28250
29756
|
onChange: handleChange,
|
|
28251
29757
|
disabled,
|
|
28252
|
-
translationFn
|
|
29758
|
+
translationFn,
|
|
29759
|
+
multiline: true,
|
|
29760
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextAreaField, {
|
|
29761
|
+
field,
|
|
29762
|
+
value,
|
|
29763
|
+
onChange: handleChange,
|
|
29764
|
+
disabled,
|
|
29765
|
+
translationFn
|
|
29766
|
+
})
|
|
28253
29767
|
});
|
|
28254
29768
|
case "slider": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderField, {
|
|
28255
29769
|
field,
|
|
@@ -28272,6 +29786,13 @@ function FormField({ field, values, onChange, disabled, translationFn, onTestFie
|
|
|
28272
29786
|
disabled,
|
|
28273
29787
|
translationFn
|
|
28274
29788
|
});
|
|
29789
|
+
case "icon": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconField, {
|
|
29790
|
+
field,
|
|
29791
|
+
value,
|
|
29792
|
+
onChange: handleChange,
|
|
29793
|
+
disabled,
|
|
29794
|
+
translationFn
|
|
29795
|
+
});
|
|
28275
29796
|
case "node-select": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectField, {
|
|
28276
29797
|
field,
|
|
28277
29798
|
value,
|
|
@@ -30044,47 +31565,6 @@ var Separator = (0, react$1.forwardRef)(({ className, orientation, ...props }, r
|
|
|
30044
31565
|
});
|
|
30045
31566
|
Separator.displayName = "Separator";
|
|
30046
31567
|
//#endregion
|
|
30047
|
-
//#region src/primitives/input.tsx
|
|
30048
|
-
/**
|
|
30049
|
-
* `disabled:opacity-50 disabled:cursor-not-allowed` for the same reason as
|
|
30050
|
-
* `Select` — an explicit `bg-surface` / `text-foreground` overrides the greying
|
|
30051
|
-
* a browser would otherwise apply, leaving an inert field indistinguishable
|
|
30052
|
-
* from a live one. See `disabled-affordance.test.tsx`.
|
|
30053
|
-
*/
|
|
30054
|
-
var inputVariants = cva("h-7 w-full px-2.5 text-xs bg-surface border rounded-md text-foreground placeholder:text-foreground-subtle focus:outline-none focus:ring-1 focus:ring-primary transition-colors disabled:opacity-50 disabled:cursor-not-allowed", {
|
|
30055
|
-
variants: { state: {
|
|
30056
|
-
default: "border-border",
|
|
30057
|
-
error: "border-danger"
|
|
30058
|
-
} },
|
|
30059
|
-
defaultVariants: { state: "default" }
|
|
30060
|
-
});
|
|
30061
|
-
var Input = (0, react$1.forwardRef)(({ className, state, leftSlot, rightSlot, ...props }, ref) => {
|
|
30062
|
-
if (leftSlot || rightSlot) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
30063
|
-
className: "relative flex items-center w-full",
|
|
30064
|
-
children: [
|
|
30065
|
-
leftSlot && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
30066
|
-
className: "absolute left-2.5 flex items-center pointer-events-none",
|
|
30067
|
-
children: leftSlot
|
|
30068
|
-
}),
|
|
30069
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
30070
|
-
ref,
|
|
30071
|
-
className: cn(inputVariants({ state }), leftSlot ? "pl-7" : "", rightSlot ? "pr-7" : "", className),
|
|
30072
|
-
...props
|
|
30073
|
-
}),
|
|
30074
|
-
rightSlot && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
30075
|
-
className: "absolute right-2.5 flex items-center pointer-events-none",
|
|
30076
|
-
children: rightSlot
|
|
30077
|
-
})
|
|
30078
|
-
]
|
|
30079
|
-
});
|
|
30080
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
30081
|
-
ref,
|
|
30082
|
-
className: cn(inputVariants({ state }), className),
|
|
30083
|
-
...props
|
|
30084
|
-
});
|
|
30085
|
-
});
|
|
30086
|
-
Input.displayName = "Input";
|
|
30087
|
-
//#endregion
|
|
30088
31568
|
//#region src/primitives/checkbox.tsx
|
|
30089
31569
|
var Checkbox = (0, react$1.forwardRef)(({ className, ...props }, ref) => {
|
|
30090
31570
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -30117,204 +31597,6 @@ var Switch = (0, react$1.forwardRef)(({ className, checked, onCheckedChange, lab
|
|
|
30117
31597
|
});
|
|
30118
31598
|
Switch.displayName = "Switch";
|
|
30119
31599
|
//#endregion
|
|
30120
|
-
//#region src/primitives/dialog.tsx
|
|
30121
|
-
var DialogContext = (0, react$1.createContext)(null);
|
|
30122
|
-
function useDialogContext() {
|
|
30123
|
-
const ctx = (0, react$1.useContext)(DialogContext);
|
|
30124
|
-
if (!ctx) throw new Error("Dialog compound components must be used within <Dialog>");
|
|
30125
|
-
return ctx;
|
|
30126
|
-
}
|
|
30127
|
-
function Dialog({ children, open: controlledOpen, onOpenChange }) {
|
|
30128
|
-
const [uncontrolledOpen, setUncontrolledOpen] = (0, react$1.useState)(false);
|
|
30129
|
-
const open = controlledOpen ?? uncontrolledOpen;
|
|
30130
|
-
const contentId = (0, react$1.useId)();
|
|
30131
|
-
const setOpen = (0, react$1.useCallback)((next) => {
|
|
30132
|
-
onOpenChange?.(next);
|
|
30133
|
-
if (controlledOpen === void 0) setUncontrolledOpen(next);
|
|
30134
|
-
}, [controlledOpen, onOpenChange]);
|
|
30135
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogContext.Provider, {
|
|
30136
|
-
value: {
|
|
30137
|
-
open,
|
|
30138
|
-
setOpen,
|
|
30139
|
-
contentId
|
|
30140
|
-
},
|
|
30141
|
-
children
|
|
30142
|
-
});
|
|
30143
|
-
}
|
|
30144
|
-
function DialogTrigger({ children, ...props }) {
|
|
30145
|
-
const { setOpen } = useDialogContext();
|
|
30146
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
30147
|
-
type: "button",
|
|
30148
|
-
onClick: () => setOpen(true),
|
|
30149
|
-
...props,
|
|
30150
|
-
children
|
|
30151
|
-
});
|
|
30152
|
-
}
|
|
30153
|
-
/**
|
|
30154
|
-
* A native <dialog> opened with showModal() is promoted to the top layer, so
|
|
30155
|
-
* ancestor transform/overflow can't clip it — but it relies on the UA
|
|
30156
|
-
* stylesheet's `dialog { margin: auto }` for modal centering. Tailwind's
|
|
30157
|
-
* preflight resets `margin: 0` on every element, which defeats that centering
|
|
30158
|
-
* and pins the modal to the top-left. We therefore center deterministically:
|
|
30159
|
-
* a top-layer dialog with `position: fixed` is viewport-relative, and a
|
|
30160
|
-
* self-transform is fine, so `fixed left-1/2 top-1/2 -translate-x-1/2
|
|
30161
|
-
* -translate-y-1/2 m-0` always centers regardless of any margin reset. Width
|
|
30162
|
-
* is capped to the viewport (`w-[calc(100vw-2rem)]`) rather than a bare
|
|
30163
|
-
* `w-full` (which would overflow/clip a centered dialog); the per-variant
|
|
30164
|
-
* `max-w-*` keeps small dialogs small while the cap prevents overflow.
|
|
30165
|
-
*
|
|
30166
|
-
* Height is capped against the SAFE viewport, not the raw one. A top-layer
|
|
30167
|
-
* `fixed` dialog is viewport-relative, and under `viewport-fit=cover` the
|
|
30168
|
-
* viewport extends behind the iOS notch/Dynamic Island and home indicator —
|
|
30169
|
-
* so a dialog tall enough to hit its `max-h` put its header under the clock.
|
|
30170
|
-
* Because the box is centred on the viewport (not on the safe area), the cap
|
|
30171
|
-
* has to subtract TWICE the LARGER of the two insets for the box to clear
|
|
30172
|
-
* both edges; using each inset once would still overflow the taller side
|
|
30173
|
-
* whenever they are asymmetric (they are: ~59px top vs ~34px bottom). Every
|
|
30174
|
-
* term collapses to the previous `100dvh - 2rem` where the insets are 0.
|
|
30175
|
-
*/
|
|
30176
|
-
var contentVariants = cva("fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 m-0 w-[calc(100vw-2rem)] bg-background-elevated text-foreground border border-border rounded-lg p-4 backdrop:bg-black/50 backdrop:backdrop-blur-sm max-h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))] [&:not([open])]:hidden", {
|
|
30177
|
-
variants: {
|
|
30178
|
-
width: {
|
|
30179
|
-
sm: "max-w-sm",
|
|
30180
|
-
md: "max-w-md",
|
|
30181
|
-
lg: "max-w-lg"
|
|
30182
|
-
},
|
|
30183
|
-
/**
|
|
30184
|
-
* WHICH box scrolls when the content is taller than the height cap.
|
|
30185
|
-
*
|
|
30186
|
-
* `content` (default) scrolls the whole dialog: the title, any tab strip
|
|
30187
|
-
* and the Save/Cancel row travel with the fields. That is fine for a
|
|
30188
|
-
* short form and wrong for a long one — the operator scrolls to the
|
|
30189
|
-
* bottom of a rule editor and the buttons are somewhere above him, the
|
|
30190
|
-
* tab strip is gone, and the only way back to either is to scroll the
|
|
30191
|
-
* form he was reading.
|
|
30192
|
-
*
|
|
30193
|
-
* `body` makes the dialog a flex column that does not scroll at all and
|
|
30194
|
-
* hands the scrolling to `<DialogBody>`. The chrome around the body —
|
|
30195
|
-
* header, tab strip, footer — stays put at every height. The dialog
|
|
30196
|
-
* keeps its own padding either way, so `sheet-on-mobile`'s
|
|
30197
|
-
* safe-area bottom padding still applies to the footer.
|
|
30198
|
-
*
|
|
30199
|
-
* `body` also takes a DEFINITE height below `md` — see the
|
|
30200
|
-
* `scroll: 'body'` compound variants. That is not a style choice; the
|
|
30201
|
-
* flex chain does not resolve without it on iOS Safari.
|
|
30202
|
-
*/
|
|
30203
|
-
scroll: {
|
|
30204
|
-
content: "overflow-y-auto",
|
|
30205
|
-
body: "flex flex-col overflow-hidden"
|
|
30206
|
-
},
|
|
30207
|
-
placement: {
|
|
30208
|
-
center: "",
|
|
30209
|
-
"sheet-on-mobile": "bottom-0 left-0 right-0 top-auto w-full max-w-full translate-x-0 translate-y-0 rounded-b-none max-h-[92dvh] pb-[calc(1rem+env(safe-area-inset-bottom))] md:left-1/2 md:top-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:rounded-lg md:bottom-auto md:pb-4 md:max-h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))]"
|
|
30210
|
-
}
|
|
30211
|
-
},
|
|
30212
|
-
compoundVariants: [
|
|
30213
|
-
{
|
|
30214
|
-
placement: "sheet-on-mobile",
|
|
30215
|
-
width: "sm",
|
|
30216
|
-
class: "md:w-[calc(100vw-2rem)] md:max-w-sm"
|
|
30217
|
-
},
|
|
30218
|
-
{
|
|
30219
|
-
placement: "sheet-on-mobile",
|
|
30220
|
-
width: "md",
|
|
30221
|
-
class: "md:w-[calc(100vw-2rem)] md:max-w-md"
|
|
30222
|
-
},
|
|
30223
|
-
{
|
|
30224
|
-
placement: "sheet-on-mobile",
|
|
30225
|
-
width: "lg",
|
|
30226
|
-
class: "md:w-[calc(100vw-2rem)] md:max-w-lg"
|
|
30227
|
-
},
|
|
30228
|
-
{
|
|
30229
|
-
scroll: "body",
|
|
30230
|
-
placement: "sheet-on-mobile",
|
|
30231
|
-
class: "h-[92dvh] md:h-auto"
|
|
30232
|
-
},
|
|
30233
|
-
{
|
|
30234
|
-
scroll: "body",
|
|
30235
|
-
placement: "center",
|
|
30236
|
-
class: "h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))] md:h-auto"
|
|
30237
|
-
}
|
|
30238
|
-
],
|
|
30239
|
-
defaultVariants: {
|
|
30240
|
-
width: "md",
|
|
30241
|
-
placement: "center",
|
|
30242
|
-
scroll: "content"
|
|
30243
|
-
}
|
|
30244
|
-
});
|
|
30245
|
-
var DialogContent = (0, react$1.forwardRef)(({ className, width, placement, scroll, children, ...props }, ref) => {
|
|
30246
|
-
const { open, setOpen, contentId } = useDialogContext();
|
|
30247
|
-
const innerRef = (0, react$1.useRef)(null);
|
|
30248
|
-
const dialogRef = ref ?? innerRef;
|
|
30249
|
-
(0, react$1.useEffect)(() => {
|
|
30250
|
-
const el = dialogRef.current;
|
|
30251
|
-
if (!el) return;
|
|
30252
|
-
if (open && !el.open) el.showModal();
|
|
30253
|
-
if (!open && el.open) el.close();
|
|
30254
|
-
}, [open, dialogRef]);
|
|
30255
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dialog", {
|
|
30256
|
-
ref: dialogRef,
|
|
30257
|
-
id: contentId,
|
|
30258
|
-
className: cn(contentVariants({
|
|
30259
|
-
width,
|
|
30260
|
-
placement,
|
|
30261
|
-
scroll
|
|
30262
|
-
}), className),
|
|
30263
|
-
onClose: () => setOpen(false),
|
|
30264
|
-
...props,
|
|
30265
|
-
children
|
|
30266
|
-
});
|
|
30267
|
-
});
|
|
30268
|
-
DialogContent.displayName = "DialogContent";
|
|
30269
|
-
/** `shrink-0` is inert outside a flex column and load-bearing inside one:
|
|
30270
|
-
* under `scroll="body"` the header must keep its height while the body gives
|
|
30271
|
-
* ground, or a long form squeezes its own title to nothing. */
|
|
30272
|
-
function DialogHeader({ className, ...props }) {
|
|
30273
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
30274
|
-
className: cn("flex flex-col gap-1 mb-3 shrink-0", className),
|
|
30275
|
-
...props
|
|
30276
|
-
});
|
|
30277
|
-
}
|
|
30278
|
-
/**
|
|
30279
|
-
* The scrolling middle of a `scroll="body"` dialog.
|
|
30280
|
-
*
|
|
30281
|
-
* `min-h-0` is what makes it work: a flex item's default `min-height: auto`
|
|
30282
|
-
* refuses to shrink below its content, so without it the body pushes the
|
|
30283
|
-
* footer out of the dialog instead of scrolling — the exact failure this
|
|
30284
|
-
* variant exists to remove. Anything sticky INSIDE a dialog body pins against
|
|
30285
|
-
* this element, since it is the scroll container.
|
|
30286
|
-
*
|
|
30287
|
-
* It only bounds a scroll area if every ancestor up to the dialog has a
|
|
30288
|
-
* resolved height. `DialogContent` supplies the top of that chain (see the
|
|
30289
|
-
* `scroll: 'body'` compound variants); an intermediate wrapper between them —
|
|
30290
|
-
* a `<Tabs>` that owns the tab strip, say — must carry `flex-1 min-h-0` too,
|
|
30291
|
-
* or it collapses and takes the body with it.
|
|
30292
|
-
*/
|
|
30293
|
-
function DialogBody({ className, ...props }) {
|
|
30294
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
30295
|
-
className: cn("flex-1 min-h-0 overflow-y-auto", className),
|
|
30296
|
-
...props
|
|
30297
|
-
});
|
|
30298
|
-
}
|
|
30299
|
-
function DialogFooter({ className, ...props }) {
|
|
30300
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
30301
|
-
className: cn("flex justify-end gap-2 mt-4 shrink-0", className),
|
|
30302
|
-
...props
|
|
30303
|
-
});
|
|
30304
|
-
}
|
|
30305
|
-
function DialogTitle({ className, ...props }) {
|
|
30306
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
30307
|
-
className: cn("text-sm font-semibold text-foreground", className),
|
|
30308
|
-
...props
|
|
30309
|
-
});
|
|
30310
|
-
}
|
|
30311
|
-
function DialogDescription({ className, ...props }) {
|
|
30312
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
30313
|
-
className: cn("text-xs text-foreground-muted", className),
|
|
30314
|
-
...props
|
|
30315
|
-
});
|
|
30316
|
-
}
|
|
30317
|
-
//#endregion
|
|
30318
31600
|
//#region src/primitives/tooltip.tsx
|
|
30319
31601
|
var TooltipContext = (0, react$1.createContext)(null);
|
|
30320
31602
|
function useTooltipContext() {
|
|
@@ -49421,1248 +50703,683 @@ function SwitchInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
|
49421
50703
|
optimistic
|
|
49422
50704
|
});
|
|
49423
50705
|
}
|
|
49424
|
-
function useLockCapGuard(trpc, deviceId) {
|
|
49425
|
-
const { cap } = useDeviceCapability({
|
|
49426
|
-
trpc,
|
|
49427
|
-
deviceId,
|
|
49428
|
-
cap: (d) => d.lockControl,
|
|
49429
|
-
slice: (d) => d.state.lockControl
|
|
49430
|
-
});
|
|
49431
|
-
return cap !== void 0;
|
|
49432
|
-
}
|
|
49433
|
-
function LockInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
49434
|
-
if (!useLockCapGuard(trpc, deviceId)) return null;
|
|
49435
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LockPanel, {
|
|
49436
|
-
trpc,
|
|
49437
|
-
deviceId,
|
|
49438
|
-
features,
|
|
49439
|
-
layout: "compact",
|
|
49440
|
-
optimistic
|
|
49441
|
-
});
|
|
49442
|
-
}
|
|
49443
|
-
function LightInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
49444
|
-
if (!useSwitchCapGuard(trpc, deviceId)) return null;
|
|
49445
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49446
|
-
className: "inline-flex items-center gap-2",
|
|
49447
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchPanel, {
|
|
49448
|
-
trpc,
|
|
49449
|
-
deviceId,
|
|
49450
|
-
features,
|
|
49451
|
-
layout: "compact",
|
|
49452
|
-
optimistic
|
|
49453
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BrightnessPanel, {
|
|
49454
|
-
trpc,
|
|
49455
|
-
deviceId,
|
|
49456
|
-
features,
|
|
49457
|
-
layout: "compact",
|
|
49458
|
-
optimistic
|
|
49459
|
-
})]
|
|
49460
|
-
});
|
|
49461
|
-
}
|
|
49462
|
-
function useFanCapGuard(trpc, deviceId) {
|
|
49463
|
-
const { cap } = useDeviceCapability({
|
|
49464
|
-
trpc,
|
|
49465
|
-
deviceId,
|
|
49466
|
-
cap: (d) => d.fanControl,
|
|
49467
|
-
slice: (d) => d.state.fanControl
|
|
49468
|
-
});
|
|
49469
|
-
return cap !== void 0;
|
|
49470
|
-
}
|
|
49471
|
-
function FanInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
49472
|
-
if (!useFanCapGuard(trpc, deviceId)) return null;
|
|
49473
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49474
|
-
className: "inline-flex items-center gap-2",
|
|
49475
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchPanel, {
|
|
49476
|
-
trpc,
|
|
49477
|
-
deviceId,
|
|
49478
|
-
features,
|
|
49479
|
-
layout: "compact",
|
|
49480
|
-
optimistic
|
|
49481
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FanPanel, {
|
|
49482
|
-
trpc,
|
|
49483
|
-
deviceId,
|
|
49484
|
-
features,
|
|
49485
|
-
layout: "compact",
|
|
49486
|
-
optimistic
|
|
49487
|
-
})]
|
|
49488
|
-
});
|
|
49489
|
-
}
|
|
49490
|
-
function useClimateCapGuard(trpc, deviceId) {
|
|
49491
|
-
const { cap } = useDeviceCapability({
|
|
49492
|
-
trpc,
|
|
49493
|
-
deviceId,
|
|
49494
|
-
cap: (d) => d.climateControl,
|
|
49495
|
-
slice: (d) => d.state.climateControl
|
|
49496
|
-
});
|
|
49497
|
-
return cap !== void 0;
|
|
49498
|
-
}
|
|
49499
|
-
function ThermostatInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
49500
|
-
if (!useClimateCapGuard(trpc, deviceId)) return null;
|
|
49501
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClimatePanel, {
|
|
49502
|
-
trpc,
|
|
49503
|
-
deviceId,
|
|
49504
|
-
features,
|
|
49505
|
-
layout: "compact",
|
|
49506
|
-
optimistic
|
|
49507
|
-
});
|
|
49508
|
-
}
|
|
49509
|
-
function AcInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
49510
|
-
if (!useClimateCapGuard(trpc, deviceId)) return null;
|
|
49511
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AcPanel, {
|
|
49512
|
-
trpc,
|
|
49513
|
-
deviceId,
|
|
49514
|
-
features,
|
|
49515
|
-
layout: "compact",
|
|
49516
|
-
optimistic
|
|
49517
|
-
});
|
|
49518
|
-
}
|
|
49519
|
-
function AcHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
49520
|
-
if (!useClimateCapGuard(trpc, deviceId)) return null;
|
|
49521
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AcPanel, {
|
|
49522
|
-
trpc,
|
|
49523
|
-
deviceId,
|
|
49524
|
-
features,
|
|
49525
|
-
layout: "full",
|
|
49526
|
-
optimistic
|
|
49527
|
-
});
|
|
49528
|
-
}
|
|
49529
|
-
function useMediaPlayerCapGuard(trpc, deviceId) {
|
|
49530
|
-
const { cap } = useDeviceCapability({
|
|
49531
|
-
trpc,
|
|
49532
|
-
deviceId,
|
|
49533
|
-
cap: (d) => d.mediaPlayer,
|
|
49534
|
-
slice: (d) => d.state.mediaPlayer
|
|
49535
|
-
});
|
|
49536
|
-
return cap !== void 0;
|
|
49537
|
-
}
|
|
49538
|
-
function MediaPlayerInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
49539
|
-
if (!useMediaPlayerCapGuard(trpc, deviceId)) return null;
|
|
49540
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MediaPlayerPanel, {
|
|
49541
|
-
trpc,
|
|
49542
|
-
deviceId,
|
|
49543
|
-
features,
|
|
49544
|
-
layout: "compact",
|
|
49545
|
-
optimistic
|
|
49546
|
-
});
|
|
49547
|
-
}
|
|
49548
|
-
function useControlCapGuard(trpc, deviceId) {
|
|
49549
|
-
const { cap } = useDeviceCapability({
|
|
49550
|
-
trpc,
|
|
49551
|
-
deviceId,
|
|
49552
|
-
cap: (d) => d.control,
|
|
49553
|
-
slice: (d) => d.state.control
|
|
49554
|
-
});
|
|
49555
|
-
return cap !== void 0;
|
|
49556
|
-
}
|
|
49557
|
-
function ControlInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
49558
|
-
if (!useControlCapGuard(trpc, deviceId)) return null;
|
|
49559
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ControlPanel, {
|
|
49560
|
-
trpc,
|
|
49561
|
-
deviceId,
|
|
49562
|
-
features,
|
|
49563
|
-
layout: "compact",
|
|
49564
|
-
optimistic
|
|
49565
|
-
});
|
|
49566
|
-
}
|
|
49567
|
-
function ControlHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
49568
|
-
if (!useControlCapGuard(trpc, deviceId)) return null;
|
|
49569
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeroShell, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ControlPanel, {
|
|
49570
|
-
trpc,
|
|
49571
|
-
deviceId,
|
|
49572
|
-
features,
|
|
49573
|
-
layout: "full",
|
|
49574
|
-
optimistic
|
|
49575
|
-
}) });
|
|
49576
|
-
}
|
|
49577
|
-
//#endregion
|
|
49578
|
-
//#region src/composites/device-controls/control-registry.tsx
|
|
49579
|
-
/**
|
|
49580
|
-
* Presentation binding: the shared `DeviceControlKind` (decided in
|
|
49581
|
-
* `@camstack/types` `resolveDeviceControlKind`) → this platform's web
|
|
49582
|
-
* components. Exhaustive over `DeviceControlKind` — omitting a kind fails the
|
|
49583
|
-
* build here. The type→kind DECISION is single-sourced in `@camstack/types`;
|
|
49584
|
-
* this map only binds a kind to its web widgets.
|
|
49585
|
-
*/
|
|
49586
|
-
var CONTROL_KIND_COMPONENTS = {
|
|
49587
|
-
cover: {
|
|
49588
|
-
InlineControl: CoverInlineControl,
|
|
49589
|
-
HeroCard: CoverHeroCard
|
|
49590
|
-
},
|
|
49591
|
-
valve: {
|
|
49592
|
-
InlineControl: ValveInlineControl,
|
|
49593
|
-
HeroCard: ValveHeroCard
|
|
49594
|
-
},
|
|
49595
|
-
humidifier: {
|
|
49596
|
-
InlineControl: HumidifierInlineControl,
|
|
49597
|
-
HeroCard: HumidifierHeroCard
|
|
49598
|
-
},
|
|
49599
|
-
"water-heater": {
|
|
49600
|
-
InlineControl: WaterHeaterInlineControl,
|
|
49601
|
-
HeroCard: WaterHeaterHeroCard
|
|
49602
|
-
},
|
|
49603
|
-
switch: {
|
|
49604
|
-
InlineControl: SwitchInlineControl,
|
|
49605
|
-
HeroCard: SwitchHeroCard
|
|
49606
|
-
},
|
|
49607
|
-
light: {
|
|
49608
|
-
InlineControl: LightInlineControl,
|
|
49609
|
-
HeroCard: LightHeroCard
|
|
49610
|
-
},
|
|
49611
|
-
fan: {
|
|
49612
|
-
InlineControl: FanInlineControl,
|
|
49613
|
-
HeroCard: FanHeroCard
|
|
49614
|
-
},
|
|
49615
|
-
sensor: {
|
|
49616
|
-
InlineControl: SensorInlineControl,
|
|
49617
|
-
HeroCard: SensorHeroCard
|
|
49618
|
-
},
|
|
49619
|
-
thermostat: {
|
|
49620
|
-
InlineControl: ThermostatInlineControl,
|
|
49621
|
-
HeroCard: ThermostatHeroCard
|
|
49622
|
-
},
|
|
49623
|
-
climate: {
|
|
49624
|
-
InlineControl: AcInlineControl,
|
|
49625
|
-
HeroCard: AcHeroCard
|
|
49626
|
-
},
|
|
49627
|
-
button: {
|
|
49628
|
-
InlineControl: ButtonControl,
|
|
49629
|
-
HeroCard: ButtonHeroCard
|
|
49630
|
-
},
|
|
49631
|
-
update: {
|
|
49632
|
-
InlineControl: UpdateInlineControl,
|
|
49633
|
-
HeroCard: UpdateHeroCard
|
|
49634
|
-
},
|
|
49635
|
-
lock: {
|
|
49636
|
-
InlineControl: LockInlineControl,
|
|
49637
|
-
HeroCard: LockHeroCard
|
|
49638
|
-
},
|
|
49639
|
-
"media-player": {
|
|
49640
|
-
InlineControl: MediaPlayerInlineControl,
|
|
49641
|
-
HeroCard: MediaPlayerHeroCard
|
|
49642
|
-
},
|
|
49643
|
-
alarm: {
|
|
49644
|
-
InlineControl: AlarmInlineControl,
|
|
49645
|
-
HeroCard: AlarmHeroCard
|
|
49646
|
-
},
|
|
49647
|
-
control: {
|
|
49648
|
-
InlineControl: ControlInlineControl,
|
|
49649
|
-
HeroCard: ControlHeroCard
|
|
49650
|
-
},
|
|
49651
|
-
weather: {
|
|
49652
|
-
InlineControl: WeatherInlineControl,
|
|
49653
|
-
HeroCard: WeatherHeroCard
|
|
49654
|
-
},
|
|
49655
|
-
vacuum: {
|
|
49656
|
-
InlineControl: VacuumInlineControl,
|
|
49657
|
-
HeroCard: VacuumHeroCard
|
|
49658
|
-
},
|
|
49659
|
-
"lawn-mower": {
|
|
49660
|
-
InlineControl: LawnMowerInlineControl,
|
|
49661
|
-
HeroCard: LawnMowerHeroCard
|
|
49662
|
-
},
|
|
49663
|
-
image: {
|
|
49664
|
-
InlineControl: ImageInlineControl,
|
|
49665
|
-
HeroCard: ImageHeroCard
|
|
49666
|
-
},
|
|
49667
|
-
"pet-feeder": {
|
|
49668
|
-
InlineControl: PetFeederInlineControl,
|
|
49669
|
-
HeroCard: PetFeederHeroCard
|
|
49670
|
-
},
|
|
49671
|
-
script: {
|
|
49672
|
-
InlineControl: ScriptInlineControl,
|
|
49673
|
-
HeroCard: ScriptHeroCard
|
|
49674
|
-
},
|
|
49675
|
-
automation: {
|
|
49676
|
-
InlineControl: AutomationInlineControl,
|
|
49677
|
-
HeroCard: AutomationHeroCard
|
|
49678
|
-
},
|
|
49679
|
-
dummy: {
|
|
49680
|
-
InlineControl: DummyInline,
|
|
49681
|
-
HeroCard: DummyHeroCard
|
|
49682
|
-
}
|
|
49683
|
-
};
|
|
49684
|
-
/**
|
|
49685
|
-
* Exhaustive record over DeviceType, DERIVED from the shared decision
|
|
49686
|
-
* (`DEVICE_TYPE_CONTROL_KIND`) composed with the web `CONTROL_KIND_COMPONENTS`
|
|
49687
|
-
* binding — so the type→control mapping can never diverge from the single
|
|
49688
|
-
* source in `@camstack/types`. Preserved as a public export for consumers/tests
|
|
49689
|
-
* that index it by `DeviceType`.
|
|
49690
|
-
*
|
|
49691
|
-
* The `Object.fromEntries` result is a `{ [k: string]: DeviceTypeControl }`;
|
|
49692
|
-
* `Object.values(DeviceType)` (a string enum) yields exactly the enum's members,
|
|
49693
|
-
* so the record is fully populated — the assertion narrows that documented
|
|
49694
|
-
* enum-record projection boundary.
|
|
49695
|
-
*/
|
|
49696
|
-
var DEVICE_TYPE_CONTROL = Object.fromEntries(Object.values(_camstack_types.DeviceType).map((type) => [type, CONTROL_KIND_COMPONENTS[_camstack_types.DEVICE_TYPE_CONTROL_KIND[type]]]));
|
|
49697
|
-
/**
|
|
49698
|
-
* Per-type FILL flag.
|
|
49699
|
-
*
|
|
49700
|
-
* true — range / choice / button controls (cover ▲■▼ triad, thermostat
|
|
49701
|
-
* setpoint stepper, media/control/button action buttons, alarm
|
|
49702
|
-
* segmented control). The child STRETCHES to the full column width.
|
|
49703
|
-
* false — readouts, tap-toggles, icon controls (lock), and dummy/placeholder
|
|
49704
|
-
* types. The child keeps its NATURAL width; where it sits in the
|
|
49705
|
-
* column (flush right vs centered) is decided by the caller via a
|
|
49706
|
-
* `ControlPlacement` — see `resolveControlAlign`.
|
|
49707
|
-
*
|
|
49708
|
-
* Dummy types (Camera, Hub, Generic, Weather, Vacuum, Container, and placeholder
|
|
49709
|
-
* types Notifier/Script/Automation whose InlineControl renders "—") are `false` —
|
|
49710
|
-
* the dash placeholder must not be stretched to the full column width. (AlarmPanel
|
|
49711
|
-
* has a real segmented inline control and is `true`.) When a later task gives a
|
|
49712
|
-
* remaining placeholder type a real stretchy inline control, flip its flag to true.
|
|
49713
|
-
*
|
|
49714
|
-
* Exhaustive over DeviceType — a missing member is a TypeScript build error.
|
|
49715
|
-
*/
|
|
49716
|
-
var CONTROL_FILLS = {
|
|
49717
|
-
[_camstack_types.DeviceType.Cover]: true,
|
|
49718
|
-
[_camstack_types.DeviceType.Valve]: true,
|
|
49719
|
-
[_camstack_types.DeviceType.Thermostat]: true,
|
|
49720
|
-
[_camstack_types.DeviceType.Climate]: true,
|
|
49721
|
-
[_camstack_types.DeviceType.Control]: true,
|
|
49722
|
-
[_camstack_types.DeviceType.Button]: true,
|
|
49723
|
-
[_camstack_types.DeviceType.MediaPlayer]: true,
|
|
49724
|
-
[_camstack_types.DeviceType.AlarmPanel]: true,
|
|
49725
|
-
[_camstack_types.DeviceType.Lock]: false,
|
|
49726
|
-
[_camstack_types.DeviceType.Vacuum]: false,
|
|
49727
|
-
[_camstack_types.DeviceType.LawnMower]: false,
|
|
49728
|
-
[_camstack_types.DeviceType.Humidifier]: false,
|
|
49729
|
-
[_camstack_types.DeviceType.Switch]: false,
|
|
49730
|
-
[_camstack_types.DeviceType.Siren]: false,
|
|
49731
|
-
[_camstack_types.DeviceType.Light]: false,
|
|
49732
|
-
[_camstack_types.DeviceType.Fan]: false,
|
|
49733
|
-
[_camstack_types.DeviceType.WaterHeater]: false,
|
|
49734
|
-
[_camstack_types.DeviceType.Sensor]: false,
|
|
49735
|
-
[_camstack_types.DeviceType.Presence]: false,
|
|
49736
|
-
[_camstack_types.DeviceType.EventEmitter]: false,
|
|
49737
|
-
[_camstack_types.DeviceType.Notifier]: false,
|
|
49738
|
-
[_camstack_types.DeviceType.Script]: false,
|
|
49739
|
-
[_camstack_types.DeviceType.Automation]: false,
|
|
49740
|
-
[_camstack_types.DeviceType.Camera]: false,
|
|
49741
|
-
[_camstack_types.DeviceType.Hub]: false,
|
|
49742
|
-
[_camstack_types.DeviceType.Update]: false,
|
|
49743
|
-
[_camstack_types.DeviceType.Generic]: false,
|
|
49744
|
-
[_camstack_types.DeviceType.Weather]: false,
|
|
49745
|
-
[_camstack_types.DeviceType.Container]: false,
|
|
49746
|
-
[_camstack_types.DeviceType.Image]: false,
|
|
49747
|
-
[_camstack_types.DeviceType.PetFeeder]: false
|
|
49748
|
-
};
|
|
49749
|
-
var DEVICE_TYPE_VALUES$1 = new Set(Object.values(_camstack_types.DeviceType));
|
|
49750
|
-
/**
|
|
49751
|
-
* Runtime-string-safe lookup: a device row's `type` crosses the wire as a plain
|
|
49752
|
-
* string. The type→control-kind DECISION is single-sourced in `@camstack/types`
|
|
49753
|
-
* (`resolveDeviceControlKind`, null for an unrecognised type); this maps the
|
|
49754
|
-
* resolved kind onto the web components.
|
|
49755
|
-
*/
|
|
49756
|
-
function resolveDeviceControl(type) {
|
|
49757
|
-
const kind = (0, _camstack_types.resolveDeviceControlKind)(type);
|
|
49758
|
-
return kind ? CONTROL_KIND_COMPONENTS[kind] : null;
|
|
49759
|
-
}
|
|
49760
|
-
/**
|
|
49761
|
-
* Runtime-string-safe alignment lookup for the fixed-width control column.
|
|
49762
|
-
*
|
|
49763
|
-
* A device's `type` crosses the wire as a plain string;
|
|
49764
|
-
* `DEVICE_TYPE_VALUES.has(type)` narrows it to `DeviceType` before indexing
|
|
49765
|
-
* `CONTROL_FILLS`, making the cast sound. The `'table'` placement is
|
|
49766
|
-
* content-width for EVERY type (returns `FIT`), so a wide control never
|
|
49767
|
-
* overflows a fixed box — the FILL flag is irrelevant there. For the fixed-box
|
|
49768
|
-
* placements a FILL type always stretches (`FILL`) and a natural-width type is
|
|
49769
|
-
* placed by the caller's `placement` — `'center'` → `CENTER`, `'right'` →
|
|
49770
|
-
* `RIGHT` (card / minimal view). An unrecognised type falls back to
|
|
49771
|
-
* natural-width placement.
|
|
49772
|
-
*/
|
|
49773
|
-
function resolveControlAlign(type, placement) {
|
|
49774
|
-
if (placement === "table") return "fit";
|
|
49775
|
-
return DEVICE_TYPE_VALUES$1.has(type) && CONTROL_FILLS[type] ? FILL : placement === "center" ? CENTER : RIGHT;
|
|
49776
|
-
}
|
|
49777
|
-
//#endregion
|
|
49778
|
-
//#region src/lib/format-last-seen.ts
|
|
49779
|
-
/**
|
|
49780
|
-
* Relative "last seen" formatter for the offline affordance.
|
|
49781
|
-
*
|
|
49782
|
-
* Renders a short relative-time label from a millisecond epoch:
|
|
49783
|
-
* "just now", "5m ago", "3h ago", "2d ago". Returns `null` for missing
|
|
49784
|
-
* inputs, or inputs below {@link MIN_PLAUSIBLE_TIMESTAMP_MS} (a
|
|
49785
|
-
* placeholder or a seeded "known value, unknown change time" stamp), so
|
|
49786
|
-
* callers can render nothing rather than a misleading "—", "NaN ago", or
|
|
49787
|
-
* (for a seeded `1`) "20000 days ago".
|
|
49788
|
-
*
|
|
49789
|
-
* Future timestamps (clock skew between the device and the browser)
|
|
49790
|
-
* collapse to "just now" instead of a negative duration.
|
|
49791
|
-
*/
|
|
49792
|
-
var SECOND_MS = 1e3;
|
|
49793
|
-
var MINUTE_MS = 60 * SECOND_MS;
|
|
49794
|
-
var HOUR_MS = 60 * MINUTE_MS;
|
|
49795
|
-
var DAY_MS = 24 * HOUR_MS;
|
|
49796
|
-
var JUST_NOW_MS = 30 * SECOND_MS;
|
|
49797
|
-
/**
|
|
49798
|
-
* The earliest timestamp a real "last seen" could plausibly carry. Below
|
|
49799
|
-
* this, a value is a placeholder or a seeded "known value, unknown change
|
|
49800
|
-
* time" stamp (e.g. Homematic's `HM_SEEDED_CHANGED_AT = 1`) — never a real
|
|
49801
|
-
* instant. Year 2000 predates every device this renders.
|
|
49802
|
-
*/
|
|
49803
|
-
var MIN_PLAUSIBLE_TIMESTAMP_MS = Date.UTC(2e3, 0, 1);
|
|
49804
|
-
function formatLastSeen(timestampMs) {
|
|
49805
|
-
if (timestampMs === null || timestampMs === void 0 || timestampMs < MIN_PLAUSIBLE_TIMESTAMP_MS) return null;
|
|
49806
|
-
const delta = Date.now() - timestampMs;
|
|
49807
|
-
if (delta < JUST_NOW_MS) return "just now";
|
|
49808
|
-
if (delta < HOUR_MS) return `${String(Math.floor(delta / MINUTE_MS))}m ago`;
|
|
49809
|
-
if (delta < DAY_MS) return `${String(Math.floor(delta / HOUR_MS))}h ago`;
|
|
49810
|
-
return `${String(Math.floor(delta / DAY_MS))}d ago`;
|
|
49811
|
-
}
|
|
49812
|
-
//#endregion
|
|
49813
|
-
//#region src/composites/device-controls/offline-badge.tsx
|
|
49814
|
-
function OfflineBadge({ lastChangedAt, className }) {
|
|
49815
|
-
const lastSeen = formatLastSeen(lastChangedAt);
|
|
49816
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
49817
|
-
title: lastSeen === null ? "Unavailable" : `Unavailable · last seen ${lastSeen}`,
|
|
49818
|
-
className: cn("inline-flex items-center gap-1 rounded-full px-2 py-0.5", "bg-surface-hover text-foreground-subtle text-[10px] font-medium", "border border-border", className),
|
|
49819
|
-
children: [
|
|
49820
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WifiOff, {
|
|
49821
|
-
className: "h-3 w-3 flex-shrink-0",
|
|
49822
|
-
"aria-hidden": "true"
|
|
49823
|
-
}),
|
|
49824
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Unavailable" }),
|
|
49825
|
-
lastSeen !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
49826
|
-
className: "text-foreground-subtle/70",
|
|
49827
|
-
children: `· last seen ${lastSeen}`
|
|
49828
|
-
}) : null
|
|
49829
|
-
]
|
|
49830
|
-
});
|
|
49831
|
-
}
|
|
49832
|
-
//#endregion
|
|
49833
|
-
//#region src/composites/device-controls/primary-child.ts
|
|
49834
|
-
/**
|
|
49835
|
-
* Which child a container is rendered AS — this surface's name for the shared
|
|
49836
|
-
* rule.
|
|
49837
|
-
*
|
|
49838
|
-
* The list and the resolution used to live here, in a COPY that carried a
|
|
49839
|
-
* comment asking the Home Assistant adoption mapping and the viewer's panel to
|
|
49840
|
-
* stay in sync with it. Three lists and three comments is not one rule, it is
|
|
49841
|
-
* three chances to drift. Both now come from `@camstack/types`, which all of
|
|
49842
|
-
* them already depend on; what remains here is the adapter for this module's
|
|
49843
|
-
* historical `ChildRef` shape.
|
|
49844
|
-
*/
|
|
49845
|
-
/** The shared precedence, under this module's historical name. */
|
|
49846
|
-
var PRIORITY = _camstack_types.CONTAINER_CHILD_PRIORITY;
|
|
49847
|
-
/**
|
|
49848
|
-
* Resolve a container's primary child: the operator's pick when it still
|
|
49849
|
-
* matches a live child, else the highest-priority type.
|
|
49850
|
-
*
|
|
49851
|
-
* `entityId` here is `sourceInfo?.id ?? stableId` (see `containerChildToRef`).
|
|
49852
|
-
* The shared resolver matches its canonical `stableId` key first and accepts
|
|
49853
|
-
* that legacy value too, so a pick stored under either keeps resolving.
|
|
49854
|
-
*/
|
|
49855
|
-
function resolvePrimaryChild(children, overrideEntityId, containerName) {
|
|
49856
|
-
const picked = (0, _camstack_types.resolveContainerPrimaryChild)(children.map((c) => ({
|
|
49857
|
-
id: c.id,
|
|
49858
|
-
stableId: c.entityId,
|
|
49859
|
-
type: c.type,
|
|
49860
|
-
...c.role !== void 0 ? { role: c.role } : {},
|
|
49861
|
-
...c.name !== void 0 ? { name: c.name } : {}
|
|
49862
|
-
})), overrideEntityId, containerName);
|
|
49863
|
-
return picked === null ? null : children.find((c) => c.id === picked.id) ?? null;
|
|
49864
|
-
}
|
|
49865
|
-
//#endregion
|
|
49866
|
-
//#region src/composites/device-controls/container-primary.ts
|
|
49867
|
-
/**
|
|
49868
|
-
* Re-sync-stable identity of a child. Prefers the upstream-system dispatch key
|
|
49869
|
-
* (`sourceInfo.id` — e.g. HA `entity_id`); falls back to `stableId` for
|
|
49870
|
-
* providers that never populated SourceInfo. Both survive a re-sync that may
|
|
49871
|
-
* reallocate the numeric `id`.
|
|
49872
|
-
*/
|
|
49873
|
-
function childEntityId(child) {
|
|
49874
|
-
return child.sourceInfo?.id ?? child.stableId;
|
|
49875
|
-
}
|
|
49876
|
-
/** Project a raw child row onto the pure resolver's {@link ChildRef} shape. */
|
|
49877
|
-
function containerChildToRef(child) {
|
|
49878
|
-
return {
|
|
49879
|
-
id: child.id,
|
|
49880
|
-
entityId: childEntityId(child),
|
|
49881
|
-
type: child.type,
|
|
49882
|
-
...child.role !== void 0 && child.role !== null ? { role: child.role } : {},
|
|
49883
|
-
...child.name !== void 0 ? { name: child.name } : {}
|
|
49884
|
-
};
|
|
49885
|
-
}
|
|
49886
|
-
/**
|
|
49887
|
-
* Resolve the entityId override from the container's persisted `linkDeviceId`.
|
|
49888
|
-
* Returns the linked child's stable entityId, or `undefined` when there is no
|
|
49889
|
-
* link or the link is stale (points to a numeric id no longer present) — in
|
|
49890
|
-
* which case the resolver falls back to the priority default.
|
|
49891
|
-
*/
|
|
49892
|
-
function overrideEntityIdFromLink(children, linkDeviceId) {
|
|
49893
|
-
if (linkDeviceId === null || linkDeviceId === void 0) return void 0;
|
|
49894
|
-
const linked = children.find((c) => c.id === linkDeviceId);
|
|
49895
|
-
return linked === void 0 ? void 0 : childEntityId(linked);
|
|
49896
|
-
}
|
|
49897
|
-
/**
|
|
49898
|
-
* Full container → primary-child resolution: project children to refs, resolve
|
|
49899
|
-
* the override entityId, and delegate to the pure resolver. Returns `null` for a
|
|
49900
|
-
* container with no children (caller degrades gracefully).
|
|
49901
|
-
*
|
|
49902
|
-
* Override precedence:
|
|
49903
|
-
* 1. `primaryChildEntityId` — the DURABLE override (re-sync/rename-stable),
|
|
49904
|
-
* passed straight to the resolver which matches on `entityId`. Wins when a
|
|
49905
|
-
* non-empty string is supplied.
|
|
49906
|
-
* 2. `linkDeviceId` — LEGACY numeric override, re-derived to an entityId from
|
|
49907
|
-
* the live child set. Consulted only when there is no durable override.
|
|
49908
|
-
*
|
|
49909
|
-
* When neither resolves to a live child, the resolver falls back to the
|
|
49910
|
-
* priority default.
|
|
49911
|
-
*/
|
|
49912
|
-
function resolveContainerPrimary(children, linkDeviceId, primaryChildEntityId) {
|
|
49913
|
-
return resolvePrimaryChild(children.map(containerChildToRef), primaryChildEntityId !== null && primaryChildEntityId !== void 0 && primaryChildEntityId !== "" ? primaryChildEntityId : overrideEntityIdFromLink(children, linkDeviceId));
|
|
49914
|
-
}
|
|
49915
|
-
//#endregion
|
|
49916
|
-
//#region src/composites/device-controls/container-primary-hero.tsx
|
|
49917
|
-
function ContainerPrimaryHero({ trpc, children, linkDeviceId, primaryChildEntityId }) {
|
|
49918
|
-
const primary = resolveContainerPrimary(children, linkDeviceId, primaryChildEntityId);
|
|
49919
|
-
const primaryChild = primary === null ? void 0 : children.find((c) => c.id === primary.id);
|
|
49920
|
-
if (primaryChild === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49921
|
-
className: "flex h-full w-full items-center justify-center p-4 text-[11px] italic text-foreground-subtle",
|
|
49922
|
-
children: "No child entities to display."
|
|
50706
|
+
function useLockCapGuard(trpc, deviceId) {
|
|
50707
|
+
const { cap } = useDeviceCapability({
|
|
50708
|
+
trpc,
|
|
50709
|
+
deviceId,
|
|
50710
|
+
cap: (d) => d.lockControl,
|
|
50711
|
+
slice: (d) => d.state.lockControl
|
|
49923
50712
|
});
|
|
49924
|
-
return
|
|
49925
|
-
|
|
49926
|
-
|
|
50713
|
+
return cap !== void 0;
|
|
50714
|
+
}
|
|
50715
|
+
function LockInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
50716
|
+
if (!useLockCapGuard(trpc, deviceId)) return null;
|
|
50717
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LockPanel, {
|
|
50718
|
+
trpc,
|
|
50719
|
+
deviceId,
|
|
50720
|
+
features,
|
|
50721
|
+
layout: "compact",
|
|
50722
|
+
optimistic
|
|
50723
|
+
});
|
|
50724
|
+
}
|
|
50725
|
+
function LightInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
50726
|
+
if (!useSwitchCapGuard(trpc, deviceId)) return null;
|
|
50727
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50728
|
+
className: "inline-flex items-center gap-2",
|
|
50729
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchPanel, {
|
|
49927
50730
|
trpc,
|
|
49928
|
-
deviceId
|
|
49929
|
-
features
|
|
49930
|
-
|
|
50731
|
+
deviceId,
|
|
50732
|
+
features,
|
|
50733
|
+
layout: "compact",
|
|
50734
|
+
optimistic
|
|
50735
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BrightnessPanel, {
|
|
50736
|
+
trpc,
|
|
50737
|
+
deviceId,
|
|
50738
|
+
features,
|
|
50739
|
+
layout: "compact",
|
|
50740
|
+
optimistic
|
|
50741
|
+
})]
|
|
49931
50742
|
});
|
|
49932
50743
|
}
|
|
49933
|
-
|
|
49934
|
-
|
|
49935
|
-
|
|
49936
|
-
|
|
49937
|
-
|
|
49938
|
-
|
|
49939
|
-
disabled,
|
|
49940
|
-
onChange: (e) => {
|
|
49941
|
-
const nextId = Number(e.target.value);
|
|
49942
|
-
if (!Number.isFinite(nextId) || nextId === selectedId) return;
|
|
49943
|
-
const picked = childrenList.find((c) => c.id === nextId);
|
|
49944
|
-
if (picked !== void 0) onSetPrimary(picked.stableId);
|
|
49945
|
-
},
|
|
49946
|
-
className: className === void 0 ? BASE_SELECT_CLASS : `${BASE_SELECT_CLASS} ${className}`,
|
|
49947
|
-
children: childrenList.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
49948
|
-
value: c.id,
|
|
49949
|
-
children: [
|
|
49950
|
-
c.name,
|
|
49951
|
-
" (",
|
|
49952
|
-
c.type,
|
|
49953
|
-
")"
|
|
49954
|
-
]
|
|
49955
|
-
}, c.id))
|
|
50744
|
+
function useFanCapGuard(trpc, deviceId) {
|
|
50745
|
+
const { cap } = useDeviceCapability({
|
|
50746
|
+
trpc,
|
|
50747
|
+
deviceId,
|
|
50748
|
+
cap: (d) => d.fanControl,
|
|
50749
|
+
slice: (d) => d.state.fanControl
|
|
49956
50750
|
});
|
|
50751
|
+
return cap !== void 0;
|
|
49957
50752
|
}
|
|
49958
|
-
|
|
49959
|
-
|
|
49960
|
-
function DeviceBatchToolbar({ selectedIds, onClear, remove, disable, enable }) {
|
|
49961
|
-
const [pendingDelete, setPendingDelete] = (0, react$1.useState)(false);
|
|
49962
|
-
if (selectedIds.size === 0) return null;
|
|
49963
|
-
const ids = [...selectedIds];
|
|
49964
|
-
const count = ids.length;
|
|
49965
|
-
const handleDisable = () => {
|
|
49966
|
-
for (const id of ids) disable({ deviceId: id });
|
|
49967
|
-
onClear();
|
|
49968
|
-
};
|
|
49969
|
-
const handleEnable = () => {
|
|
49970
|
-
for (const id of ids) enable({ deviceId: id });
|
|
49971
|
-
onClear();
|
|
49972
|
-
};
|
|
49973
|
-
const handleDeleteFirst = () => {
|
|
49974
|
-
setPendingDelete(true);
|
|
49975
|
-
};
|
|
49976
|
-
const handleDeleteConfirm = () => {
|
|
49977
|
-
for (const id of ids) remove({ deviceId: id });
|
|
49978
|
-
setPendingDelete(false);
|
|
49979
|
-
onClear();
|
|
49980
|
-
};
|
|
49981
|
-
const handleDeleteCancel = () => {
|
|
49982
|
-
setPendingDelete(false);
|
|
49983
|
-
};
|
|
49984
|
-
const handleClear = () => {
|
|
49985
|
-
setPendingDelete(false);
|
|
49986
|
-
onClear();
|
|
49987
|
-
};
|
|
50753
|
+
function FanInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
50754
|
+
if (!useFanCapGuard(trpc, deviceId)) return null;
|
|
49988
50755
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49989
|
-
className: "flex items-center gap-2
|
|
49990
|
-
children: [
|
|
49991
|
-
|
|
49992
|
-
|
|
49993
|
-
|
|
49994
|
-
|
|
49995
|
-
|
|
49996
|
-
|
|
49997
|
-
|
|
49998
|
-
|
|
49999
|
-
|
|
50000
|
-
|
|
50001
|
-
|
|
50002
|
-
|
|
50003
|
-
type: "button",
|
|
50004
|
-
onClick: handleDisable,
|
|
50005
|
-
className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
|
|
50006
|
-
children: "Disable"
|
|
50007
|
-
}),
|
|
50008
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
50009
|
-
type: "button",
|
|
50010
|
-
onClick: handleEnable,
|
|
50011
|
-
className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
|
|
50012
|
-
children: "Enable"
|
|
50013
|
-
}),
|
|
50014
|
-
pendingDelete ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
50015
|
-
type: "button",
|
|
50016
|
-
onClick: handleDeleteConfirm,
|
|
50017
|
-
className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-red-500/50 bg-red-500/10 text-red-400 hover:bg-red-500/20"),
|
|
50018
|
-
children: [
|
|
50019
|
-
"Confirm delete ",
|
|
50020
|
-
count,
|
|
50021
|
-
"?"
|
|
50022
|
-
]
|
|
50023
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
50024
|
-
type: "button",
|
|
50025
|
-
onClick: handleDeleteCancel,
|
|
50026
|
-
className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
|
|
50027
|
-
children: "Cancel"
|
|
50028
|
-
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
50029
|
-
type: "button",
|
|
50030
|
-
onClick: handleDeleteFirst,
|
|
50031
|
-
className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-red-500/40 bg-red-500/10 text-red-400 hover:bg-red-500/20"),
|
|
50032
|
-
children: "Delete"
|
|
50033
|
-
}),
|
|
50034
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
50035
|
-
type: "button",
|
|
50036
|
-
onClick: handleClear,
|
|
50037
|
-
className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
|
|
50038
|
-
children: "Clear"
|
|
50039
|
-
})
|
|
50040
|
-
]
|
|
50041
|
-
})
|
|
50042
|
-
]
|
|
50756
|
+
className: "inline-flex items-center gap-2",
|
|
50757
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchPanel, {
|
|
50758
|
+
trpc,
|
|
50759
|
+
deviceId,
|
|
50760
|
+
features,
|
|
50761
|
+
layout: "compact",
|
|
50762
|
+
optimistic
|
|
50763
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FanPanel, {
|
|
50764
|
+
trpc,
|
|
50765
|
+
deviceId,
|
|
50766
|
+
features,
|
|
50767
|
+
layout: "compact",
|
|
50768
|
+
optimistic
|
|
50769
|
+
})]
|
|
50043
50770
|
});
|
|
50044
50771
|
}
|
|
50045
|
-
|
|
50046
|
-
|
|
50047
|
-
|
|
50048
|
-
|
|
50049
|
-
|
|
50050
|
-
|
|
50051
|
-
|
|
50052
|
-
|
|
50053
|
-
* hosts the SAME <DeviceBatchToolbar>, so the action logic (inline
|
|
50054
|
-
* delete-confirm, clear) is reused verbatim — never forked.
|
|
50055
|
-
*
|
|
50056
|
-
* Renders nothing while the selection is empty (delegated to the toolbar).
|
|
50057
|
-
*/
|
|
50058
|
-
function DeviceBatchActionsBar(props) {
|
|
50059
|
-
const [open, setOpen] = (0, react$1.useState)(false);
|
|
50060
|
-
const rootRef = (0, react$1.useRef)(null);
|
|
50061
|
-
const count = props.selectedIds.size;
|
|
50062
|
-
(0, react$1.useEffect)(() => {
|
|
50063
|
-
if (!open) return;
|
|
50064
|
-
const onOutside = (e) => {
|
|
50065
|
-
const target = e.target instanceof Node ? e.target : null;
|
|
50066
|
-
if (target && rootRef.current && !rootRef.current.contains(target)) setOpen(false);
|
|
50067
|
-
};
|
|
50068
|
-
const onKey = (e) => {
|
|
50069
|
-
if (e.key === "Escape") setOpen(false);
|
|
50070
|
-
};
|
|
50071
|
-
document.addEventListener("mousedown", onOutside);
|
|
50072
|
-
document.addEventListener("touchstart", onOutside);
|
|
50073
|
-
document.addEventListener("keydown", onKey);
|
|
50074
|
-
return () => {
|
|
50075
|
-
document.removeEventListener("mousedown", onOutside);
|
|
50076
|
-
document.removeEventListener("touchstart", onOutside);
|
|
50077
|
-
document.removeEventListener("keydown", onKey);
|
|
50078
|
-
};
|
|
50079
|
-
}, [open]);
|
|
50080
|
-
if (count === 0) return null;
|
|
50081
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
50082
|
-
className: "hidden sm:block",
|
|
50083
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceBatchToolbar, { ...props })
|
|
50084
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50085
|
-
ref: rootRef,
|
|
50086
|
-
"data-testid": "batch-actions-compact",
|
|
50087
|
-
className: "relative sm:hidden",
|
|
50088
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
50089
|
-
type: "button",
|
|
50090
|
-
onClick: () => setOpen((v) => !v),
|
|
50091
|
-
"aria-haspopup": "menu",
|
|
50092
|
-
"aria-expanded": open,
|
|
50093
|
-
className: "inline-flex items-center gap-1.5 h-7 px-2.5 rounded-full border border-primary/40 bg-primary/15 text-[10.5px] font-medium text-primary",
|
|
50094
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["Actions · ", String(count)] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: `h-3 w-3 transition-transform ${open ? "rotate-180" : ""}` })]
|
|
50095
|
-
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
50096
|
-
role: "menu",
|
|
50097
|
-
className: "absolute left-0 top-full mt-1 z-30 rounded-lg border border-border bg-surface shadow-md",
|
|
50098
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceBatchToolbar, { ...props })
|
|
50099
|
-
})]
|
|
50100
|
-
})] });
|
|
50772
|
+
function useClimateCapGuard(trpc, deviceId) {
|
|
50773
|
+
const { cap } = useDeviceCapability({
|
|
50774
|
+
trpc,
|
|
50775
|
+
deviceId,
|
|
50776
|
+
cap: (d) => d.climateControl,
|
|
50777
|
+
slice: (d) => d.state.climateControl
|
|
50778
|
+
});
|
|
50779
|
+
return cap !== void 0;
|
|
50101
50780
|
}
|
|
50102
|
-
|
|
50103
|
-
|
|
50104
|
-
|
|
50105
|
-
|
|
50106
|
-
|
|
50107
|
-
|
|
50108
|
-
|
|
50109
|
-
|
|
50110
|
-
|
|
50111
|
-
|
|
50112
|
-
|
|
50113
|
-
|
|
50114
|
-
|
|
50115
|
-
|
|
50116
|
-
|
|
50117
|
-
|
|
50118
|
-
|
|
50119
|
-
|
|
50120
|
-
}
|
|
50121
|
-
|
|
50122
|
-
|
|
50123
|
-
|
|
50124
|
-
|
|
50125
|
-
|
|
50126
|
-
|
|
50127
|
-
|
|
50128
|
-
|
|
50129
|
-
|
|
50130
|
-
|
|
50131
|
-
|
|
50132
|
-
|
|
50133
|
-
|
|
50134
|
-
|
|
50135
|
-
|
|
50136
|
-
|
|
50137
|
-
|
|
50138
|
-
|
|
50139
|
-
|
|
50140
|
-
|
|
50141
|
-
|
|
50142
|
-
|
|
50143
|
-
|
|
50144
|
-
|
|
50145
|
-
|
|
50146
|
-
|
|
50147
|
-
|
|
50148
|
-
|
|
50149
|
-
|
|
50150
|
-
|
|
50151
|
-
|
|
50152
|
-
}
|
|
50153
|
-
|
|
50154
|
-
|
|
50155
|
-
|
|
50156
|
-
|
|
50157
|
-
|
|
50158
|
-
|
|
50159
|
-
|
|
50160
|
-
|
|
50161
|
-
|
|
50162
|
-
|
|
50163
|
-
|
|
50164
|
-
|
|
50165
|
-
|
|
50166
|
-
|
|
50167
|
-
|
|
50168
|
-
}
|
|
50169
|
-
|
|
50170
|
-
|
|
50171
|
-
|
|
50172
|
-
|
|
50173
|
-
|
|
50174
|
-
|
|
50175
|
-
|
|
50176
|
-
|
|
50177
|
-
|
|
50178
|
-
|
|
50179
|
-
|
|
50180
|
-
|
|
50181
|
-
|
|
50182
|
-
|
|
50183
|
-
|
|
50184
|
-
|
|
50185
|
-
|
|
50186
|
-
|
|
50187
|
-
|
|
50188
|
-
|
|
50189
|
-
|
|
50190
|
-
|
|
50191
|
-
|
|
50192
|
-
|
|
50193
|
-
[_camstack_types.DeviceType.AlarmPanel]: {
|
|
50194
|
-
icon: ShieldAlert,
|
|
50195
|
-
label: "Alarm panel"
|
|
50196
|
-
},
|
|
50197
|
-
[_camstack_types.DeviceType.Control]: {
|
|
50198
|
-
icon: SlidersHorizontal,
|
|
50199
|
-
label: "Control"
|
|
50200
|
-
},
|
|
50201
|
-
[_camstack_types.DeviceType.Presence]: {
|
|
50202
|
-
icon: MapPin,
|
|
50203
|
-
label: "Presence"
|
|
50204
|
-
},
|
|
50205
|
-
[_camstack_types.DeviceType.Weather]: {
|
|
50206
|
-
icon: CloudSun,
|
|
50207
|
-
label: "Weather"
|
|
50208
|
-
},
|
|
50209
|
-
[_camstack_types.DeviceType.Vacuum]: {
|
|
50210
|
-
icon: Bot,
|
|
50211
|
-
label: "Vacuum"
|
|
50212
|
-
},
|
|
50213
|
-
[_camstack_types.DeviceType.LawnMower]: {
|
|
50214
|
-
icon: Tractor,
|
|
50215
|
-
label: "Lawn mower"
|
|
50216
|
-
},
|
|
50217
|
-
[_camstack_types.DeviceType.Container]: {
|
|
50218
|
-
icon: Box,
|
|
50219
|
-
label: "Container"
|
|
50220
|
-
},
|
|
50221
|
-
[_camstack_types.DeviceType.Image]: {
|
|
50222
|
-
icon: Image,
|
|
50223
|
-
label: "Image"
|
|
50224
|
-
},
|
|
50225
|
-
[_camstack_types.DeviceType.PetFeeder]: {
|
|
50226
|
-
icon: Bone,
|
|
50227
|
-
label: "Pet feeder"
|
|
50228
|
-
}
|
|
50229
|
-
};
|
|
50230
|
-
var DEVICE_ROLE_META = {
|
|
50231
|
-
[_camstack_types.DeviceRole.Siren]: {
|
|
50232
|
-
icon: Siren,
|
|
50233
|
-
label: "Siren"
|
|
50234
|
-
},
|
|
50235
|
-
[_camstack_types.DeviceRole.Floodlight]: {
|
|
50236
|
-
icon: Lightbulb,
|
|
50237
|
-
label: "Floodlight"
|
|
50238
|
-
},
|
|
50239
|
-
[_camstack_types.DeviceRole.Spotlight]: {
|
|
50240
|
-
icon: Flashlight,
|
|
50241
|
-
label: "Spotlight"
|
|
50242
|
-
},
|
|
50243
|
-
[_camstack_types.DeviceRole.PirSensor]: {
|
|
50244
|
-
icon: Radar,
|
|
50245
|
-
label: "PIR sensor"
|
|
50246
|
-
},
|
|
50247
|
-
[_camstack_types.DeviceRole.Chime]: {
|
|
50248
|
-
icon: Bell,
|
|
50249
|
-
label: "Chime"
|
|
50250
|
-
},
|
|
50251
|
-
[_camstack_types.DeviceRole.Autotrack]: {
|
|
50252
|
-
icon: Crosshair,
|
|
50253
|
-
label: "Auto-track"
|
|
50254
|
-
},
|
|
50255
|
-
[_camstack_types.DeviceRole.Nightvision]: {
|
|
50256
|
-
icon: Moon,
|
|
50257
|
-
label: "Night vision"
|
|
50258
|
-
},
|
|
50259
|
-
[_camstack_types.DeviceRole.PrivacyMask]: {
|
|
50260
|
-
icon: EyeOff,
|
|
50261
|
-
label: "Privacy mask"
|
|
50262
|
-
},
|
|
50263
|
-
[_camstack_types.DeviceRole.Doorbell]: {
|
|
50264
|
-
icon: DoorClosed,
|
|
50265
|
-
label: "Doorbell"
|
|
50266
|
-
},
|
|
50267
|
-
[_camstack_types.DeviceRole.BinaryHelper]: {
|
|
50268
|
-
icon: ToggleLeft,
|
|
50269
|
-
label: "Binary helper"
|
|
50270
|
-
},
|
|
50271
|
-
[_camstack_types.DeviceRole.MotionSensor]: {
|
|
50272
|
-
icon: Footprints,
|
|
50273
|
-
label: "Motion sensor"
|
|
50274
|
-
},
|
|
50275
|
-
[_camstack_types.DeviceRole.ContactSensor]: {
|
|
50276
|
-
icon: DoorOpen,
|
|
50277
|
-
label: "Contact sensor"
|
|
50278
|
-
},
|
|
50279
|
-
[_camstack_types.DeviceRole.LeakSensor]: {
|
|
50280
|
-
icon: Droplet,
|
|
50281
|
-
label: "Leak sensor"
|
|
50282
|
-
},
|
|
50283
|
-
[_camstack_types.DeviceRole.SmokeSensor]: {
|
|
50284
|
-
icon: AlarmSmoke,
|
|
50285
|
-
label: "Smoke sensor"
|
|
50286
|
-
},
|
|
50287
|
-
[_camstack_types.DeviceRole.COSensor]: {
|
|
50288
|
-
icon: Wind,
|
|
50289
|
-
label: "CO sensor"
|
|
50290
|
-
},
|
|
50291
|
-
[_camstack_types.DeviceRole.GasSensor]: {
|
|
50292
|
-
icon: Flame,
|
|
50293
|
-
label: "Gas sensor"
|
|
50294
|
-
},
|
|
50295
|
-
[_camstack_types.DeviceRole.TamperSensor]: {
|
|
50296
|
-
icon: ShieldAlert,
|
|
50297
|
-
label: "Tamper sensor"
|
|
50298
|
-
},
|
|
50299
|
-
[_camstack_types.DeviceRole.VibrationSensor]: {
|
|
50300
|
-
icon: Vibrate,
|
|
50301
|
-
label: "Vibration sensor"
|
|
50302
|
-
},
|
|
50303
|
-
[_camstack_types.DeviceRole.ConnectivitySensor]: {
|
|
50304
|
-
icon: Wifi,
|
|
50305
|
-
label: "Connectivity"
|
|
50306
|
-
},
|
|
50307
|
-
[_camstack_types.DeviceRole.SoundSensor]: {
|
|
50308
|
-
icon: Volume2,
|
|
50309
|
-
label: "Sound sensor"
|
|
50310
|
-
},
|
|
50311
|
-
[_camstack_types.DeviceRole.BinarySensor]: {
|
|
50312
|
-
icon: ToggleLeft,
|
|
50313
|
-
label: "Binary sensor"
|
|
50314
|
-
},
|
|
50315
|
-
[_camstack_types.DeviceRole.TemperatureSensor]: {
|
|
50316
|
-
icon: Thermometer,
|
|
50317
|
-
label: "Temperature"
|
|
50318
|
-
},
|
|
50319
|
-
[_camstack_types.DeviceRole.HumiditySensor]: {
|
|
50320
|
-
icon: Droplets,
|
|
50321
|
-
label: "Humidity"
|
|
50322
|
-
},
|
|
50323
|
-
[_camstack_types.DeviceRole.AmbientLightSensor]: {
|
|
50324
|
-
icon: Sun,
|
|
50325
|
-
label: "Ambient light"
|
|
50326
|
-
},
|
|
50327
|
-
[_camstack_types.DeviceRole.PressureSensor]: {
|
|
50328
|
-
icon: Gauge,
|
|
50329
|
-
label: "Pressure"
|
|
50781
|
+
function ThermostatInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
50782
|
+
if (!useClimateCapGuard(trpc, deviceId)) return null;
|
|
50783
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClimatePanel, {
|
|
50784
|
+
trpc,
|
|
50785
|
+
deviceId,
|
|
50786
|
+
features,
|
|
50787
|
+
layout: "compact",
|
|
50788
|
+
optimistic
|
|
50789
|
+
});
|
|
50790
|
+
}
|
|
50791
|
+
function AcInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
50792
|
+
if (!useClimateCapGuard(trpc, deviceId)) return null;
|
|
50793
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AcPanel, {
|
|
50794
|
+
trpc,
|
|
50795
|
+
deviceId,
|
|
50796
|
+
features,
|
|
50797
|
+
layout: "compact",
|
|
50798
|
+
optimistic
|
|
50799
|
+
});
|
|
50800
|
+
}
|
|
50801
|
+
function AcHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
50802
|
+
if (!useClimateCapGuard(trpc, deviceId)) return null;
|
|
50803
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AcPanel, {
|
|
50804
|
+
trpc,
|
|
50805
|
+
deviceId,
|
|
50806
|
+
features,
|
|
50807
|
+
layout: "full",
|
|
50808
|
+
optimistic
|
|
50809
|
+
});
|
|
50810
|
+
}
|
|
50811
|
+
function useMediaPlayerCapGuard(trpc, deviceId) {
|
|
50812
|
+
const { cap } = useDeviceCapability({
|
|
50813
|
+
trpc,
|
|
50814
|
+
deviceId,
|
|
50815
|
+
cap: (d) => d.mediaPlayer,
|
|
50816
|
+
slice: (d) => d.state.mediaPlayer
|
|
50817
|
+
});
|
|
50818
|
+
return cap !== void 0;
|
|
50819
|
+
}
|
|
50820
|
+
function MediaPlayerInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
50821
|
+
if (!useMediaPlayerCapGuard(trpc, deviceId)) return null;
|
|
50822
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MediaPlayerPanel, {
|
|
50823
|
+
trpc,
|
|
50824
|
+
deviceId,
|
|
50825
|
+
features,
|
|
50826
|
+
layout: "compact",
|
|
50827
|
+
optimistic
|
|
50828
|
+
});
|
|
50829
|
+
}
|
|
50830
|
+
function useControlCapGuard(trpc, deviceId) {
|
|
50831
|
+
const { cap } = useDeviceCapability({
|
|
50832
|
+
trpc,
|
|
50833
|
+
deviceId,
|
|
50834
|
+
cap: (d) => d.control,
|
|
50835
|
+
slice: (d) => d.state.control
|
|
50836
|
+
});
|
|
50837
|
+
return cap !== void 0;
|
|
50838
|
+
}
|
|
50839
|
+
function ControlInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
50840
|
+
if (!useControlCapGuard(trpc, deviceId)) return null;
|
|
50841
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ControlPanel, {
|
|
50842
|
+
trpc,
|
|
50843
|
+
deviceId,
|
|
50844
|
+
features,
|
|
50845
|
+
layout: "compact",
|
|
50846
|
+
optimistic
|
|
50847
|
+
});
|
|
50848
|
+
}
|
|
50849
|
+
function ControlHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
50850
|
+
if (!useControlCapGuard(trpc, deviceId)) return null;
|
|
50851
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeroShell, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ControlPanel, {
|
|
50852
|
+
trpc,
|
|
50853
|
+
deviceId,
|
|
50854
|
+
features,
|
|
50855
|
+
layout: "full",
|
|
50856
|
+
optimistic
|
|
50857
|
+
}) });
|
|
50858
|
+
}
|
|
50859
|
+
//#endregion
|
|
50860
|
+
//#region src/composites/device-controls/control-registry.tsx
|
|
50861
|
+
/**
|
|
50862
|
+
* Presentation binding: the shared `DeviceControlKind` (decided in
|
|
50863
|
+
* `@camstack/types` `resolveDeviceControlKind`) → this platform's web
|
|
50864
|
+
* components. Exhaustive over `DeviceControlKind` — omitting a kind fails the
|
|
50865
|
+
* build here. The type→kind DECISION is single-sourced in `@camstack/types`;
|
|
50866
|
+
* this map only binds a kind to its web widgets.
|
|
50867
|
+
*/
|
|
50868
|
+
var CONTROL_KIND_COMPONENTS = {
|
|
50869
|
+
cover: {
|
|
50870
|
+
InlineControl: CoverInlineControl,
|
|
50871
|
+
HeroCard: CoverHeroCard
|
|
50330
50872
|
},
|
|
50331
|
-
|
|
50332
|
-
|
|
50333
|
-
|
|
50873
|
+
valve: {
|
|
50874
|
+
InlineControl: ValveInlineControl,
|
|
50875
|
+
HeroCard: ValveHeroCard
|
|
50334
50876
|
},
|
|
50335
|
-
|
|
50336
|
-
|
|
50337
|
-
|
|
50877
|
+
humidifier: {
|
|
50878
|
+
InlineControl: HumidifierInlineControl,
|
|
50879
|
+
HeroCard: HumidifierHeroCard
|
|
50338
50880
|
},
|
|
50339
|
-
|
|
50340
|
-
|
|
50341
|
-
|
|
50881
|
+
"water-heater": {
|
|
50882
|
+
InlineControl: WaterHeaterInlineControl,
|
|
50883
|
+
HeroCard: WaterHeaterHeroCard
|
|
50342
50884
|
},
|
|
50343
|
-
|
|
50344
|
-
|
|
50345
|
-
|
|
50885
|
+
switch: {
|
|
50886
|
+
InlineControl: SwitchInlineControl,
|
|
50887
|
+
HeroCard: SwitchHeroCard
|
|
50346
50888
|
},
|
|
50347
|
-
|
|
50348
|
-
|
|
50349
|
-
|
|
50889
|
+
light: {
|
|
50890
|
+
InlineControl: LightInlineControl,
|
|
50891
|
+
HeroCard: LightHeroCard
|
|
50350
50892
|
},
|
|
50351
|
-
|
|
50352
|
-
|
|
50353
|
-
|
|
50893
|
+
fan: {
|
|
50894
|
+
InlineControl: FanInlineControl,
|
|
50895
|
+
HeroCard: FanHeroCard
|
|
50354
50896
|
},
|
|
50355
|
-
|
|
50356
|
-
|
|
50357
|
-
|
|
50897
|
+
sensor: {
|
|
50898
|
+
InlineControl: SensorInlineControl,
|
|
50899
|
+
HeroCard: SensorHeroCard
|
|
50358
50900
|
},
|
|
50359
|
-
|
|
50360
|
-
|
|
50361
|
-
|
|
50901
|
+
thermostat: {
|
|
50902
|
+
InlineControl: ThermostatInlineControl,
|
|
50903
|
+
HeroCard: ThermostatHeroCard
|
|
50362
50904
|
},
|
|
50363
|
-
|
|
50364
|
-
|
|
50365
|
-
|
|
50905
|
+
climate: {
|
|
50906
|
+
InlineControl: AcInlineControl,
|
|
50907
|
+
HeroCard: AcHeroCard
|
|
50366
50908
|
},
|
|
50367
|
-
|
|
50368
|
-
|
|
50369
|
-
|
|
50909
|
+
button: {
|
|
50910
|
+
InlineControl: ButtonControl,
|
|
50911
|
+
HeroCard: ButtonHeroCard
|
|
50370
50912
|
},
|
|
50371
|
-
|
|
50372
|
-
|
|
50373
|
-
|
|
50913
|
+
update: {
|
|
50914
|
+
InlineControl: UpdateInlineControl,
|
|
50915
|
+
HeroCard: UpdateHeroCard
|
|
50374
50916
|
},
|
|
50375
|
-
|
|
50376
|
-
|
|
50377
|
-
|
|
50917
|
+
lock: {
|
|
50918
|
+
InlineControl: LockInlineControl,
|
|
50919
|
+
HeroCard: LockHeroCard
|
|
50378
50920
|
},
|
|
50379
|
-
|
|
50380
|
-
|
|
50381
|
-
|
|
50921
|
+
"media-player": {
|
|
50922
|
+
InlineControl: MediaPlayerInlineControl,
|
|
50923
|
+
HeroCard: MediaPlayerHeroCard
|
|
50382
50924
|
},
|
|
50383
|
-
|
|
50384
|
-
|
|
50385
|
-
|
|
50925
|
+
alarm: {
|
|
50926
|
+
InlineControl: AlarmInlineControl,
|
|
50927
|
+
HeroCard: AlarmHeroCard
|
|
50386
50928
|
},
|
|
50387
|
-
|
|
50388
|
-
|
|
50389
|
-
|
|
50929
|
+
control: {
|
|
50930
|
+
InlineControl: ControlInlineControl,
|
|
50931
|
+
HeroCard: ControlHeroCard
|
|
50390
50932
|
},
|
|
50391
|
-
|
|
50392
|
-
|
|
50393
|
-
|
|
50933
|
+
weather: {
|
|
50934
|
+
InlineControl: WeatherInlineControl,
|
|
50935
|
+
HeroCard: WeatherHeroCard
|
|
50394
50936
|
},
|
|
50395
|
-
|
|
50396
|
-
|
|
50397
|
-
|
|
50937
|
+
vacuum: {
|
|
50938
|
+
InlineControl: VacuumInlineControl,
|
|
50939
|
+
HeroCard: VacuumHeroCard
|
|
50398
50940
|
},
|
|
50399
|
-
|
|
50400
|
-
|
|
50401
|
-
|
|
50941
|
+
"lawn-mower": {
|
|
50942
|
+
InlineControl: LawnMowerInlineControl,
|
|
50943
|
+
HeroCard: LawnMowerHeroCard
|
|
50402
50944
|
},
|
|
50403
|
-
|
|
50404
|
-
|
|
50405
|
-
|
|
50945
|
+
image: {
|
|
50946
|
+
InlineControl: ImageInlineControl,
|
|
50947
|
+
HeroCard: ImageHeroCard
|
|
50406
50948
|
},
|
|
50407
|
-
|
|
50408
|
-
|
|
50409
|
-
|
|
50949
|
+
"pet-feeder": {
|
|
50950
|
+
InlineControl: PetFeederInlineControl,
|
|
50951
|
+
HeroCard: PetFeederHeroCard
|
|
50410
50952
|
},
|
|
50411
|
-
|
|
50412
|
-
|
|
50413
|
-
|
|
50953
|
+
script: {
|
|
50954
|
+
InlineControl: ScriptInlineControl,
|
|
50955
|
+
HeroCard: ScriptHeroCard
|
|
50414
50956
|
},
|
|
50415
|
-
|
|
50416
|
-
|
|
50417
|
-
|
|
50957
|
+
automation: {
|
|
50958
|
+
InlineControl: AutomationInlineControl,
|
|
50959
|
+
HeroCard: AutomationHeroCard
|
|
50418
50960
|
},
|
|
50419
|
-
|
|
50420
|
-
|
|
50421
|
-
|
|
50961
|
+
dummy: {
|
|
50962
|
+
InlineControl: DummyInline,
|
|
50963
|
+
HeroCard: DummyHeroCard
|
|
50422
50964
|
}
|
|
50423
50965
|
};
|
|
50966
|
+
/**
|
|
50967
|
+
* Exhaustive record over DeviceType, DERIVED from the shared decision
|
|
50968
|
+
* (`DEVICE_TYPE_CONTROL_KIND`) composed with the web `CONTROL_KIND_COMPONENTS`
|
|
50969
|
+
* binding — so the type→control mapping can never diverge from the single
|
|
50970
|
+
* source in `@camstack/types`. Preserved as a public export for consumers/tests
|
|
50971
|
+
* that index it by `DeviceType`.
|
|
50972
|
+
*
|
|
50973
|
+
* The `Object.fromEntries` result is a `{ [k: string]: DeviceTypeControl }`;
|
|
50974
|
+
* `Object.values(DeviceType)` (a string enum) yields exactly the enum's members,
|
|
50975
|
+
* so the record is fully populated — the assertion narrows that documented
|
|
50976
|
+
* enum-record projection boundary.
|
|
50977
|
+
*/
|
|
50978
|
+
var DEVICE_TYPE_CONTROL = Object.fromEntries(Object.values(_camstack_types.DeviceType).map((type) => [type, CONTROL_KIND_COMPONENTS[_camstack_types.DEVICE_TYPE_CONTROL_KIND[type]]]));
|
|
50979
|
+
/**
|
|
50980
|
+
* Per-type FILL flag.
|
|
50981
|
+
*
|
|
50982
|
+
* true — range / choice / button controls (cover ▲■▼ triad, thermostat
|
|
50983
|
+
* setpoint stepper, media/control/button action buttons, alarm
|
|
50984
|
+
* segmented control). The child STRETCHES to the full column width.
|
|
50985
|
+
* false — readouts, tap-toggles, icon controls (lock), and dummy/placeholder
|
|
50986
|
+
* types. The child keeps its NATURAL width; where it sits in the
|
|
50987
|
+
* column (flush right vs centered) is decided by the caller via a
|
|
50988
|
+
* `ControlPlacement` — see `resolveControlAlign`.
|
|
50989
|
+
*
|
|
50990
|
+
* Dummy types (Camera, Hub, Generic, Weather, Vacuum, Container, and placeholder
|
|
50991
|
+
* types Notifier/Script/Automation whose InlineControl renders "—") are `false` —
|
|
50992
|
+
* the dash placeholder must not be stretched to the full column width. (AlarmPanel
|
|
50993
|
+
* has a real segmented inline control and is `true`.) When a later task gives a
|
|
50994
|
+
* remaining placeholder type a real stretchy inline control, flip its flag to true.
|
|
50995
|
+
*
|
|
50996
|
+
* Exhaustive over DeviceType — a missing member is a TypeScript build error.
|
|
50997
|
+
*/
|
|
50998
|
+
var CONTROL_FILLS = {
|
|
50999
|
+
[_camstack_types.DeviceType.Cover]: true,
|
|
51000
|
+
[_camstack_types.DeviceType.Valve]: true,
|
|
51001
|
+
[_camstack_types.DeviceType.Thermostat]: true,
|
|
51002
|
+
[_camstack_types.DeviceType.Climate]: true,
|
|
51003
|
+
[_camstack_types.DeviceType.Control]: true,
|
|
51004
|
+
[_camstack_types.DeviceType.Button]: true,
|
|
51005
|
+
[_camstack_types.DeviceType.MediaPlayer]: true,
|
|
51006
|
+
[_camstack_types.DeviceType.AlarmPanel]: true,
|
|
51007
|
+
[_camstack_types.DeviceType.Lock]: false,
|
|
51008
|
+
[_camstack_types.DeviceType.Vacuum]: false,
|
|
51009
|
+
[_camstack_types.DeviceType.LawnMower]: false,
|
|
51010
|
+
[_camstack_types.DeviceType.Humidifier]: false,
|
|
51011
|
+
[_camstack_types.DeviceType.Switch]: false,
|
|
51012
|
+
[_camstack_types.DeviceType.Siren]: false,
|
|
51013
|
+
[_camstack_types.DeviceType.Light]: false,
|
|
51014
|
+
[_camstack_types.DeviceType.Fan]: false,
|
|
51015
|
+
[_camstack_types.DeviceType.WaterHeater]: false,
|
|
51016
|
+
[_camstack_types.DeviceType.Sensor]: false,
|
|
51017
|
+
[_camstack_types.DeviceType.Presence]: false,
|
|
51018
|
+
[_camstack_types.DeviceType.EventEmitter]: false,
|
|
51019
|
+
[_camstack_types.DeviceType.Notifier]: false,
|
|
51020
|
+
[_camstack_types.DeviceType.Script]: false,
|
|
51021
|
+
[_camstack_types.DeviceType.Automation]: false,
|
|
51022
|
+
[_camstack_types.DeviceType.Camera]: false,
|
|
51023
|
+
[_camstack_types.DeviceType.Hub]: false,
|
|
51024
|
+
[_camstack_types.DeviceType.Update]: false,
|
|
51025
|
+
[_camstack_types.DeviceType.Generic]: false,
|
|
51026
|
+
[_camstack_types.DeviceType.Weather]: false,
|
|
51027
|
+
[_camstack_types.DeviceType.Container]: false,
|
|
51028
|
+
[_camstack_types.DeviceType.Image]: false,
|
|
51029
|
+
[_camstack_types.DeviceType.PetFeeder]: false
|
|
51030
|
+
};
|
|
50424
51031
|
var DEVICE_TYPE_VALUES = new Set(Object.values(_camstack_types.DeviceType));
|
|
50425
|
-
|
|
50426
|
-
|
|
50427
|
-
|
|
50428
|
-
|
|
50429
|
-
|
|
50430
|
-
|
|
50431
|
-
|
|
50432
|
-
|
|
50433
|
-
|
|
50434
|
-
return DEVICE_TYPE_META[type];
|
|
50435
|
-
}
|
|
50436
|
-
/** Meta for a known `DeviceRole`. */
|
|
50437
|
-
function deviceRoleMeta(role) {
|
|
50438
|
-
return DEVICE_ROLE_META[role];
|
|
51032
|
+
/**
|
|
51033
|
+
* Runtime-string-safe lookup: a device row's `type` crosses the wire as a plain
|
|
51034
|
+
* string. The type→control-kind DECISION is single-sourced in `@camstack/types`
|
|
51035
|
+
* (`resolveDeviceControlKind`, null for an unrecognised type); this maps the
|
|
51036
|
+
* resolved kind onto the web components.
|
|
51037
|
+
*/
|
|
51038
|
+
function resolveDeviceControl(type) {
|
|
51039
|
+
const kind = (0, _camstack_types.resolveDeviceControlKind)(type);
|
|
51040
|
+
return kind ? CONTROL_KIND_COMPONENTS[kind] : null;
|
|
50439
51041
|
}
|
|
50440
|
-
var FALLBACK_DEVICE_TYPE_META = DEVICE_TYPE_META[_camstack_types.DeviceType.Generic];
|
|
50441
|
-
var FALLBACK_DEVICE_ROLE_META = DEVICE_ROLE_META[_camstack_types.DeviceRole.GenericSensor];
|
|
50442
51042
|
/**
|
|
50443
|
-
* Runtime-string-safe lookup
|
|
50444
|
-
* as plain strings, so an unrecognised value falls back to the `Generic` meta
|
|
50445
|
-
* instead of throwing.
|
|
51043
|
+
* Runtime-string-safe alignment lookup for the fixed-width control column.
|
|
50446
51044
|
*
|
|
50447
|
-
*
|
|
50448
|
-
*
|
|
50449
|
-
*
|
|
51045
|
+
* A device's `type` crosses the wire as a plain string;
|
|
51046
|
+
* `DEVICE_TYPE_VALUES.has(type)` narrows it to `DeviceType` before indexing
|
|
51047
|
+
* `CONTROL_FILLS`, making the cast sound. The `'table'` placement is
|
|
51048
|
+
* content-width for EVERY type (returns `FIT`), so a wide control never
|
|
51049
|
+
* overflows a fixed box — the FILL flag is irrelevant there. For the fixed-box
|
|
51050
|
+
* placements a FILL type always stretches (`FILL`) and a natural-width type is
|
|
51051
|
+
* placed by the caller's `placement` — `'center'` → `CENTER`, `'right'` →
|
|
51052
|
+
* `RIGHT` (card / minimal view). An unrecognised type falls back to
|
|
51053
|
+
* natural-width placement.
|
|
50450
51054
|
*/
|
|
50451
|
-
function
|
|
50452
|
-
if (
|
|
50453
|
-
return
|
|
50454
|
-
}
|
|
50455
|
-
/** Runtime-string-safe role lookup; unknown roles fall back to `GenericSensor`. */
|
|
50456
|
-
function deviceRoleMetaOf(role) {
|
|
50457
|
-
if (!isDeviceRole(role)) return FALLBACK_DEVICE_ROLE_META;
|
|
50458
|
-
return DEVICE_ROLE_META[role] ?? FALLBACK_DEVICE_ROLE_META;
|
|
51055
|
+
function resolveControlAlign(type, placement) {
|
|
51056
|
+
if (placement === "table") return "fit";
|
|
51057
|
+
return DEVICE_TYPE_VALUES.has(type) && CONTROL_FILLS[type] ? FILL : placement === "center" ? CENTER : RIGHT;
|
|
50459
51058
|
}
|
|
51059
|
+
//#endregion
|
|
51060
|
+
//#region src/lib/format-last-seen.ts
|
|
50460
51061
|
/**
|
|
50461
|
-
*
|
|
50462
|
-
* a per-role default), keyed by a stable kebab-case name. Named imports only —
|
|
50463
|
-
* no dynamic indexing into lucide, so the bundle carries exactly this set.
|
|
50464
|
-
* Unknown keys resolve to `undefined` (caller falls through to the next level).
|
|
51062
|
+
* Relative "last seen" formatter for the offline affordance.
|
|
50465
51063
|
*
|
|
50466
|
-
*
|
|
50467
|
-
*
|
|
50468
|
-
*
|
|
50469
|
-
*
|
|
50470
|
-
*
|
|
50471
|
-
*
|
|
51064
|
+
* Renders a short relative-time label from a millisecond epoch:
|
|
51065
|
+
* "just now", "5m ago", "3h ago", "2d ago". Returns `null` for missing
|
|
51066
|
+
* inputs, or inputs below {@link MIN_PLAUSIBLE_TIMESTAMP_MS} (a
|
|
51067
|
+
* placeholder or a seeded "known value, unknown change time" stamp), so
|
|
51068
|
+
* callers can render nothing rather than a misleading "—", "NaN ago", or
|
|
51069
|
+
* (for a seeded `1`) "20000 days ago".
|
|
50472
51070
|
*
|
|
50473
|
-
*
|
|
50474
|
-
*
|
|
50475
|
-
* is a legacy alias kept for rows that persisted it.
|
|
51071
|
+
* Future timestamps (clock skew between the device and the browser)
|
|
51072
|
+
* collapse to "just now" instead of a negative duration.
|
|
50476
51073
|
*/
|
|
50477
|
-
var
|
|
50478
|
-
|
|
50479
|
-
|
|
50480
|
-
|
|
50481
|
-
|
|
50482
|
-
|
|
50483
|
-
|
|
50484
|
-
|
|
50485
|
-
|
|
50486
|
-
|
|
50487
|
-
|
|
50488
|
-
|
|
50489
|
-
|
|
50490
|
-
|
|
50491
|
-
|
|
50492
|
-
"
|
|
50493
|
-
|
|
50494
|
-
|
|
50495
|
-
|
|
50496
|
-
|
|
50497
|
-
|
|
50498
|
-
|
|
50499
|
-
|
|
50500
|
-
|
|
50501
|
-
|
|
50502
|
-
|
|
50503
|
-
|
|
50504
|
-
|
|
50505
|
-
|
|
50506
|
-
|
|
50507
|
-
|
|
50508
|
-
|
|
50509
|
-
|
|
50510
|
-
|
|
50511
|
-
|
|
50512
|
-
|
|
50513
|
-
|
|
50514
|
-
|
|
50515
|
-
|
|
50516
|
-
cpu: Cpu,
|
|
50517
|
-
"hard-drive": HardDrive,
|
|
50518
|
-
server: Server,
|
|
50519
|
-
mic: Mic,
|
|
50520
|
-
"mic-off": MicOff,
|
|
50521
|
-
video: Video,
|
|
50522
|
-
"video-off": VideoOff,
|
|
50523
|
-
music: Music,
|
|
50524
|
-
plug: Plug,
|
|
50525
|
-
"plug-zap": PlugZap,
|
|
50526
|
-
power: Power,
|
|
50527
|
-
"power-off": PowerOff,
|
|
50528
|
-
cable: Cable,
|
|
50529
|
-
battery: Battery,
|
|
50530
|
-
"battery-charging": BatteryCharging,
|
|
50531
|
-
"battery-full": BatteryFull,
|
|
50532
|
-
"battery-low": BatteryLow,
|
|
50533
|
-
key: Key,
|
|
50534
|
-
"key-round": KeyRound,
|
|
50535
|
-
mailbox: Mailbox,
|
|
50536
|
-
package: Package,
|
|
50537
|
-
shield: Shield,
|
|
50538
|
-
"shield-check": ShieldCheck,
|
|
50539
|
-
"shield-off": ShieldOff,
|
|
50540
|
-
lightbulb: Lightbulb,
|
|
50541
|
-
lamp: Lamp,
|
|
50542
|
-
"lamp-ceiling": LampCeiling,
|
|
50543
|
-
"lamp-desk": LampDesk,
|
|
50544
|
-
"lamp-floor": LampFloor,
|
|
50545
|
-
flashlight: Flashlight,
|
|
50546
|
-
heater: Heater,
|
|
50547
|
-
snowflake: Snowflake,
|
|
50548
|
-
"thermometer-sun": ThermometerSun,
|
|
50549
|
-
"thermometer-snowflake": ThermometerSnowflake,
|
|
50550
|
-
refrigerator: Refrigerator,
|
|
50551
|
-
microwave: Microwave,
|
|
50552
|
-
"washing-machine": WashingMachine,
|
|
50553
|
-
"cooking-pot": CookingPot,
|
|
50554
|
-
utensils: Utensils,
|
|
50555
|
-
thermometer: Thermometer,
|
|
50556
|
-
droplets: Droplets,
|
|
50557
|
-
droplet: Droplet,
|
|
50558
|
-
gauge: Gauge,
|
|
50559
|
-
gauge2: Gauge,
|
|
50560
|
-
sun: Sun,
|
|
50561
|
-
"sun-medium": SunMedium,
|
|
50562
|
-
sunrise: Sunrise,
|
|
50563
|
-
moon: Moon,
|
|
50564
|
-
wind: Wind,
|
|
50565
|
-
cloud: Cloud,
|
|
50566
|
-
"cloud-rain": CloudRain,
|
|
50567
|
-
"cloud-sun": CloudSun,
|
|
50568
|
-
waves: Waves,
|
|
50569
|
-
sprout: Sprout,
|
|
50570
|
-
leaf: Leaf,
|
|
50571
|
-
flower: Flower,
|
|
50572
|
-
zap: Zap,
|
|
50573
|
-
flame: Flame,
|
|
50574
|
-
bell: Bell,
|
|
50575
|
-
"bell-ring": BellRing,
|
|
50576
|
-
"bell-off": BellOff,
|
|
50577
|
-
siren: Siren,
|
|
50578
|
-
radar: Radar,
|
|
50579
|
-
activity: Activity,
|
|
50580
|
-
hash: Hash,
|
|
50581
|
-
clock: Clock,
|
|
50582
|
-
timer: Timer,
|
|
50583
|
-
calendar: Calendar,
|
|
50584
|
-
pipette: Pipette,
|
|
50585
|
-
fan: Fan,
|
|
50586
|
-
bath: Bath,
|
|
50587
|
-
footprints: Footprints,
|
|
50588
|
-
"alarm-smoke": AlarmSmoke,
|
|
50589
|
-
"shield-alert": ShieldAlert,
|
|
50590
|
-
vibrate: Vibrate,
|
|
50591
|
-
wifi: Wifi,
|
|
50592
|
-
"wifi-off": WifiOff,
|
|
50593
|
-
"volume-2": Volume2,
|
|
50594
|
-
"volume-x": VolumeX,
|
|
50595
|
-
heart: Heart,
|
|
50596
|
-
"heart-pulse": HeartPulse,
|
|
50597
|
-
star: Star,
|
|
50598
|
-
sparkles: Sparkles,
|
|
50599
|
-
house: House,
|
|
50600
|
-
sofa: Sofa,
|
|
50601
|
-
"bed-double": BedDouble,
|
|
50602
|
-
bed: Bed,
|
|
50603
|
-
warehouse: Warehouse,
|
|
50604
|
-
trees: Trees,
|
|
50605
|
-
"tree-pine": TreePine,
|
|
50606
|
-
fence: Fence,
|
|
50607
|
-
store: Store,
|
|
50608
|
-
building: Building,
|
|
50609
|
-
"building-2": Building2,
|
|
50610
|
-
mountain: Mountain,
|
|
50611
|
-
umbrella: Umbrella,
|
|
50612
|
-
dumbbell: Dumbbell,
|
|
50613
|
-
wrench: Wrench,
|
|
50614
|
-
hammer: Hammer,
|
|
50615
|
-
drill: Drill,
|
|
50616
|
-
"waves-ladder": WavesLadder,
|
|
50617
|
-
"circle-parking": CircleParking,
|
|
50618
|
-
briefcase: Briefcase,
|
|
50619
|
-
route: Route,
|
|
50620
|
-
car: Car,
|
|
50621
|
-
bike: Bike,
|
|
50622
|
-
bus: Bus,
|
|
50623
|
-
truck: Truck,
|
|
50624
|
-
plane: Plane,
|
|
50625
|
-
"train-front": TrainFront,
|
|
50626
|
-
sailboat: Sailboat,
|
|
50627
|
-
dog: Dog,
|
|
50628
|
-
cat: Cat,
|
|
50629
|
-
bird: Bird,
|
|
50630
|
-
"paw-print": PawPrint,
|
|
50631
|
-
baby: Baby,
|
|
50632
|
-
user: User,
|
|
50633
|
-
users: Users,
|
|
50634
|
-
"person-standing": PersonStanding
|
|
50635
|
-
};
|
|
50636
|
-
/** Resolve a `display.icon` name to a lucide component, or `undefined` when the
|
|
50637
|
-
* name is not in the curated `DISPLAY_ICON_REGISTRY`. */
|
|
50638
|
-
function resolveDisplayIcon(name) {
|
|
50639
|
-
return Object.hasOwn(DISPLAY_ICON_REGISTRY, name) ? DISPLAY_ICON_REGISTRY[name] : void 0;
|
|
51074
|
+
var SECOND_MS = 1e3;
|
|
51075
|
+
var MINUTE_MS = 60 * SECOND_MS;
|
|
51076
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
51077
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
51078
|
+
var JUST_NOW_MS = 30 * SECOND_MS;
|
|
51079
|
+
/**
|
|
51080
|
+
* The earliest timestamp a real "last seen" could plausibly carry. Below
|
|
51081
|
+
* this, a value is a placeholder or a seeded "known value, unknown change
|
|
51082
|
+
* time" stamp (e.g. Homematic's `HM_SEEDED_CHANGED_AT = 1`) — never a real
|
|
51083
|
+
* instant. Year 2000 predates every device this renders.
|
|
51084
|
+
*/
|
|
51085
|
+
var MIN_PLAUSIBLE_TIMESTAMP_MS = Date.UTC(2e3, 0, 1);
|
|
51086
|
+
function formatLastSeen(timestampMs) {
|
|
51087
|
+
if (timestampMs === null || timestampMs === void 0 || timestampMs < MIN_PLAUSIBLE_TIMESTAMP_MS) return null;
|
|
51088
|
+
const delta = Date.now() - timestampMs;
|
|
51089
|
+
if (delta < JUST_NOW_MS) return "just now";
|
|
51090
|
+
if (delta < HOUR_MS) return `${String(Math.floor(delta / MINUTE_MS))}m ago`;
|
|
51091
|
+
if (delta < DAY_MS) return `${String(Math.floor(delta / HOUR_MS))}h ago`;
|
|
51092
|
+
return `${String(Math.floor(delta / DAY_MS))}d ago`;
|
|
51093
|
+
}
|
|
51094
|
+
//#endregion
|
|
51095
|
+
//#region src/composites/device-controls/offline-badge.tsx
|
|
51096
|
+
function OfflineBadge({ lastChangedAt, className }) {
|
|
51097
|
+
const lastSeen = formatLastSeen(lastChangedAt);
|
|
51098
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
51099
|
+
title: lastSeen === null ? "Unavailable" : `Unavailable · last seen ${lastSeen}`,
|
|
51100
|
+
className: cn("inline-flex items-center gap-1 rounded-full px-2 py-0.5", "bg-surface-hover text-foreground-subtle text-[10px] font-medium", "border border-border", className),
|
|
51101
|
+
children: [
|
|
51102
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WifiOff, {
|
|
51103
|
+
className: "h-3 w-3 flex-shrink-0",
|
|
51104
|
+
"aria-hidden": "true"
|
|
51105
|
+
}),
|
|
51106
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Unavailable" }),
|
|
51107
|
+
lastSeen !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
51108
|
+
className: "text-foreground-subtle/70",
|
|
51109
|
+
children: `· last seen ${lastSeen}`
|
|
51110
|
+
}) : null
|
|
51111
|
+
]
|
|
51112
|
+
});
|
|
50640
51113
|
}
|
|
51114
|
+
//#endregion
|
|
51115
|
+
//#region src/composites/device-controls/primary-child.ts
|
|
50641
51116
|
/**
|
|
50642
|
-
*
|
|
50643
|
-
*
|
|
50644
|
-
* added `DeviceType` automatically appears in every type filter (Devices page,
|
|
50645
|
-
* adoption modal, …) without touching any consumer.
|
|
51117
|
+
* Which child a container is rendered AS — this surface's name for the shared
|
|
51118
|
+
* rule.
|
|
50646
51119
|
*
|
|
50647
|
-
* The
|
|
50648
|
-
*
|
|
50649
|
-
*
|
|
50650
|
-
*
|
|
51120
|
+
* The list and the resolution used to live here, in a COPY that carried a
|
|
51121
|
+
* comment asking the Home Assistant adoption mapping and the viewer's panel to
|
|
51122
|
+
* stay in sync with it. Three lists and three comments is not one rule, it is
|
|
51123
|
+
* three chances to drift. Both now come from `@camstack/types`, which all of
|
|
51124
|
+
* them already depend on; what remains here is the adapter for this module's
|
|
51125
|
+
* historical `ChildRef` shape.
|
|
51126
|
+
*/
|
|
51127
|
+
/** The shared precedence, under this module's historical name. */
|
|
51128
|
+
var PRIORITY = _camstack_types.CONTAINER_CHILD_PRIORITY;
|
|
51129
|
+
/**
|
|
51130
|
+
* Resolve a container's primary child: the operator's pick when it still
|
|
51131
|
+
* matches a live child, else the highest-priority type.
|
|
50651
51132
|
*
|
|
50652
|
-
* `
|
|
50653
|
-
*
|
|
50654
|
-
*
|
|
51133
|
+
* `entityId` here is `sourceInfo?.id ?? stableId` (see `containerChildToRef`).
|
|
51134
|
+
* The shared resolver matches its canonical `stableId` key first and accepts
|
|
51135
|
+
* that legacy value too, so a pick stored under either keeps resolving.
|
|
50655
51136
|
*/
|
|
50656
|
-
|
|
50657
|
-
const
|
|
51137
|
+
function resolvePrimaryChild(children, overrideEntityId, containerName) {
|
|
51138
|
+
const picked = (0, _camstack_types.resolveContainerPrimaryChild)(children.map((c) => ({
|
|
51139
|
+
id: c.id,
|
|
51140
|
+
stableId: c.entityId,
|
|
51141
|
+
type: c.type,
|
|
51142
|
+
...c.role !== void 0 ? { role: c.role } : {},
|
|
51143
|
+
...c.name !== void 0 ? { name: c.name } : {}
|
|
51144
|
+
})), overrideEntityId, containerName);
|
|
51145
|
+
return picked === null ? null : children.find((c) => c.id === picked.id) ?? null;
|
|
51146
|
+
}
|
|
51147
|
+
//#endregion
|
|
51148
|
+
//#region src/composites/device-controls/container-primary.ts
|
|
51149
|
+
/**
|
|
51150
|
+
* Re-sync-stable identity of a child. Prefers the upstream-system dispatch key
|
|
51151
|
+
* (`sourceInfo.id` — e.g. HA `entity_id`); falls back to `stableId` for
|
|
51152
|
+
* providers that never populated SourceInfo. Both survive a re-sync that may
|
|
51153
|
+
* reallocate the numeric `id`.
|
|
51154
|
+
*/
|
|
51155
|
+
function childEntityId(child) {
|
|
51156
|
+
return child.sourceInfo?.id ?? child.stableId;
|
|
51157
|
+
}
|
|
51158
|
+
/** Project a raw child row onto the pure resolver's {@link ChildRef} shape. */
|
|
51159
|
+
function containerChildToRef(child) {
|
|
50658
51160
|
return {
|
|
50659
|
-
id:
|
|
50660
|
-
|
|
50661
|
-
|
|
51161
|
+
id: child.id,
|
|
51162
|
+
entityId: childEntityId(child),
|
|
51163
|
+
type: child.type,
|
|
51164
|
+
...child.role !== void 0 && child.role !== null ? { role: child.role } : {},
|
|
51165
|
+
...child.name !== void 0 ? { name: child.name } : {}
|
|
50662
51166
|
};
|
|
50663
|
-
}
|
|
50664
|
-
|
|
50665
|
-
|
|
51167
|
+
}
|
|
51168
|
+
/**
|
|
51169
|
+
* Resolve the entityId override from the container's persisted `linkDeviceId`.
|
|
51170
|
+
* Returns the linked child's stable entityId, or `undefined` when there is no
|
|
51171
|
+
* link or the link is stale (points to a numeric id no longer present) — in
|
|
51172
|
+
* which case the resolver falls back to the priority default.
|
|
51173
|
+
*/
|
|
51174
|
+
function overrideEntityIdFromLink(children, linkDeviceId) {
|
|
51175
|
+
if (linkDeviceId === null || linkDeviceId === void 0) return void 0;
|
|
51176
|
+
const linked = children.find((c) => c.id === linkDeviceId);
|
|
51177
|
+
return linked === void 0 ? void 0 : childEntityId(linked);
|
|
51178
|
+
}
|
|
51179
|
+
/**
|
|
51180
|
+
* Full container → primary-child resolution: project children to refs, resolve
|
|
51181
|
+
* the override entityId, and delegate to the pure resolver. Returns `null` for a
|
|
51182
|
+
* container with no children (caller degrades gracefully).
|
|
51183
|
+
*
|
|
51184
|
+
* Override precedence:
|
|
51185
|
+
* 1. `primaryChildEntityId` — the DURABLE override (re-sync/rename-stable),
|
|
51186
|
+
* passed straight to the resolver which matches on `entityId`. Wins when a
|
|
51187
|
+
* non-empty string is supplied.
|
|
51188
|
+
* 2. `linkDeviceId` — LEGACY numeric override, re-derived to an entityId from
|
|
51189
|
+
* the live child set. Consulted only when there is no durable override.
|
|
51190
|
+
*
|
|
51191
|
+
* When neither resolves to a live child, the resolver falls back to the
|
|
51192
|
+
* priority default.
|
|
51193
|
+
*/
|
|
51194
|
+
function resolveContainerPrimary(children, linkDeviceId, primaryChildEntityId) {
|
|
51195
|
+
return resolvePrimaryChild(children.map(containerChildToRef), primaryChildEntityId !== null && primaryChildEntityId !== void 0 && primaryChildEntityId !== "" ? primaryChildEntityId : overrideEntityIdFromLink(children, linkDeviceId));
|
|
51196
|
+
}
|
|
51197
|
+
//#endregion
|
|
51198
|
+
//#region src/composites/device-controls/container-primary-hero.tsx
|
|
51199
|
+
function ContainerPrimaryHero({ trpc, children, linkDeviceId, primaryChildEntityId }) {
|
|
51200
|
+
const primary = resolveContainerPrimary(children, linkDeviceId, primaryChildEntityId);
|
|
51201
|
+
const primaryChild = primary === null ? void 0 : children.find((c) => c.id === primary.id);
|
|
51202
|
+
if (primaryChild === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
51203
|
+
className: "flex h-full w-full items-center justify-center p-4 text-[11px] italic text-foreground-subtle",
|
|
51204
|
+
children: "No child entities to display."
|
|
51205
|
+
});
|
|
51206
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
51207
|
+
className: "flex h-full w-full flex-col items-stretch gap-3 p-3 sm:p-4",
|
|
51208
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(resolveDeviceControl(primaryChild.type)?.HeroCard ?? DummyHeroCard, {
|
|
51209
|
+
trpc,
|
|
51210
|
+
deviceId: primaryChild.id,
|
|
51211
|
+
features: primaryChild.features
|
|
51212
|
+
})
|
|
51213
|
+
});
|
|
51214
|
+
}
|
|
51215
|
+
//#endregion
|
|
51216
|
+
//#region src/composites/device-controls/primary-child-picker.tsx
|
|
51217
|
+
var BASE_SELECT_CLASS = "rounded-md border border-border bg-surface px-1.5 py-0.5 text-[11px] text-foreground";
|
|
51218
|
+
function PrimaryChildPicker({ childrenList, selectedId, onSetPrimary, disabled, className }) {
|
|
51219
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
51220
|
+
value: selectedId,
|
|
51221
|
+
disabled,
|
|
51222
|
+
onChange: (e) => {
|
|
51223
|
+
const nextId = Number(e.target.value);
|
|
51224
|
+
if (!Number.isFinite(nextId) || nextId === selectedId) return;
|
|
51225
|
+
const picked = childrenList.find((c) => c.id === nextId);
|
|
51226
|
+
if (picked !== void 0) onSetPrimary(picked.stableId);
|
|
51227
|
+
},
|
|
51228
|
+
className: className === void 0 ? BASE_SELECT_CLASS : `${BASE_SELECT_CLASS} ${className}`,
|
|
51229
|
+
children: childrenList.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
51230
|
+
value: c.id,
|
|
51231
|
+
children: [
|
|
51232
|
+
c.name,
|
|
51233
|
+
" (",
|
|
51234
|
+
c.type,
|
|
51235
|
+
")"
|
|
51236
|
+
]
|
|
51237
|
+
}, c.id))
|
|
51238
|
+
});
|
|
51239
|
+
}
|
|
51240
|
+
//#endregion
|
|
51241
|
+
//#region src/composites/device-list/batch-toolbar.tsx
|
|
51242
|
+
function DeviceBatchToolbar({ selectedIds, onClear, remove, disable, enable }) {
|
|
51243
|
+
const [pendingDelete, setPendingDelete] = (0, react$1.useState)(false);
|
|
51244
|
+
if (selectedIds.size === 0) return null;
|
|
51245
|
+
const ids = [...selectedIds];
|
|
51246
|
+
const count = ids.length;
|
|
51247
|
+
const handleDisable = () => {
|
|
51248
|
+
for (const id of ids) disable({ deviceId: id });
|
|
51249
|
+
onClear();
|
|
51250
|
+
};
|
|
51251
|
+
const handleEnable = () => {
|
|
51252
|
+
for (const id of ids) enable({ deviceId: id });
|
|
51253
|
+
onClear();
|
|
51254
|
+
};
|
|
51255
|
+
const handleDeleteFirst = () => {
|
|
51256
|
+
setPendingDelete(true);
|
|
51257
|
+
};
|
|
51258
|
+
const handleDeleteConfirm = () => {
|
|
51259
|
+
for (const id of ids) remove({ deviceId: id });
|
|
51260
|
+
setPendingDelete(false);
|
|
51261
|
+
onClear();
|
|
51262
|
+
};
|
|
51263
|
+
const handleDeleteCancel = () => {
|
|
51264
|
+
setPendingDelete(false);
|
|
51265
|
+
};
|
|
51266
|
+
const handleClear = () => {
|
|
51267
|
+
setPendingDelete(false);
|
|
51268
|
+
onClear();
|
|
51269
|
+
};
|
|
51270
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
51271
|
+
className: "flex items-center gap-2 px-3 py-2 rounded-lg border border-border bg-surface-hover",
|
|
51272
|
+
children: [
|
|
51273
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
51274
|
+
className: "text-xs font-medium text-foreground-subtle shrink-0",
|
|
51275
|
+
children: [count, " selected"]
|
|
51276
|
+
}),
|
|
51277
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
51278
|
+
className: "h-3.5 w-px bg-border shrink-0",
|
|
51279
|
+
"aria-hidden": "true"
|
|
51280
|
+
}),
|
|
51281
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
51282
|
+
className: "flex items-center gap-1.5 flex-wrap",
|
|
51283
|
+
children: [
|
|
51284
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
51285
|
+
type: "button",
|
|
51286
|
+
onClick: handleDisable,
|
|
51287
|
+
className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
|
|
51288
|
+
children: "Disable"
|
|
51289
|
+
}),
|
|
51290
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
51291
|
+
type: "button",
|
|
51292
|
+
onClick: handleEnable,
|
|
51293
|
+
className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
|
|
51294
|
+
children: "Enable"
|
|
51295
|
+
}),
|
|
51296
|
+
pendingDelete ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
51297
|
+
type: "button",
|
|
51298
|
+
onClick: handleDeleteConfirm,
|
|
51299
|
+
className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-red-500/50 bg-red-500/10 text-red-400 hover:bg-red-500/20"),
|
|
51300
|
+
children: [
|
|
51301
|
+
"Confirm delete ",
|
|
51302
|
+
count,
|
|
51303
|
+
"?"
|
|
51304
|
+
]
|
|
51305
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
51306
|
+
type: "button",
|
|
51307
|
+
onClick: handleDeleteCancel,
|
|
51308
|
+
className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
|
|
51309
|
+
children: "Cancel"
|
|
51310
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
51311
|
+
type: "button",
|
|
51312
|
+
onClick: handleDeleteFirst,
|
|
51313
|
+
className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-red-500/40 bg-red-500/10 text-red-400 hover:bg-red-500/20"),
|
|
51314
|
+
children: "Delete"
|
|
51315
|
+
}),
|
|
51316
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
51317
|
+
type: "button",
|
|
51318
|
+
onClick: handleClear,
|
|
51319
|
+
className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
|
|
51320
|
+
children: "Clear"
|
|
51321
|
+
})
|
|
51322
|
+
]
|
|
51323
|
+
})
|
|
51324
|
+
]
|
|
51325
|
+
});
|
|
51326
|
+
}
|
|
51327
|
+
//#endregion
|
|
51328
|
+
//#region src/composites/device-list/batch-actions-bar.tsx
|
|
51329
|
+
/**
|
|
51330
|
+
* <DeviceBatchActionsBar> — responsive wrapper around <DeviceBatchToolbar>.
|
|
51331
|
+
*
|
|
51332
|
+
* Surfaces the available bulk actions once devices are selected:
|
|
51333
|
+
* - L2+ (`sm` and up): the toolbar renders inline as a row of buttons.
|
|
51334
|
+
* - L1 (`< sm`): a compact "Actions · N" trigger opens a popover that
|
|
51335
|
+
* hosts the SAME <DeviceBatchToolbar>, so the action logic (inline
|
|
51336
|
+
* delete-confirm, clear) is reused verbatim — never forked.
|
|
51337
|
+
*
|
|
51338
|
+
* Renders nothing while the selection is empty (delegated to the toolbar).
|
|
51339
|
+
*/
|
|
51340
|
+
function DeviceBatchActionsBar(props) {
|
|
51341
|
+
const [open, setOpen] = (0, react$1.useState)(false);
|
|
51342
|
+
const rootRef = (0, react$1.useRef)(null);
|
|
51343
|
+
const count = props.selectedIds.size;
|
|
51344
|
+
(0, react$1.useEffect)(() => {
|
|
51345
|
+
if (!open) return;
|
|
51346
|
+
const onOutside = (e) => {
|
|
51347
|
+
const target = e.target instanceof Node ? e.target : null;
|
|
51348
|
+
if (target && rootRef.current && !rootRef.current.contains(target)) setOpen(false);
|
|
51349
|
+
};
|
|
51350
|
+
const onKey = (e) => {
|
|
51351
|
+
if (e.key === "Escape") setOpen(false);
|
|
51352
|
+
};
|
|
51353
|
+
document.addEventListener("mousedown", onOutside);
|
|
51354
|
+
document.addEventListener("touchstart", onOutside);
|
|
51355
|
+
document.addEventListener("keydown", onKey);
|
|
51356
|
+
return () => {
|
|
51357
|
+
document.removeEventListener("mousedown", onOutside);
|
|
51358
|
+
document.removeEventListener("touchstart", onOutside);
|
|
51359
|
+
document.removeEventListener("keydown", onKey);
|
|
51360
|
+
};
|
|
51361
|
+
}, [open]);
|
|
51362
|
+
if (count === 0) return null;
|
|
51363
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
51364
|
+
className: "hidden sm:block",
|
|
51365
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceBatchToolbar, { ...props })
|
|
51366
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
51367
|
+
ref: rootRef,
|
|
51368
|
+
"data-testid": "batch-actions-compact",
|
|
51369
|
+
className: "relative sm:hidden",
|
|
51370
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
51371
|
+
type: "button",
|
|
51372
|
+
onClick: () => setOpen((v) => !v),
|
|
51373
|
+
"aria-haspopup": "menu",
|
|
51374
|
+
"aria-expanded": open,
|
|
51375
|
+
className: "inline-flex items-center gap-1.5 h-7 px-2.5 rounded-full border border-primary/40 bg-primary/15 text-[10.5px] font-medium text-primary",
|
|
51376
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["Actions · ", String(count)] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: `h-3 w-3 transition-transform ${open ? "rotate-180" : ""}` })]
|
|
51377
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
51378
|
+
role: "menu",
|
|
51379
|
+
className: "absolute left-0 top-full mt-1 z-30 rounded-lg border border-border bg-surface shadow-md",
|
|
51380
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceBatchToolbar, { ...props })
|
|
51381
|
+
})]
|
|
51382
|
+
})] });
|
|
50666
51383
|
}
|
|
50667
51384
|
//#endregion
|
|
50668
51385
|
//#region src/hooks/use-debounced-string.ts
|
|
@@ -50683,6 +51400,59 @@ function useDebouncedString(value, delayMs) {
|
|
|
50683
51400
|
return debounced;
|
|
50684
51401
|
}
|
|
50685
51402
|
//#endregion
|
|
51403
|
+
//#region src/hooks/use-persisted-page-size.ts
|
|
51404
|
+
/**
|
|
51405
|
+
* usePersistedPageSize — a "rows per page" choice remembered per viewer.
|
|
51406
|
+
*
|
|
51407
|
+
* The caller names the storage key, the allowed sizes and the default. Each
|
|
51408
|
+
* surface with its own default or option set keeps its own key, so one
|
|
51409
|
+
* surface's choice never lands on another that cannot show it (the device
|
|
51410
|
+
* list's global key stays `useDeviceListPageSize`'s, with its own options).
|
|
51411
|
+
*
|
|
51412
|
+
* `PAGE_SIZE_ALL` (0) is the "All" option: one page holding every row.
|
|
51413
|
+
*
|
|
51414
|
+
* Storage is a convenience, never a requirement: every read and write is
|
|
51415
|
+
* guarded, and a blocked / private-mode / missing storage simply means the
|
|
51416
|
+
* choice lasts until the page is left. A stored value that is not one of the
|
|
51417
|
+
* options (an older option set, a hand-edited value) is ignored.
|
|
51418
|
+
*/
|
|
51419
|
+
/** The "All" option — one page holding every row. */
|
|
51420
|
+
var PAGE_SIZE_ALL = 0;
|
|
51421
|
+
function readStored(key, options, fallback) {
|
|
51422
|
+
if (key === null || typeof window === "undefined") return fallback;
|
|
51423
|
+
try {
|
|
51424
|
+
const raw = window.localStorage.getItem(key);
|
|
51425
|
+
if (raw === null) return fallback;
|
|
51426
|
+
const parsed = Number.parseInt(raw, 10);
|
|
51427
|
+
return Number.isFinite(parsed) && options.includes(parsed) ? parsed : fallback;
|
|
51428
|
+
} catch {
|
|
51429
|
+
return fallback;
|
|
51430
|
+
}
|
|
51431
|
+
}
|
|
51432
|
+
function writeStored(key, value) {
|
|
51433
|
+
if (key === null || typeof window === "undefined") return;
|
|
51434
|
+
try {
|
|
51435
|
+
window.localStorage.setItem(key, String(value));
|
|
51436
|
+
} catch {}
|
|
51437
|
+
}
|
|
51438
|
+
/** @returns `[size, setSize]`; `setSize` ignores a size that is not an option. */
|
|
51439
|
+
function usePersistedPageSize({ storageKey, options, defaultSize }) {
|
|
51440
|
+
const seed = options.includes(defaultSize) ? defaultSize : options[0] ?? defaultSize;
|
|
51441
|
+
const [size, setSize] = (0, react$1.useState)(() => readStored(storageKey, options, seed));
|
|
51442
|
+
return [size, (0, react$1.useCallback)((next) => {
|
|
51443
|
+
if (!options.includes(next)) return;
|
|
51444
|
+
setSize(next);
|
|
51445
|
+
writeStored(storageKey, next);
|
|
51446
|
+
}, [options, storageKey])];
|
|
51447
|
+
}
|
|
51448
|
+
/**
|
|
51449
|
+
* The number of rows one page holds for a chosen size: "All" is every row
|
|
51450
|
+
* (at least 1, so the pager arithmetic never divides by zero).
|
|
51451
|
+
*/
|
|
51452
|
+
function effectivePageSize(size, totalItems) {
|
|
51453
|
+
return size === 0 ? Math.max(1, totalItems) : size;
|
|
51454
|
+
}
|
|
51455
|
+
//#endregion
|
|
50686
51456
|
//#region src/hooks/use-device-list-page-size.ts
|
|
50687
51457
|
/**
|
|
50688
51458
|
* useDeviceListPageSize — global page-size preference for the device-list
|
|
@@ -50693,8 +51463,9 @@ function useDebouncedString(value, delayMs) {
|
|
|
50693
51463
|
* value is validated against {@link DEVICE_LIST_PAGE_SIZE_OPTIONS}; any
|
|
50694
51464
|
* garbage / out-of-range value falls back to the provided default.
|
|
50695
51465
|
*
|
|
50696
|
-
*
|
|
50697
|
-
* a
|
|
51466
|
+
* A thin binding of `usePersistedPageSize` to that key and option set; a
|
|
51467
|
+
* surface with a different default or options uses the generic hook with a
|
|
51468
|
+
* key of its own.
|
|
50698
51469
|
*/
|
|
50699
51470
|
/** Selectable per-page sizes. The table default (50) is intentionally one of them. */
|
|
50700
51471
|
var DEVICE_LIST_PAGE_SIZE_OPTIONS = [
|
|
@@ -50705,33 +51476,6 @@ var DEVICE_LIST_PAGE_SIZE_OPTIONS = [
|
|
|
50705
51476
|
];
|
|
50706
51477
|
/** Single global key shared across every device-list surface. */
|
|
50707
51478
|
var DEVICE_LIST_PAGE_SIZE_KEY = "camstack.device-list.pageSize";
|
|
50708
|
-
function isAllowed(value) {
|
|
50709
|
-
return DEVICE_LIST_PAGE_SIZE_OPTIONS.includes(value);
|
|
50710
|
-
}
|
|
50711
|
-
/**
|
|
50712
|
-
* Normalize a caller-supplied `defaultSize` to an allowed option, falling back
|
|
50713
|
-
* to the canonical default so a stray value can never seed a phantom size.
|
|
50714
|
-
*/
|
|
50715
|
-
function safeDefault(defaultSize) {
|
|
50716
|
-
return isAllowed(defaultSize) ? defaultSize : 50;
|
|
50717
|
-
}
|
|
50718
|
-
function readStored(defaultSize) {
|
|
50719
|
-
if (typeof window === "undefined") return defaultSize;
|
|
50720
|
-
try {
|
|
50721
|
-
const raw = window.localStorage.getItem(DEVICE_LIST_PAGE_SIZE_KEY);
|
|
50722
|
-
if (raw === null) return defaultSize;
|
|
50723
|
-
const parsed = Number.parseInt(raw, 10);
|
|
50724
|
-
return Number.isFinite(parsed) && isAllowed(parsed) ? parsed : defaultSize;
|
|
50725
|
-
} catch {
|
|
50726
|
-
return defaultSize;
|
|
50727
|
-
}
|
|
50728
|
-
}
|
|
50729
|
-
function writeStored(value) {
|
|
50730
|
-
if (typeof window === "undefined") return;
|
|
50731
|
-
try {
|
|
50732
|
-
window.localStorage.setItem(DEVICE_LIST_PAGE_SIZE_KEY, String(value));
|
|
50733
|
-
} catch {}
|
|
50734
|
-
}
|
|
50735
51479
|
/**
|
|
50736
51480
|
* @param defaultSize fallback when nothing valid is stored. If it is not itself
|
|
50737
51481
|
* a member of {@link DEVICE_LIST_PAGE_SIZE_OPTIONS}, it is replaced by
|
|
@@ -50739,13 +51483,11 @@ function writeStored(value) {
|
|
|
50739
51483
|
* @returns `[size, setSize]` — `setSize` ignores values outside the allowed set.
|
|
50740
51484
|
*/
|
|
50741
51485
|
function useDeviceListPageSize(defaultSize) {
|
|
50742
|
-
|
|
50743
|
-
|
|
50744
|
-
|
|
50745
|
-
|
|
50746
|
-
|
|
50747
|
-
writeStored(next);
|
|
50748
|
-
}, [])];
|
|
51486
|
+
return usePersistedPageSize({
|
|
51487
|
+
storageKey: DEVICE_LIST_PAGE_SIZE_KEY,
|
|
51488
|
+
options: DEVICE_LIST_PAGE_SIZE_OPTIONS,
|
|
51489
|
+
defaultSize: DEVICE_LIST_PAGE_SIZE_OPTIONS.includes(defaultSize) ? defaultSize : 50
|
|
51490
|
+
});
|
|
50749
51491
|
}
|
|
50750
51492
|
//#endregion
|
|
50751
51493
|
//#region src/composites/device-item/helpers.ts
|
|
@@ -53043,6 +53785,29 @@ function groupTopLevelAndAccessories(devices, options) {
|
|
|
53043
53785
|
};
|
|
53044
53786
|
}
|
|
53045
53787
|
//#endregion
|
|
53788
|
+
//#region src/composites/device-list/page-size-select.tsx
|
|
53789
|
+
function pageSizeLabel(size) {
|
|
53790
|
+
return size === 0 ? "All" : String(size);
|
|
53791
|
+
}
|
|
53792
|
+
function PageSizeSelect({ value, options, onChange, ariaLabel }) {
|
|
53793
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
53794
|
+
className: "inline-flex items-center gap-1.5",
|
|
53795
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
53796
|
+
"aria-hidden": "true",
|
|
53797
|
+
children: "Per page"
|
|
53798
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
53799
|
+
"aria-label": ariaLabel,
|
|
53800
|
+
value,
|
|
53801
|
+
onChange: (e) => onChange(Number.parseInt(e.target.value, 10)),
|
|
53802
|
+
className: "h-7 px-1.5 rounded-md border border-border bg-surface text-[11px] text-foreground focus:outline-none focus:ring-1 focus:ring-primary/40",
|
|
53803
|
+
children: options.map((opt) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
53804
|
+
value: opt,
|
|
53805
|
+
children: pageSizeLabel(opt)
|
|
53806
|
+
}, opt))
|
|
53807
|
+
})]
|
|
53808
|
+
});
|
|
53809
|
+
}
|
|
53810
|
+
//#endregion
|
|
53046
53811
|
//#region src/composites/device-list/multi-select.tsx
|
|
53047
53812
|
/**
|
|
53048
53813
|
* Compact multi-select dropdown — a single chip-sized trigger ("Label · N")
|
|
@@ -53957,23 +54722,11 @@ function DeviceModeList(props) {
|
|
|
53957
54722
|
totalItems,
|
|
53958
54723
|
" ",
|
|
53959
54724
|
totalItems === 1 ? "device" : "devices"
|
|
53960
|
-
] }), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
53961
|
-
|
|
53962
|
-
|
|
53963
|
-
|
|
53964
|
-
|
|
53965
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
53966
|
-
"aria-label": "Devices per page",
|
|
53967
|
-
value: tablePageSize,
|
|
53968
|
-
onChange: (e) => {
|
|
53969
|
-
setTablePageSize(Number.parseInt(e.target.value, 10));
|
|
53970
|
-
},
|
|
53971
|
-
className: "h-7 px-1.5 rounded-md border border-border bg-surface text-[11px] text-foreground focus:outline-none focus:ring-1 focus:ring-primary/40",
|
|
53972
|
-
children: DEVICE_LIST_PAGE_SIZE_OPTIONS.map((opt) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
53973
|
-
value: opt,
|
|
53974
|
-
children: opt
|
|
53975
|
-
}, opt))
|
|
53976
|
-
})]
|
|
54725
|
+
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageSizeSelect, {
|
|
54726
|
+
ariaLabel: "Devices per page",
|
|
54727
|
+
value: tablePageSize,
|
|
54728
|
+
options: DEVICE_LIST_PAGE_SIZE_OPTIONS,
|
|
54729
|
+
onChange: setTablePageSize
|
|
53977
54730
|
})]
|
|
53978
54731
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pagination, {
|
|
53979
54732
|
currentPage: safePage,
|
|
@@ -54017,6 +54770,7 @@ function useDeviceListRealtimeSync() {
|
|
|
54017
54770
|
}
|
|
54018
54771
|
//#endregion
|
|
54019
54772
|
//#region src/composites/device-list/generic-mode.tsx
|
|
54773
|
+
var FIXED_PAGE_SIZE = 100;
|
|
54020
54774
|
/**
|
|
54021
54775
|
* Generic render-prop list: search + pagination chrome shared with the device
|
|
54022
54776
|
* list, over an arbitrary item type. Local (non-URL) state — intended for
|
|
@@ -54024,7 +54778,12 @@ function useDeviceListRealtimeSync() {
|
|
|
54024
54778
|
* `renderItem`.
|
|
54025
54779
|
*/
|
|
54026
54780
|
function GenericModeList(props) {
|
|
54027
|
-
const { items, getKey, getSearchText, renderItem, getFilterType, mapFilterOption, filterOptions, filterLabel = "Type", initialTypeFilters = [], columns, renderExpanded, isExpanded, rowClassName, onRowClick, isLoading = false, searchPlaceholder = "Search", emptyLabel = "No items found.", pageSize =
|
|
54781
|
+
const { items, getKey, getSearchText, renderItem, getFilterType, mapFilterOption, filterOptions, filterLabel = "Type", initialTypeFilters = [], columns, renderExpanded, isExpanded, rowClassName, onRowClick, isLoading = false, searchPlaceholder = "Search", emptyLabel = "No items found.", pageSize: fixedPageSize = FIXED_PAGE_SIZE, pageSizeSelector, toolbar } = props;
|
|
54782
|
+
const [chosenPageSize, setChosenPageSize] = usePersistedPageSize(pageSizeSelector ?? {
|
|
54783
|
+
storageKey: null,
|
|
54784
|
+
options: [fixedPageSize],
|
|
54785
|
+
defaultSize: fixedPageSize
|
|
54786
|
+
});
|
|
54028
54787
|
const [searchInput, setSearchInput] = (0, react$1.useState)("");
|
|
54029
54788
|
const debouncedSearch = useDebouncedString(searchInput, 300);
|
|
54030
54789
|
const [typeFilters, setTypeFilters] = (0, react$1.useState)(() => [...initialTypeFilters]);
|
|
@@ -54073,6 +54832,7 @@ function GenericModeList(props) {
|
|
|
54073
54832
|
getFilterType
|
|
54074
54833
|
]);
|
|
54075
54834
|
const totalItems = filtered.length;
|
|
54835
|
+
const pageSize = pageSizeSelector ? effectivePageSize(chosenPageSize, totalItems) : fixedPageSize;
|
|
54076
54836
|
const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
|
|
54077
54837
|
const safePage = Math.min(page, totalPages);
|
|
54078
54838
|
const startIdx = (safePage - 1) * pageSize;
|
|
@@ -54156,7 +54916,23 @@ function GenericModeList(props) {
|
|
|
54156
54916
|
}),
|
|
54157
54917
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
54158
54918
|
className: "shrink-0",
|
|
54159
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
54919
|
+
children: pageSizeSelector && totalItems > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
54920
|
+
className: "flex items-center justify-between gap-4 flex-wrap pt-2 text-[11px] text-foreground-subtle",
|
|
54921
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
54922
|
+
className: "inline-flex items-center gap-2",
|
|
54923
|
+
children: [totalItems <= pageSize && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [totalItems, " total"] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageSizeSelect, {
|
|
54924
|
+
ariaLabel: "Rows per page",
|
|
54925
|
+
value: chosenPageSize,
|
|
54926
|
+
options: pageSizeSelector.options,
|
|
54927
|
+
onChange: setChosenPageSize
|
|
54928
|
+
})]
|
|
54929
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pagination, {
|
|
54930
|
+
currentPage: safePage,
|
|
54931
|
+
totalItems,
|
|
54932
|
+
pageSize,
|
|
54933
|
+
onPageChange: setPage
|
|
54934
|
+
})]
|
|
54935
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pagination, {
|
|
54160
54936
|
currentPage: safePage,
|
|
54161
54937
|
totalItems,
|
|
54162
54938
|
pageSize,
|
|
@@ -54285,6 +55061,24 @@ var DeviceItemRowSchema = zod.z.object({
|
|
|
54285
55061
|
});
|
|
54286
55062
|
var SupportedKindsSchema = zod.z.array(zod.z.string());
|
|
54287
55063
|
var STATUS_POLL_INTERVAL_MS = 1e4;
|
|
55064
|
+
/** Where the viewer's "rows per page" choice for this table is remembered. */
|
|
55065
|
+
var DEVICE_EXPORT_PAGE_SIZE_KEY = "camstack.device-export.pageSize";
|
|
55066
|
+
/**
|
|
55067
|
+
* The exposed-devices table's page sizes. Its own key and options, not the
|
|
55068
|
+
* Devices page's (25/50/100/200, default 50): the operator asked for a short
|
|
55069
|
+
* default here (25) and an "All" for a quick scan of every exportable device.
|
|
55070
|
+
*/
|
|
55071
|
+
var DEVICE_EXPORT_PAGE_SIZE = {
|
|
55072
|
+
options: [
|
|
55073
|
+
10,
|
|
55074
|
+
25,
|
|
55075
|
+
50,
|
|
55076
|
+
100,
|
|
55077
|
+
0
|
|
55078
|
+
],
|
|
55079
|
+
defaultSize: 25,
|
|
55080
|
+
storageKey: DEVICE_EXPORT_PAGE_SIZE_KEY
|
|
55081
|
+
};
|
|
54288
55082
|
/**
|
|
54289
55083
|
* The fleet-list poll. Deliberately the same 30 s as the window
|
|
54290
55084
|
* `applyFleetQueryDefaults` registers for `deviceManager.listAll` (D274) —
|
|
@@ -54369,6 +55163,35 @@ function SetupSection({ setup }) {
|
|
|
54369
55163
|
})]
|
|
54370
55164
|
});
|
|
54371
55165
|
}
|
|
55166
|
+
/** Parse `getStatus` against the canonical cap schema (loose wire output). */
|
|
55167
|
+
function parseStatus(data) {
|
|
55168
|
+
const parsed = _camstack_types.DeviceExportStatusSchema.safeParse(data);
|
|
55169
|
+
return parsed.success ? parsed.data : null;
|
|
55170
|
+
}
|
|
55171
|
+
/**
|
|
55172
|
+
* The exporter's error and Setup block on their own — for a page that shows
|
|
55173
|
+
* them above its settings while the devices table sits at the bottom. Renders
|
|
55174
|
+
* nothing when the exporter reports neither. Shares `getStatus`'s query key
|
|
55175
|
+
* with the panel, so mounting both costs one request.
|
|
55176
|
+
*/
|
|
55177
|
+
function DeviceExportSetup({ addonId }) {
|
|
55178
|
+
const statusQuery = useDeviceExportGetStatus({
|
|
55179
|
+
nodeId: "hub",
|
|
55180
|
+
addonId
|
|
55181
|
+
}, {
|
|
55182
|
+
refetchInterval: STATUS_POLL_INTERVAL_MS,
|
|
55183
|
+
retry: false
|
|
55184
|
+
});
|
|
55185
|
+
const status = (0, react$1.useMemo)(() => parseStatus(statusQuery.data), [statusQuery.data]);
|
|
55186
|
+
if (!status?.error && !status?.setup) return null;
|
|
55187
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
55188
|
+
className: "rounded-lg border border-border bg-surface overflow-hidden",
|
|
55189
|
+
children: [status.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
55190
|
+
className: "px-2 sm:px-4 py-3 border-b border-border",
|
|
55191
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBox, { message: status.error })
|
|
55192
|
+
}), status.setup && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SetupSection, { setup: status.setup })]
|
|
55193
|
+
});
|
|
55194
|
+
}
|
|
54372
55195
|
/**
|
|
54373
55196
|
* Per-row Expose/Exposed toggle. Reflects whether the row's device is exposed
|
|
54374
55197
|
* to THIS exporter and flips it through the SAME runner the bulk buttons use —
|
|
@@ -54447,7 +55270,7 @@ function BulkSummary({ outcome, onDismiss }) {
|
|
|
54447
55270
|
* Generic device-export panel. Addon-agnostic — only the `device-export`
|
|
54448
55271
|
* cap drives it.
|
|
54449
55272
|
*/
|
|
54450
|
-
function DeviceExportPanel({ addonId, onOpenDevice }) {
|
|
55273
|
+
function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
|
|
54451
55274
|
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
54452
55275
|
const statusQuery = useDeviceExportGetStatus({
|
|
54453
55276
|
nodeId: "hub",
|
|
@@ -54473,10 +55296,7 @@ function DeviceExportPanel({ addonId, onOpenDevice }) {
|
|
|
54473
55296
|
}, [queryClient]);
|
|
54474
55297
|
const exposeMutation = useDeviceExportExposeDevice({ onSuccess: invalidate });
|
|
54475
55298
|
const unexposeMutation = useDeviceExportUnexposeDevice({ onSuccess: invalidate });
|
|
54476
|
-
const status = (0, react$1.useMemo)(() =>
|
|
54477
|
-
const parsed = _camstack_types.DeviceExportStatusSchema.safeParse(statusQuery.data);
|
|
54478
|
-
return parsed.success ? parsed.data : null;
|
|
54479
|
-
}, [statusQuery.data]);
|
|
55299
|
+
const status = (0, react$1.useMemo)(() => parseStatus(statusQuery.data), [statusQuery.data]);
|
|
54480
55300
|
const exposed = (0, react$1.useMemo)(() => {
|
|
54481
55301
|
const parsed = ExposedDeviceArraySchema.safeParse(exposedQuery.data);
|
|
54482
55302
|
return parsed.success ? parsed.data : [];
|
|
@@ -54699,11 +55519,11 @@ function DeviceExportPanel({ addonId, onOpenDevice }) {
|
|
|
54699
55519
|
]
|
|
54700
55520
|
}), loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin text-foreground-subtle" })]
|
|
54701
55521
|
}),
|
|
54702
|
-
status?.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
55522
|
+
showSetup && status?.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
54703
55523
|
className: "px-2 sm:px-4 py-3 border-b border-border",
|
|
54704
55524
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBox, { message: status.error })
|
|
54705
55525
|
}),
|
|
54706
|
-
status?.setup && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SetupSection, { setup: status.setup }),
|
|
55526
|
+
showSetup && status?.setup && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SetupSection, { setup: status.setup }),
|
|
54707
55527
|
outcome !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BulkSummary, {
|
|
54708
55528
|
outcome,
|
|
54709
55529
|
onDismiss: dismissOutcome
|
|
@@ -54723,6 +55543,7 @@ function DeviceExportPanel({ addonId, onOpenDevice }) {
|
|
|
54723
55543
|
columns,
|
|
54724
55544
|
onRowClick: onOpenDevice ? onRowClick : void 0,
|
|
54725
55545
|
toolbar: selectionToolbar,
|
|
55546
|
+
pageSizeSelector: DEVICE_EXPORT_PAGE_SIZE,
|
|
54726
55547
|
searchPlaceholder: "Search by name or stableId",
|
|
54727
55548
|
emptyLabel: "No device matches this search."
|
|
54728
55549
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {
|
|
@@ -54748,145 +55569,6 @@ function DeviceGrid({ children, minCardWidth = 220, gap = 3, className }) {
|
|
|
54748
55569
|
});
|
|
54749
55570
|
}
|
|
54750
55571
|
//#endregion
|
|
54751
|
-
//#region src/composites/icon-picker.tsx
|
|
54752
|
-
/**
|
|
54753
|
-
* icon-picker — the ONE control for choosing an icon, wherever admin-ui asks.
|
|
54754
|
-
*
|
|
54755
|
-
* There were two: a device one over `DISPLAY_ICON_REGISTRY` and a room one
|
|
54756
|
-
* over `LOCATION_ICON_IDS`, identical apart from the vocabulary they walked.
|
|
54757
|
-
* That was tolerable while the device set was 56 glyphs in a flat grid. It is
|
|
54758
|
-
* not at 157: a picker that only lets the operator HUNT is a picker they stop
|
|
54759
|
-
* using, and the two copies were already drifting — only one of them had an
|
|
54760
|
-
* i18n'd label.
|
|
54761
|
-
*
|
|
54762
|
-
* So the vocabulary, the grouping and the search all come from
|
|
54763
|
-
* `@camstack/types`' `display-icon-vocabulary`, which is the same module the
|
|
54764
|
-
* viewer's sheet searches. What is left here is the web half: a popover, a
|
|
54765
|
-
* grid, and a text field.
|
|
54766
|
-
*
|
|
54767
|
-
* ## What it does NOT own
|
|
54768
|
-
*
|
|
54769
|
-
* - **The glyphs.** `resolveGlyph` is a prop, because this package's table is
|
|
54770
|
-
* `lucide-react` and the viewer's is `lucide-react-native`. A key this build
|
|
54771
|
-
* cannot draw is SKIPPED rather than rendered as a hole — an empty tile
|
|
54772
|
-
* reads as a rendering bug, not as a missing choice.
|
|
54773
|
-
* - **The words.** Every string is a prop. This package has no i18n (one file
|
|
54774
|
-
* in it imports react-i18next, and that is the exception), and a control
|
|
54775
|
-
* that reached for a translation would force it on every consumer.
|
|
54776
|
-
* - **The write.** `onPick` is called and that is all; whether it is a form
|
|
54777
|
-
* value or an immediate mutation is the caller's business, as it already was
|
|
54778
|
-
* in both pickers this replaces.
|
|
54779
|
-
*
|
|
54780
|
-
* ## The clear tile is never filtered away
|
|
54781
|
-
*
|
|
54782
|
-
* The "inherit" / "no icon" choice sits above the sections and ignores the
|
|
54783
|
-
* search. A query that hid it would strand the operator on a glyph they picked
|
|
54784
|
-
* by accident with no way back to the default — the one action a picker must
|
|
54785
|
-
* always offer.
|
|
54786
|
-
*/
|
|
54787
|
-
function IconPicker({ value, onPick, resolveGlyph, fallbackGlyph, labels, only, extraWords, clearGlyph, labelFor, disabled = false }) {
|
|
54788
|
-
const [open, setOpen] = (0, react$1.useState)(false);
|
|
54789
|
-
const [query, setQuery] = (0, react$1.useState)("");
|
|
54790
|
-
const allowed = (0, react$1.useMemo)(() => only === void 0 ? null : new Set(only), [only]);
|
|
54791
|
-
const sections = (0, react$1.useMemo)(() => {
|
|
54792
|
-
const hits = (0, _camstack_types.searchDisplayIcons)(query, extraWords);
|
|
54793
|
-
const byGroup = /* @__PURE__ */ new Map();
|
|
54794
|
-
for (const hit of hits) {
|
|
54795
|
-
const { key, group } = hit.entry;
|
|
54796
|
-
if (allowed !== null && !allowed.has(key)) continue;
|
|
54797
|
-
if (resolveGlyph(key) === void 0) continue;
|
|
54798
|
-
const bucket = byGroup.get(group);
|
|
54799
|
-
if (bucket === void 0) byGroup.set(group, [key]);
|
|
54800
|
-
else bucket.push(key);
|
|
54801
|
-
}
|
|
54802
|
-
return _camstack_types.DISPLAY_ICON_GROUPS.flatMap((group) => {
|
|
54803
|
-
const keys = byGroup.get(group);
|
|
54804
|
-
return keys === void 0 || keys.length === 0 ? [] : [{
|
|
54805
|
-
group,
|
|
54806
|
-
keys
|
|
54807
|
-
}];
|
|
54808
|
-
});
|
|
54809
|
-
}, [
|
|
54810
|
-
query,
|
|
54811
|
-
extraWords,
|
|
54812
|
-
allowed,
|
|
54813
|
-
resolveGlyph
|
|
54814
|
-
]);
|
|
54815
|
-
const Current = (value === null ? void 0 : resolveGlyph(value)) ?? fallbackGlyph;
|
|
54816
|
-
const choose = async (next) => {
|
|
54817
|
-
setOpen(false);
|
|
54818
|
-
setQuery("");
|
|
54819
|
-
await onPick(next);
|
|
54820
|
-
};
|
|
54821
|
-
const Clear = clearGlyph ?? fallbackGlyph;
|
|
54822
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
54823
|
-
className: "relative flex shrink-0 items-center gap-2",
|
|
54824
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
54825
|
-
type: "button",
|
|
54826
|
-
disabled,
|
|
54827
|
-
onClick: () => setOpen((o) => !o),
|
|
54828
|
-
"aria-expanded": open,
|
|
54829
|
-
"aria-label": labels.trigger,
|
|
54830
|
-
title: labels.trigger,
|
|
54831
|
-
className: "rounded border border-border p-1 text-foreground-muted hover:bg-surface-hover disabled:opacity-50",
|
|
54832
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Current, { className: "h-4 w-4" })
|
|
54833
|
-
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
54834
|
-
type: "button",
|
|
54835
|
-
"aria-label": labels.close,
|
|
54836
|
-
onClick: () => setOpen(false),
|
|
54837
|
-
className: "fixed inset-0 z-10 cursor-default"
|
|
54838
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
54839
|
-
role: "group",
|
|
54840
|
-
"aria-label": labels.panel,
|
|
54841
|
-
className: "absolute left-0 top-full z-20 mt-1 max-h-80 w-72 overflow-y-auto rounded-md border border-border bg-surface p-2 shadow-lg",
|
|
54842
|
-
children: [
|
|
54843
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
54844
|
-
type: "search",
|
|
54845
|
-
value: query,
|
|
54846
|
-
autoFocus: true,
|
|
54847
|
-
onChange: (e) => setQuery(e.target.value),
|
|
54848
|
-
placeholder: labels.searchPlaceholder,
|
|
54849
|
-
"aria-label": labels.searchPlaceholder,
|
|
54850
|
-
className: "mb-2 w-full rounded border border-border bg-surface-raised px-2 py-1 text-xs text-foreground placeholder:text-foreground-subtle"
|
|
54851
|
-
}),
|
|
54852
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
54853
|
-
type: "button",
|
|
54854
|
-
onClick: () => void choose(null),
|
|
54855
|
-
"aria-pressed": value === null,
|
|
54856
|
-
className: "mb-2 flex w-full items-center gap-1.5 rounded px-1.5 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover",
|
|
54857
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Clear, { className: "h-3.5 w-3.5" }), labels.clear]
|
|
54858
|
-
}),
|
|
54859
|
-
sections.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
54860
|
-
className: "px-1.5 py-2 text-[11px] text-foreground-subtle",
|
|
54861
|
-
children: labels.noMatches
|
|
54862
|
-
}) : sections.map(({ group, keys }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
54863
|
-
className: "mb-2 last:mb-0",
|
|
54864
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
54865
|
-
className: "px-1.5 pb-1 text-[10px] uppercase tracking-wide text-foreground-subtle",
|
|
54866
|
-
children: labels.groups[group]
|
|
54867
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
54868
|
-
className: "grid grid-cols-7 gap-1",
|
|
54869
|
-
children: keys.map((key) => {
|
|
54870
|
-
const Glyph = resolveGlyph(key);
|
|
54871
|
-
if (Glyph === void 0) return null;
|
|
54872
|
-
const selected = value === key;
|
|
54873
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
54874
|
-
type: "button",
|
|
54875
|
-
onClick: () => void choose(key),
|
|
54876
|
-
"aria-pressed": selected,
|
|
54877
|
-
"aria-label": labelFor?.(key) ?? key,
|
|
54878
|
-
title: labelFor?.(key) ?? key,
|
|
54879
|
-
className: selected ? "flex items-center justify-center rounded border border-primary bg-primary/10 p-1.5 text-foreground" : "flex items-center justify-center rounded border border-transparent p-1.5 text-foreground-muted hover:bg-surface-hover",
|
|
54880
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, { className: "h-4 w-4" })
|
|
54881
|
-
}, key);
|
|
54882
|
-
})
|
|
54883
|
-
})]
|
|
54884
|
-
}, group))
|
|
54885
|
-
]
|
|
54886
|
-
})] })]
|
|
54887
|
-
});
|
|
54888
|
-
}
|
|
54889
|
-
//#endregion
|
|
54890
55572
|
//#region src/composites/device-selector-picker.tsx
|
|
54891
55573
|
/**
|
|
54892
55574
|
* <DeviceSelectorPicker> — the operator-facing editor for a v3
|
|
@@ -61087,6 +61769,7 @@ exports.CoverPanel = CoverPanel;
|
|
|
61087
61769
|
exports.CustomFieldRenderersProvider = CustomFieldRenderersProvider;
|
|
61088
61770
|
exports.DEFAULT_COLOR = DEFAULT_COLOR;
|
|
61089
61771
|
exports.DEVICE_COLUMNS = DEVICE_COLUMNS;
|
|
61772
|
+
exports.DEVICE_EXPORT_PAGE_SIZE_KEY = DEVICE_EXPORT_PAGE_SIZE_KEY;
|
|
61090
61773
|
exports.DEVICE_LIST_PAGE_SIZE_KEY = DEVICE_LIST_PAGE_SIZE_KEY;
|
|
61091
61774
|
exports.DEVICE_LIST_PAGE_SIZE_OPTIONS = DEVICE_LIST_PAGE_SIZE_OPTIONS;
|
|
61092
61775
|
exports.DEVICE_PICKER_GROUP_STORAGE_KEY = DEVICE_PICKER_GROUP_STORAGE_KEY;
|
|
@@ -61105,6 +61788,7 @@ exports.DeviceBatchToolbar = DeviceBatchToolbar;
|
|
|
61105
61788
|
exports.DeviceCard = DeviceCard;
|
|
61106
61789
|
exports.DeviceContextProvider = DeviceContextProvider;
|
|
61107
61790
|
exports.DeviceExportPanel = DeviceExportPanel;
|
|
61791
|
+
exports.DeviceExportSetup = DeviceExportSetup;
|
|
61108
61792
|
exports.DeviceFilterBar = DeviceFilterBar;
|
|
61109
61793
|
exports.DeviceGrid = DeviceGrid;
|
|
61110
61794
|
exports.DeviceItem = DeviceItem;
|
|
@@ -61218,6 +61902,7 @@ exports.NotificationTargetsField = NotificationTargetsField;
|
|
|
61218
61902
|
exports.OPTION_SELECT_SEARCH_MIN_OPTIONS = OPTION_SELECT_SEARCH_MIN_OPTIONS;
|
|
61219
61903
|
exports.OfflineBadge = OfflineBadge;
|
|
61220
61904
|
exports.OptionSelect = OptionSelect;
|
|
61905
|
+
exports.PAGE_SIZE_ALL = PAGE_SIZE_ALL;
|
|
61221
61906
|
exports.PHASE_CONFIG = PHASE_CONFIG;
|
|
61222
61907
|
exports.PICKER_CHIP_LIMIT = PICKER_CHIP_LIMIT;
|
|
61223
61908
|
exports.PICKER_DEFAULT_GROUP_BY = PICKER_DEFAULT_GROUP_BY;
|
|
@@ -61232,6 +61917,7 @@ exports.PICKER_WINDOW = PICKER_WINDOW;
|
|
|
61232
61917
|
exports.PRIORITY = PRIORITY;
|
|
61233
61918
|
exports.PTZOverlay = PTZOverlay;
|
|
61234
61919
|
exports.PageHeader = PageHeader;
|
|
61920
|
+
exports.PageSizeSelect = PageSizeSelect;
|
|
61235
61921
|
exports.PhaseIcon = PhaseIcon;
|
|
61236
61922
|
exports.PipelineBuilder = PipelineBuilder;
|
|
61237
61923
|
exports.PipelineRuntimeSelector = PipelineRuntimeSelector;
|
|
@@ -61264,6 +61950,7 @@ exports.RotateCcw = RotateCcw;
|
|
|
61264
61950
|
exports.RunReport = RunReport;
|
|
61265
61951
|
exports.SCENE_CAPTURE_VARIANTS = SCENE_CAPTURE_VARIANTS;
|
|
61266
61952
|
exports.SCENE_CAPTURE_VARIANTS$1 = SCENE_CAPTURE_VARIANTS;
|
|
61953
|
+
exports.SECRET_REVEAL_MS = SECRET_REVEAL_MS;
|
|
61267
61954
|
exports.SECTION_BODY = SECTION_BODY;
|
|
61268
61955
|
exports.SECTION_CARD = SECTION_CARD;
|
|
61269
61956
|
exports.SECTION_HEADER = SECTION_HEADER;
|
|
@@ -61283,11 +61970,14 @@ exports.ScopePicker = ScopePicker;
|
|
|
61283
61970
|
exports.ScriptHeroCard = ScriptHeroCard;
|
|
61284
61971
|
exports.ScriptInlineControl = ScriptInlineControl;
|
|
61285
61972
|
exports.ScrollArea = ScrollArea;
|
|
61973
|
+
exports.SecretFieldControl = SecretFieldControl;
|
|
61974
|
+
exports.SecretRevealProvider = SecretRevealProvider;
|
|
61286
61975
|
exports.SectionCard = SectionCard;
|
|
61287
61976
|
exports.SegmentedChoice = SegmentedChoice;
|
|
61288
61977
|
exports.Select = Select;
|
|
61289
61978
|
exports.SelectedChip = SelectedChip;
|
|
61290
61979
|
exports.SemanticBadge = SemanticBadge;
|
|
61980
|
+
exports.SendFrameToTrainingButton = SendFrameToTrainingButton;
|
|
61291
61981
|
exports.SensorHeroCard = SensorHeroCard;
|
|
61292
61982
|
exports.SensorInlineControl = SensorInlineControl;
|
|
61293
61983
|
exports.SensorValueAtom = SensorValueAtom;
|
|
@@ -61335,6 +62025,7 @@ exports.TimezoneSelector = TimezoneSelector;
|
|
|
61335
62025
|
exports.Tooltip = Tooltip;
|
|
61336
62026
|
exports.TooltipContent = TooltipContent;
|
|
61337
62027
|
exports.TooltipTrigger = TooltipTrigger;
|
|
62028
|
+
exports.TrainingCollectionPicker = TrainingCollectionPicker;
|
|
61338
62029
|
exports.Trash2 = Trash2;
|
|
61339
62030
|
exports.UnitPicker = UnitPicker;
|
|
61340
62031
|
exports.VacuumHeroCard = VacuumHeroCard;
|
|
@@ -61404,6 +62095,7 @@ exports.deviceTypeMetaOf = deviceTypeMetaOf;
|
|
|
61404
62095
|
exports.devicesToOptions = devicesToOptions;
|
|
61405
62096
|
exports.dockBox = dockBox;
|
|
61406
62097
|
exports.draftFromQuery = draftFromQuery;
|
|
62098
|
+
exports.effectivePageSize = effectivePageSize;
|
|
61407
62099
|
exports.emptyDevicesWidens = emptyDevicesWidens;
|
|
61408
62100
|
exports.emptySessionRun = emptySessionRun;
|
|
61409
62101
|
exports.ensureMfHostInit = ensureMfHostInit;
|
|
@@ -61444,6 +62136,7 @@ exports.isAbsentProvider$1 = isAbsentProvider;
|
|
|
61444
62136
|
exports.isDataTableFilterActive = isDataTableFilterActive;
|
|
61445
62137
|
exports.isFieldVisible = isFieldVisible;
|
|
61446
62138
|
exports.isGroupExpanded = isGroupExpanded;
|
|
62139
|
+
exports.isKeychainFieldValue = isKeychainFieldValue;
|
|
61447
62140
|
exports.isValueField = isValueField;
|
|
61448
62141
|
exports.lawnMowerActivityMeta = lawnMowerActivityMeta;
|
|
61449
62142
|
exports.lightColors = require_theme_index.lightColors$1;
|
|
@@ -61466,6 +62159,7 @@ exports.optionSelectSearchText = optionSearchText;
|
|
|
61466
62159
|
exports.orphanDeviceLabel = orphanDeviceLabel;
|
|
61467
62160
|
exports.overrideEntityIdFromLink = overrideEntityIdFromLink;
|
|
61468
62161
|
exports.pageRows = pageRows;
|
|
62162
|
+
exports.pageSizeLabel = pageSizeLabel;
|
|
61469
62163
|
exports.parentDetailOf = parentDetailOf;
|
|
61470
62164
|
exports.parseIceCandidate = parseIceCandidate;
|
|
61471
62165
|
exports.parsePickerGroupBy = parsePickerGroupBy;
|
|
@@ -61539,6 +62233,7 @@ exports.toStationaryOverlayBox = toStationaryOverlayBox;
|
|
|
61539
62233
|
exports.toggleDataTableFacet = toggleDataTableFacet;
|
|
61540
62234
|
exports.togglePickerDevice = togglePickerDevice;
|
|
61541
62235
|
exports.togglePickerKey = togglePickerKey;
|
|
62236
|
+
exports.trainingRefusalText = trainingRefusalText;
|
|
61542
62237
|
exports.trpc = trpc;
|
|
61543
62238
|
exports.typeLabel = typeLabel;
|
|
61544
62239
|
exports.useAccessoriesGetStatus = useAccessoriesGetStatus;
|
|
@@ -61971,6 +62666,15 @@ exports.useIntercomStartTalkSession = useIntercomStartTalkSession;
|
|
|
61971
62666
|
exports.useIntercomStopSession = useIntercomStopSession;
|
|
61972
62667
|
exports.useIsMidWidth = useIsMidWidth;
|
|
61973
62668
|
exports.useIsMobile = useIsMobile;
|
|
62669
|
+
exports.useKeychainAdminDeleteOrphans = useKeychainAdminDeleteOrphans;
|
|
62670
|
+
exports.useKeychainAdminDownloadRecoveryKey = useKeychainAdminDownloadRecoveryKey;
|
|
62671
|
+
exports.useKeychainAdminListAudit = useKeychainAdminListAudit;
|
|
62672
|
+
exports.useKeychainAdminListSecrets = useKeychainAdminListSecrets;
|
|
62673
|
+
exports.useKeychainAdminPurgeRetiredKeys = useKeychainAdminPurgeRetiredKeys;
|
|
62674
|
+
exports.useKeychainAdminResetKeychain = useKeychainAdminResetKeychain;
|
|
62675
|
+
exports.useKeychainAdminReveal = useKeychainAdminReveal;
|
|
62676
|
+
exports.useKeychainAdminRotateMasterKey = useKeychainAdminRotateMasterKey;
|
|
62677
|
+
exports.useKeychainAdminStatus = useKeychainAdminStatus;
|
|
61974
62678
|
exports.useLawnMowerControlDock = useLawnMowerControlDock;
|
|
61975
62679
|
exports.useLawnMowerControlGetStatus = useLawnMowerControlGetStatus;
|
|
61976
62680
|
exports.useLawnMowerControlPause = useLawnMowerControlPause;
|
|
@@ -62176,6 +62880,7 @@ exports.useOsdManagerSetSlotBinding = useOsdManagerSetSlotBinding;
|
|
|
62176
62880
|
exports.useOsdManagerUpsertTemplate = useOsdManagerUpsertTemplate;
|
|
62177
62881
|
exports.useOsdSetOverlay = useOsdSetOverlay;
|
|
62178
62882
|
exports.usePTZ = usePTZ;
|
|
62883
|
+
exports.usePersistedPageSize = usePersistedPageSize;
|
|
62179
62884
|
exports.usePetFeederCallPet = usePetFeederCallPet;
|
|
62180
62885
|
exports.usePetFeederCancelFeed = usePetFeederCancelFeed;
|
|
62181
62886
|
exports.usePetFeederFeed = usePetFeederFeed;
|
|
@@ -62188,6 +62893,8 @@ exports.usePetFeederSetFeedSound = usePetFeederSetFeedSound;
|
|
|
62188
62893
|
exports.usePetFeederSetIndicatorLight = usePetFeederSetIndicatorLight;
|
|
62189
62894
|
exports.usePetFeederSetVolume = usePetFeederSetVolume;
|
|
62190
62895
|
exports.usePickerSiteZones = usePickerSiteZones;
|
|
62896
|
+
exports.usePipelineAnalyticsAddRetrainFrameFromRecording = usePipelineAnalyticsAddRetrainFrameFromRecording;
|
|
62897
|
+
exports.usePipelineAnalyticsAddRetrainFramesToCollection = usePipelineAnalyticsAddRetrainFramesToCollection;
|
|
62191
62898
|
exports.usePipelineAnalyticsApplyDeviceSettingsPatch = usePipelineAnalyticsApplyDeviceSettingsPatch;
|
|
62192
62899
|
exports.usePipelineAnalyticsCancelRelocateMedia = usePipelineAnalyticsCancelRelocateMedia;
|
|
62193
62900
|
exports.usePipelineAnalyticsCancelStorageMigrationMove = usePipelineAnalyticsCancelStorageMigrationMove;
|
|
@@ -62196,7 +62903,9 @@ exports.usePipelineAnalyticsClearTracks = usePipelineAnalyticsClearTracks;
|
|
|
62196
62903
|
exports.usePipelineAnalyticsCompleteRetrainTrack = usePipelineAnalyticsCompleteRetrainTrack;
|
|
62197
62904
|
exports.usePipelineAnalyticsCountRelocatableMedia = usePipelineAnalyticsCountRelocatableMedia;
|
|
62198
62905
|
exports.usePipelineAnalyticsCountUnstampedEventMedia = usePipelineAnalyticsCountUnstampedEventMedia;
|
|
62906
|
+
exports.usePipelineAnalyticsCreateRetrainCollection = usePipelineAnalyticsCreateRetrainCollection;
|
|
62199
62907
|
exports.usePipelineAnalyticsDeleteDeviceEvents = usePipelineAnalyticsDeleteDeviceEvents;
|
|
62908
|
+
exports.usePipelineAnalyticsDeleteRetrainCollection = usePipelineAnalyticsDeleteRetrainCollection;
|
|
62200
62909
|
exports.usePipelineAnalyticsDeleteTracks = usePipelineAnalyticsDeleteTracks;
|
|
62201
62910
|
exports.usePipelineAnalyticsDeselectRetrainFrame = usePipelineAnalyticsDeselectRetrainFrame;
|
|
62202
62911
|
exports.usePipelineAnalyticsGetActiveTracks = usePipelineAnalyticsGetActiveTracks;
|
|
@@ -62231,6 +62940,9 @@ exports.usePipelineAnalyticsListOpsLog = usePipelineAnalyticsListOpsLog;
|
|
|
62231
62940
|
exports.usePipelineAnalyticsListRecentTracks = usePipelineAnalyticsListRecentTracks;
|
|
62232
62941
|
exports.usePipelineAnalyticsListRelocateMediaJobs = usePipelineAnalyticsListRelocateMediaJobs;
|
|
62233
62942
|
exports.usePipelineAnalyticsListRetrainAnnotations = usePipelineAnalyticsListRetrainAnnotations;
|
|
62943
|
+
exports.usePipelineAnalyticsListRetrainAssistProfiles = usePipelineAnalyticsListRetrainAssistProfiles;
|
|
62944
|
+
exports.usePipelineAnalyticsListRetrainCollectionFrames = usePipelineAnalyticsListRetrainCollectionFrames;
|
|
62945
|
+
exports.usePipelineAnalyticsListRetrainCollections = usePipelineAnalyticsListRetrainCollections;
|
|
62234
62946
|
exports.usePipelineAnalyticsListRetrainFrames = usePipelineAnalyticsListRetrainFrames;
|
|
62235
62947
|
exports.usePipelineAnalyticsListRetrainStaging = usePipelineAnalyticsListRetrainStaging;
|
|
62236
62948
|
exports.usePipelineAnalyticsListTrackMedia = usePipelineAnalyticsListTrackMedia;
|
|
@@ -62239,6 +62951,7 @@ exports.usePipelineAnalyticsListTracks = usePipelineAnalyticsListTracks;
|
|
|
62239
62951
|
exports.usePipelineAnalyticsOwnersWithMedia = usePipelineAnalyticsOwnersWithMedia;
|
|
62240
62952
|
exports.usePipelineAnalyticsPauseForStorageMigration = usePipelineAnalyticsPauseForStorageMigration;
|
|
62241
62953
|
exports.usePipelineAnalyticsProposeRetrainAnnotations = usePipelineAnalyticsProposeRetrainAnnotations;
|
|
62954
|
+
exports.usePipelineAnalyticsProposeRetrainAnnotationsWithLlm = usePipelineAnalyticsProposeRetrainAnnotationsWithLlm;
|
|
62242
62955
|
exports.usePipelineAnalyticsPruneEvents = usePipelineAnalyticsPruneEvents;
|
|
62243
62956
|
exports.usePipelineAnalyticsPruneEventsBefore = usePipelineAnalyticsPruneEventsBefore;
|
|
62244
62957
|
exports.usePipelineAnalyticsPruneTracksBefore = usePipelineAnalyticsPruneTracksBefore;
|
|
@@ -62248,17 +62961,21 @@ exports.usePipelineAnalyticsReclaimDebugMedia = usePipelineAnalyticsReclaimDebug
|
|
|
62248
62961
|
exports.usePipelineAnalyticsReconcileFromDisk = usePipelineAnalyticsReconcileFromDisk;
|
|
62249
62962
|
exports.usePipelineAnalyticsRefreshStorageLocationsForMigration = usePipelineAnalyticsRefreshStorageLocationsForMigration;
|
|
62250
62963
|
exports.usePipelineAnalyticsRelocateMedia = usePipelineAnalyticsRelocateMedia;
|
|
62964
|
+
exports.usePipelineAnalyticsRemoveRetrainFrameFromCollection = usePipelineAnalyticsRemoveRetrainFrameFromCollection;
|
|
62965
|
+
exports.usePipelineAnalyticsRenameRetrainCollection = usePipelineAnalyticsRenameRetrainCollection;
|
|
62251
62966
|
exports.usePipelineAnalyticsRestageRetrainTrack = usePipelineAnalyticsRestageRetrainTrack;
|
|
62252
62967
|
exports.usePipelineAnalyticsResumeForStorageMigration = usePipelineAnalyticsResumeForStorageMigration;
|
|
62253
62968
|
exports.usePipelineAnalyticsSaveRetrainAnnotations = usePipelineAnalyticsSaveRetrainAnnotations;
|
|
62254
62969
|
exports.usePipelineAnalyticsSearchObjectEvents = usePipelineAnalyticsSearchObjectEvents;
|
|
62255
62970
|
exports.usePipelineAnalyticsSelectRetrainFrames = usePipelineAnalyticsSelectRetrainFrames;
|
|
62971
|
+
exports.usePipelineAnalyticsSetRetrainFrameComplete = usePipelineAnalyticsSetRetrainFrameComplete;
|
|
62256
62972
|
exports.usePipelineAnalyticsSetTrackFlags = usePipelineAnalyticsSetTrackFlags;
|
|
62257
62973
|
exports.usePipelineAnalyticsSetTrackPurity = usePipelineAnalyticsSetTrackPurity;
|
|
62258
62974
|
exports.usePipelineAnalyticsSetTrackRelation = usePipelineAnalyticsSetTrackRelation;
|
|
62259
62975
|
exports.usePipelineAnalyticsStartStorageMigrationMove = usePipelineAnalyticsStartStorageMigrationMove;
|
|
62260
62976
|
exports.usePipelineAnalyticsWipeAllAnalytics = usePipelineAnalyticsWipeAllAnalytics;
|
|
62261
62977
|
exports.usePipelineAnalyticsWipeObjectEmbeddings = usePipelineAnalyticsWipeObjectEmbeddings;
|
|
62978
|
+
exports.usePipelineAnalyticsWriteRetrainArchive = usePipelineAnalyticsWriteRetrainArchive;
|
|
62262
62979
|
exports.usePipelineExecutorCacheFrameInPool = usePipelineExecutorCacheFrameInPool;
|
|
62263
62980
|
exports.usePipelineExecutorClearDeviceOverrides = usePipelineExecutorClearDeviceOverrides;
|
|
62264
62981
|
exports.usePipelineExecutorClearModelBoundDeviceOverrides = usePipelineExecutorClearModelBoundDeviceOverrides;
|
|
@@ -62294,10 +63011,12 @@ exports.usePipelineExecutorSetVideoPipelineSteps = usePipelineExecutorSetVideoPi
|
|
|
62294
63011
|
exports.usePipelineExecutorSpinEngine = usePipelineExecutorSpinEngine;
|
|
62295
63012
|
exports.usePipelineExecutorUncacheFrame = usePipelineExecutorUncacheFrame;
|
|
62296
63013
|
exports.usePipelineExecutorValidatePipeline = usePipelineExecutorValidatePipeline;
|
|
63014
|
+
exports.usePipelineOrchestratorAbortNodeBenchmark = usePipelineOrchestratorAbortNodeBenchmark;
|
|
62297
63015
|
exports.usePipelineOrchestratorApplyAutoTune = usePipelineOrchestratorApplyAutoTune;
|
|
62298
63016
|
exports.usePipelineOrchestratorApplyDeviceSettingsPatch = usePipelineOrchestratorApplyDeviceSettingsPatch;
|
|
62299
63017
|
exports.usePipelineOrchestratorAssignAudio = usePipelineOrchestratorAssignAudio;
|
|
62300
63018
|
exports.usePipelineOrchestratorAssignPipeline = usePipelineOrchestratorAssignPipeline;
|
|
63019
|
+
exports.usePipelineOrchestratorDeleteNodeBenchmarkResult = usePipelineOrchestratorDeleteNodeBenchmarkResult;
|
|
62301
63020
|
exports.usePipelineOrchestratorGetAgentLoad = usePipelineOrchestratorGetAgentLoad;
|
|
62302
63021
|
exports.usePipelineOrchestratorGetAgentSettings = usePipelineOrchestratorGetAgentSettings;
|
|
62303
63022
|
exports.usePipelineOrchestratorGetAudioAssignment = usePipelineOrchestratorGetAudioAssignment;
|
|
@@ -62316,6 +63035,9 @@ exports.usePipelineOrchestratorGetDeviceSettingsContribution = usePipelineOrches
|
|
|
62316
63035
|
exports.usePipelineOrchestratorGetEffectiveShares = usePipelineOrchestratorGetEffectiveShares;
|
|
62317
63036
|
exports.usePipelineOrchestratorGetGlobalMetrics = usePipelineOrchestratorGetGlobalMetrics;
|
|
62318
63037
|
exports.usePipelineOrchestratorGetIngestOwner = usePipelineOrchestratorGetIngestOwner;
|
|
63038
|
+
exports.usePipelineOrchestratorGetNodeBenchmarkCandidates = usePipelineOrchestratorGetNodeBenchmarkCandidates;
|
|
63039
|
+
exports.usePipelineOrchestratorGetNodeBenchmarkPreflight = usePipelineOrchestratorGetNodeBenchmarkPreflight;
|
|
63040
|
+
exports.usePipelineOrchestratorGetNodeBenchmarkStatus = usePipelineOrchestratorGetNodeBenchmarkStatus;
|
|
62319
63041
|
exports.usePipelineOrchestratorGetNodeInferenceDevices = usePipelineOrchestratorGetNodeInferenceDevices;
|
|
62320
63042
|
exports.usePipelineOrchestratorGetPipelineAssignment = usePipelineOrchestratorGetPipelineAssignment;
|
|
62321
63043
|
exports.usePipelineOrchestratorGetPipelineAssignments = usePipelineOrchestratorGetPipelineAssignments;
|
|
@@ -62323,6 +63045,7 @@ exports.usePipelineOrchestratorGetPipelineDevicePin = usePipelineOrchestratorGet
|
|
|
62323
63045
|
exports.usePipelineOrchestratorGetPlacementStats = usePipelineOrchestratorGetPlacementStats;
|
|
62324
63046
|
exports.usePipelineOrchestratorGetReconcileFromDiskStatus = usePipelineOrchestratorGetReconcileFromDiskStatus;
|
|
62325
63047
|
exports.usePipelineOrchestratorListAgentSettings = usePipelineOrchestratorListAgentSettings;
|
|
63048
|
+
exports.usePipelineOrchestratorListNodeBenchmarkResults = usePipelineOrchestratorListNodeBenchmarkResults;
|
|
62326
63049
|
exports.usePipelineOrchestratorPauseForStorageMigration = usePipelineOrchestratorPauseForStorageMigration;
|
|
62327
63050
|
exports.usePipelineOrchestratorPlanAutoTune = usePipelineOrchestratorPlanAutoTune;
|
|
62328
63051
|
exports.usePipelineOrchestratorRebalance = usePipelineOrchestratorRebalance;
|
|
@@ -62347,9 +63070,11 @@ exports.usePipelineOrchestratorSetCapabilityBinding = usePipelineOrchestratorSet
|
|
|
62347
63070
|
exports.usePipelineOrchestratorSetLoadBalancingShares = usePipelineOrchestratorSetLoadBalancingShares;
|
|
62348
63071
|
exports.usePipelineOrchestratorSetPipelineDevicePin = usePipelineOrchestratorSetPipelineDevicePin;
|
|
62349
63072
|
exports.usePipelineOrchestratorSetShareSource = usePipelineOrchestratorSetShareSource;
|
|
63073
|
+
exports.usePipelineOrchestratorStartNodeBenchmark = usePipelineOrchestratorStartNodeBenchmark;
|
|
62350
63074
|
exports.usePipelineOrchestratorSuggestShares = usePipelineOrchestratorSuggestShares;
|
|
62351
63075
|
exports.usePipelineOrchestratorUnassignAudio = usePipelineOrchestratorUnassignAudio;
|
|
62352
63076
|
exports.usePipelineOrchestratorUnassignPipeline = usePipelineOrchestratorUnassignPipeline;
|
|
63077
|
+
exports.usePipelineRunnerAbortNodeBench = usePipelineRunnerAbortNodeBench;
|
|
62353
63078
|
exports.usePipelineRunnerAttachCamera = usePipelineRunnerAttachCamera;
|
|
62354
63079
|
exports.usePipelineRunnerDetachCamera = usePipelineRunnerDetachCamera;
|
|
62355
63080
|
exports.usePipelineRunnerGetAllCameraMetrics = usePipelineRunnerGetAllCameraMetrics;
|
|
@@ -62358,6 +63083,7 @@ exports.usePipelineRunnerGetLocalCameras = usePipelineRunnerGetLocalCameras;
|
|
|
62358
63083
|
exports.usePipelineRunnerGetLocalLoad = usePipelineRunnerGetLocalLoad;
|
|
62359
63084
|
exports.usePipelineRunnerGetLocalMetrics = usePipelineRunnerGetLocalMetrics;
|
|
62360
63085
|
exports.usePipelineRunnerGetNativeCrop = usePipelineRunnerGetNativeCrop;
|
|
63086
|
+
exports.usePipelineRunnerGetNodeBenchStatus = usePipelineRunnerGetNodeBenchStatus;
|
|
62361
63087
|
exports.usePipelineRunnerGetParkedTrackFrame = usePipelineRunnerGetParkedTrackFrame;
|
|
62362
63088
|
exports.usePipelineRunnerParkTrackFrame = usePipelineRunnerParkTrackFrame;
|
|
62363
63089
|
exports.usePipelineRunnerReleaseParkedTrackFrames = usePipelineRunnerReleaseParkedTrackFrames;
|
|
@@ -62365,6 +63091,7 @@ exports.usePipelineRunnerReportMotion = usePipelineRunnerReportMotion;
|
|
|
62365
63091
|
exports.usePipelineRunnerRequestOccupancyBurst = usePipelineRunnerRequestOccupancyBurst;
|
|
62366
63092
|
exports.usePipelineRunnerRunDetailSubtree = usePipelineRunnerRunDetailSubtree;
|
|
62367
63093
|
exports.usePipelineRunnerRunStatelessStep = usePipelineRunnerRunStatelessStep;
|
|
63094
|
+
exports.usePipelineRunnerStartNodeBench = usePipelineRunnerStartNodeBench;
|
|
62368
63095
|
exports.usePlateGalleryAssignPlate = usePlateGalleryAssignPlate;
|
|
62369
63096
|
exports.usePlateGalleryAssignPlates = usePlateGalleryAssignPlates;
|
|
62370
63097
|
exports.usePlateGalleryCorrectPlateText = usePlateGalleryCorrectPlateText;
|
|
@@ -62640,8 +63367,11 @@ exports.useUserManagementCreateScopedToken = useUserManagementCreateScopedToken;
|
|
|
62640
63367
|
exports.useUserManagementCreateUser = useUserManagementCreateUser;
|
|
62641
63368
|
exports.useUserManagementDeleteUser = useUserManagementDeleteUser;
|
|
62642
63369
|
exports.useUserManagementDisableTotp = useUserManagementDisableTotp;
|
|
63370
|
+
exports.useUserManagementFindUserByExternalIdentity = useUserManagementFindUserByExternalIdentity;
|
|
62643
63371
|
exports.useUserManagementGetTotpStatus = useUserManagementGetTotpStatus;
|
|
63372
|
+
exports.useUserManagementLinkExternalIdentity = useUserManagementLinkExternalIdentity;
|
|
62644
63373
|
exports.useUserManagementListApiKeys = useUserManagementListApiKeys;
|
|
63374
|
+
exports.useUserManagementListExternalIdentities = useUserManagementListExternalIdentities;
|
|
62645
63375
|
exports.useUserManagementListOauthSessions = useUserManagementListOauthSessions;
|
|
62646
63376
|
exports.useUserManagementListScopedTokens = useUserManagementListScopedTokens;
|
|
62647
63377
|
exports.useUserManagementListUsers = useUserManagementListUsers;
|
|
@@ -62655,6 +63385,7 @@ exports.useUserManagementRevokeOauthSession = useUserManagementRevokeOauthSessio
|
|
|
62655
63385
|
exports.useUserManagementRevokeScopedToken = useUserManagementRevokeScopedToken;
|
|
62656
63386
|
exports.useUserManagementSetUserScopes = useUserManagementSetUserScopes;
|
|
62657
63387
|
exports.useUserManagementSetupTotp = useUserManagementSetupTotp;
|
|
63388
|
+
exports.useUserManagementUnlinkExternalIdentity = useUserManagementUnlinkExternalIdentity;
|
|
62658
63389
|
exports.useUserManagementUpdateUser = useUserManagementUpdateUser;
|
|
62659
63390
|
exports.useUserManagementValidateApiKey = useUserManagementValidateApiKey;
|
|
62660
63391
|
exports.useUserManagementValidateCredentials = useUserManagementValidateCredentials;
|