@camstack/ui-library 1.2.194 → 1.2.196
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/{MotionZonesSettings-7c3yiT5E.js → MotionZonesSettings-DHDfnZyU.js} +1 -1
- package/dist/{PrivacyMaskSettings-DfZAFg32.cjs → PrivacyMaskSettings-BqjXsXZR.cjs} +1 -33
- package/dist/{PrivacyMaskSettings-PkYs077O.js → PrivacyMaskSettings-DMkchqO1.js} +1 -33
- package/dist/{SceneMonitorEditor-BvDy3x3k.js → SceneMonitorEditor-vfWTm6qv.js} +1 -1
- package/dist/composites/data-table-filter-bar.d.ts +23 -0
- package/dist/composites/data-table-filter.d.ts +36 -0
- package/dist/composites/device-item/helpers.d.ts +18 -2
- package/dist/composites/device-item/index.d.ts +1 -0
- package/dist/composites/device-item/role-display-defaults-context.d.ts +21 -0
- package/dist/composites/device-list/table-layout.d.ts +5 -2
- package/dist/composites/device-meta.d.ts +15 -5
- package/dist/composites/icon-picker.d.ts +55 -0
- package/dist/composites/index.d.ts +7 -0
- package/dist/composites/use-data-table-filter.d.ts +16 -0
- package/dist/generated/system-hooks.d.ts +2 -0
- package/dist/index.cjs +2810 -1021
- package/dist/index.js +2802 -1026
- package/dist/primitives/badge.d.ts +1 -1
- package/dist/primitives/button.d.ts +1 -1
- package/dist/primitives/icon-button.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -375,6 +375,30 @@ var Bath = createLucideIcon("bath", [
|
|
|
375
375
|
* This source code is licensed under the ISC license.
|
|
376
376
|
* See the LICENSE file in the root directory of this source tree.
|
|
377
377
|
*/
|
|
378
|
+
var BatteryCharging = createLucideIcon("battery-charging", [
|
|
379
|
+
["path", {
|
|
380
|
+
d: "m11 7-3 5h4l-3 5",
|
|
381
|
+
key: "b4a64w"
|
|
382
|
+
}],
|
|
383
|
+
["path", {
|
|
384
|
+
d: "M14.856 6H16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.935",
|
|
385
|
+
key: "lre1cr"
|
|
386
|
+
}],
|
|
387
|
+
["path", {
|
|
388
|
+
d: "M22 14v-4",
|
|
389
|
+
key: "14q9d5"
|
|
390
|
+
}],
|
|
391
|
+
["path", {
|
|
392
|
+
d: "M5.14 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.936",
|
|
393
|
+
key: "13q5k0"
|
|
394
|
+
}]
|
|
395
|
+
]);
|
|
396
|
+
/**
|
|
397
|
+
* @license lucide-react v0.576.0 - ISC
|
|
398
|
+
*
|
|
399
|
+
* This source code is licensed under the ISC license.
|
|
400
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
401
|
+
*/
|
|
378
402
|
var BatteryFull = createLucideIcon("battery-full", [
|
|
379
403
|
["path", {
|
|
380
404
|
d: "M10 10v4",
|
|
@@ -504,6 +528,78 @@ var Battery = createLucideIcon("battery", [["path", {
|
|
|
504
528
|
* This source code is licensed under the ISC license.
|
|
505
529
|
* See the LICENSE file in the root directory of this source tree.
|
|
506
530
|
*/
|
|
531
|
+
var BedDouble = createLucideIcon("bed-double", [
|
|
532
|
+
["path", {
|
|
533
|
+
d: "M2 20v-8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v8",
|
|
534
|
+
key: "1k78r4"
|
|
535
|
+
}],
|
|
536
|
+
["path", {
|
|
537
|
+
d: "M4 10V6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4",
|
|
538
|
+
key: "fb3tl2"
|
|
539
|
+
}],
|
|
540
|
+
["path", {
|
|
541
|
+
d: "M12 4v6",
|
|
542
|
+
key: "1dcgq2"
|
|
543
|
+
}],
|
|
544
|
+
["path", {
|
|
545
|
+
d: "M2 18h20",
|
|
546
|
+
key: "ajqnye"
|
|
547
|
+
}]
|
|
548
|
+
]);
|
|
549
|
+
/**
|
|
550
|
+
* @license lucide-react v0.576.0 - ISC
|
|
551
|
+
*
|
|
552
|
+
* This source code is licensed under the ISC license.
|
|
553
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
554
|
+
*/
|
|
555
|
+
var Bed = createLucideIcon("bed", [
|
|
556
|
+
["path", {
|
|
557
|
+
d: "M2 4v16",
|
|
558
|
+
key: "vw9hq8"
|
|
559
|
+
}],
|
|
560
|
+
["path", {
|
|
561
|
+
d: "M2 8h18a2 2 0 0 1 2 2v10",
|
|
562
|
+
key: "1dgv2r"
|
|
563
|
+
}],
|
|
564
|
+
["path", {
|
|
565
|
+
d: "M2 17h20",
|
|
566
|
+
key: "18nfp3"
|
|
567
|
+
}],
|
|
568
|
+
["path", {
|
|
569
|
+
d: "M6 8v9",
|
|
570
|
+
key: "1yriud"
|
|
571
|
+
}]
|
|
572
|
+
]);
|
|
573
|
+
/**
|
|
574
|
+
* @license lucide-react v0.576.0 - ISC
|
|
575
|
+
*
|
|
576
|
+
* This source code is licensed under the ISC license.
|
|
577
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
578
|
+
*/
|
|
579
|
+
var BellOff = createLucideIcon("bell-off", [
|
|
580
|
+
["path", {
|
|
581
|
+
d: "M10.268 21a2 2 0 0 0 3.464 0",
|
|
582
|
+
key: "vwvbt9"
|
|
583
|
+
}],
|
|
584
|
+
["path", {
|
|
585
|
+
d: "M17 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 .258-1.742",
|
|
586
|
+
key: "178tsu"
|
|
587
|
+
}],
|
|
588
|
+
["path", {
|
|
589
|
+
d: "m2 2 20 20",
|
|
590
|
+
key: "1ooewy"
|
|
591
|
+
}],
|
|
592
|
+
["path", {
|
|
593
|
+
d: "M8.668 3.01A6 6 0 0 1 18 8c0 2.687.77 4.653 1.707 6.05",
|
|
594
|
+
key: "1hqiys"
|
|
595
|
+
}]
|
|
596
|
+
]);
|
|
597
|
+
/**
|
|
598
|
+
* @license lucide-react v0.576.0 - ISC
|
|
599
|
+
*
|
|
600
|
+
* This source code is licensed under the ISC license.
|
|
601
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
602
|
+
*/
|
|
507
603
|
var BellRing = createLucideIcon("bell-ring", [
|
|
508
604
|
["path", {
|
|
509
605
|
d: "M10.268 21a2 2 0 0 0 3.464 0",
|
|
@@ -641,6 +737,16 @@ var Blinds = createLucideIcon("blinds", [
|
|
|
641
737
|
* This source code is licensed under the ISC license.
|
|
642
738
|
* See the LICENSE file in the root directory of this source tree.
|
|
643
739
|
*/
|
|
740
|
+
var Bluetooth = createLucideIcon("bluetooth", [["path", {
|
|
741
|
+
d: "m7 7 10 10-5 5V2l5 5L7 17",
|
|
742
|
+
key: "1q5490"
|
|
743
|
+
}]]);
|
|
744
|
+
/**
|
|
745
|
+
* @license lucide-react v0.576.0 - ISC
|
|
746
|
+
*
|
|
747
|
+
* This source code is licensed under the ISC license.
|
|
748
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
749
|
+
*/
|
|
644
750
|
var Bone = createLucideIcon("bone", [["path", {
|
|
645
751
|
d: "M17 10c.7-.7 1.69 0 2.5 0a2.5 2.5 0 1 0 0-5 .5.5 0 0 1-.5-.5 2.5 2.5 0 1 0-5 0c0 .81.7 1.8 0 2.5l-7 7c-.7.7-1.69 0-2.5 0a2.5 2.5 0 0 0 0 5c.28 0 .5.22.5.5a2.5 2.5 0 1 0 5 0c0-.81-.7-1.8 0-2.5Z",
|
|
646
752
|
key: "w610uw"
|
|
@@ -707,6 +813,23 @@ var Box = createLucideIcon("box", [
|
|
|
707
813
|
* This source code is licensed under the ISC license.
|
|
708
814
|
* See the LICENSE file in the root directory of this source tree.
|
|
709
815
|
*/
|
|
816
|
+
var Briefcase = createLucideIcon("briefcase", [["path", {
|
|
817
|
+
d: "M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16",
|
|
818
|
+
key: "jecpp"
|
|
819
|
+
}], ["rect", {
|
|
820
|
+
width: "20",
|
|
821
|
+
height: "14",
|
|
822
|
+
x: "2",
|
|
823
|
+
y: "6",
|
|
824
|
+
rx: "2",
|
|
825
|
+
key: "i6l2r4"
|
|
826
|
+
}]]);
|
|
827
|
+
/**
|
|
828
|
+
* @license lucide-react v0.576.0 - ISC
|
|
829
|
+
*
|
|
830
|
+
* This source code is licensed under the ISC license.
|
|
831
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
832
|
+
*/
|
|
710
833
|
var Bug = createLucideIcon("bug", [
|
|
711
834
|
["path", {
|
|
712
835
|
d: "M12 20v-9",
|
|
@@ -759,6 +882,90 @@ var Bug = createLucideIcon("bug", [
|
|
|
759
882
|
* This source code is licensed under the ISC license.
|
|
760
883
|
* See the LICENSE file in the root directory of this source tree.
|
|
761
884
|
*/
|
|
885
|
+
var Building2 = createLucideIcon("building-2", [
|
|
886
|
+
["path", {
|
|
887
|
+
d: "M10 12h4",
|
|
888
|
+
key: "a56b0p"
|
|
889
|
+
}],
|
|
890
|
+
["path", {
|
|
891
|
+
d: "M10 8h4",
|
|
892
|
+
key: "1sr2af"
|
|
893
|
+
}],
|
|
894
|
+
["path", {
|
|
895
|
+
d: "M14 21v-3a2 2 0 0 0-4 0v3",
|
|
896
|
+
key: "1rgiei"
|
|
897
|
+
}],
|
|
898
|
+
["path", {
|
|
899
|
+
d: "M6 10H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2",
|
|
900
|
+
key: "secmi2"
|
|
901
|
+
}],
|
|
902
|
+
["path", {
|
|
903
|
+
d: "M6 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16",
|
|
904
|
+
key: "16ra0t"
|
|
905
|
+
}]
|
|
906
|
+
]);
|
|
907
|
+
/**
|
|
908
|
+
* @license lucide-react v0.576.0 - ISC
|
|
909
|
+
*
|
|
910
|
+
* This source code is licensed under the ISC license.
|
|
911
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
912
|
+
*/
|
|
913
|
+
var Building = createLucideIcon("building", [
|
|
914
|
+
["path", {
|
|
915
|
+
d: "M12 10h.01",
|
|
916
|
+
key: "1nrarc"
|
|
917
|
+
}],
|
|
918
|
+
["path", {
|
|
919
|
+
d: "M12 14h.01",
|
|
920
|
+
key: "1etili"
|
|
921
|
+
}],
|
|
922
|
+
["path", {
|
|
923
|
+
d: "M12 6h.01",
|
|
924
|
+
key: "1vi96p"
|
|
925
|
+
}],
|
|
926
|
+
["path", {
|
|
927
|
+
d: "M16 10h.01",
|
|
928
|
+
key: "1m94wz"
|
|
929
|
+
}],
|
|
930
|
+
["path", {
|
|
931
|
+
d: "M16 14h.01",
|
|
932
|
+
key: "1gbofw"
|
|
933
|
+
}],
|
|
934
|
+
["path", {
|
|
935
|
+
d: "M16 6h.01",
|
|
936
|
+
key: "1x0f13"
|
|
937
|
+
}],
|
|
938
|
+
["path", {
|
|
939
|
+
d: "M8 10h.01",
|
|
940
|
+
key: "19clt8"
|
|
941
|
+
}],
|
|
942
|
+
["path", {
|
|
943
|
+
d: "M8 14h.01",
|
|
944
|
+
key: "6423bh"
|
|
945
|
+
}],
|
|
946
|
+
["path", {
|
|
947
|
+
d: "M8 6h.01",
|
|
948
|
+
key: "1dz90k"
|
|
949
|
+
}],
|
|
950
|
+
["path", {
|
|
951
|
+
d: "M9 22v-3a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3",
|
|
952
|
+
key: "cabbwy"
|
|
953
|
+
}],
|
|
954
|
+
["rect", {
|
|
955
|
+
x: "4",
|
|
956
|
+
y: "2",
|
|
957
|
+
width: "16",
|
|
958
|
+
height: "20",
|
|
959
|
+
rx: "2",
|
|
960
|
+
key: "1uxh74"
|
|
961
|
+
}]
|
|
962
|
+
]);
|
|
963
|
+
/**
|
|
964
|
+
* @license lucide-react v0.576.0 - ISC
|
|
965
|
+
*
|
|
966
|
+
* This source code is licensed under the ISC license.
|
|
967
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
968
|
+
*/
|
|
762
969
|
var Bus = createLucideIcon("bus", [
|
|
763
970
|
["path", {
|
|
764
971
|
d: "M8 6v6",
|
|
@@ -835,6 +1042,34 @@ var Cable = createLucideIcon("cable", [
|
|
|
835
1042
|
* This source code is licensed under the ISC license.
|
|
836
1043
|
* See the LICENSE file in the root directory of this source tree.
|
|
837
1044
|
*/
|
|
1045
|
+
var Calendar = createLucideIcon("calendar", [
|
|
1046
|
+
["path", {
|
|
1047
|
+
d: "M8 2v4",
|
|
1048
|
+
key: "1cmpym"
|
|
1049
|
+
}],
|
|
1050
|
+
["path", {
|
|
1051
|
+
d: "M16 2v4",
|
|
1052
|
+
key: "4m81vk"
|
|
1053
|
+
}],
|
|
1054
|
+
["rect", {
|
|
1055
|
+
width: "18",
|
|
1056
|
+
height: "18",
|
|
1057
|
+
x: "3",
|
|
1058
|
+
y: "4",
|
|
1059
|
+
rx: "2",
|
|
1060
|
+
key: "1hopcy"
|
|
1061
|
+
}],
|
|
1062
|
+
["path", {
|
|
1063
|
+
d: "M3 10h18",
|
|
1064
|
+
key: "8toen8"
|
|
1065
|
+
}]
|
|
1066
|
+
]);
|
|
1067
|
+
/**
|
|
1068
|
+
* @license lucide-react v0.576.0 - ISC
|
|
1069
|
+
*
|
|
1070
|
+
* This source code is licensed under the ISC license.
|
|
1071
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1072
|
+
*/
|
|
838
1073
|
var Camera = createLucideIcon("camera", [["path", {
|
|
839
1074
|
d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z",
|
|
840
1075
|
key: "18u6gg"
|
|
@@ -1078,6 +1313,21 @@ var CircleDot = createLucideIcon("circle-dot", [["circle", {
|
|
|
1078
1313
|
* This source code is licensed under the ISC license.
|
|
1079
1314
|
* See the LICENSE file in the root directory of this source tree.
|
|
1080
1315
|
*/
|
|
1316
|
+
var CircleParking = createLucideIcon("circle-parking", [["circle", {
|
|
1317
|
+
cx: "12",
|
|
1318
|
+
cy: "12",
|
|
1319
|
+
r: "10",
|
|
1320
|
+
key: "1mglay"
|
|
1321
|
+
}], ["path", {
|
|
1322
|
+
d: "M9 17V7h4a3 3 0 0 1 0 6H9",
|
|
1323
|
+
key: "1dfk2c"
|
|
1324
|
+
}]]);
|
|
1325
|
+
/**
|
|
1326
|
+
* @license lucide-react v0.576.0 - ISC
|
|
1327
|
+
*
|
|
1328
|
+
* This source code is licensed under the ISC license.
|
|
1329
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1330
|
+
*/
|
|
1081
1331
|
var CirclePause = createLucideIcon("circle-pause", [
|
|
1082
1332
|
["circle", {
|
|
1083
1333
|
cx: "12",
|
|
@@ -1433,6 +1683,30 @@ var Cog = createLucideIcon("cog", [
|
|
|
1433
1683
|
* This source code is licensed under the ISC license.
|
|
1434
1684
|
* See the LICENSE file in the root directory of this source tree.
|
|
1435
1685
|
*/
|
|
1686
|
+
var CookingPot = createLucideIcon("cooking-pot", [
|
|
1687
|
+
["path", {
|
|
1688
|
+
d: "M2 12h20",
|
|
1689
|
+
key: "9i4pu4"
|
|
1690
|
+
}],
|
|
1691
|
+
["path", {
|
|
1692
|
+
d: "M20 12v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8",
|
|
1693
|
+
key: "u0tga0"
|
|
1694
|
+
}],
|
|
1695
|
+
["path", {
|
|
1696
|
+
d: "m4 8 16-4",
|
|
1697
|
+
key: "16g0ng"
|
|
1698
|
+
}],
|
|
1699
|
+
["path", {
|
|
1700
|
+
d: "m8.86 6.78-.45-1.81a2 2 0 0 1 1.45-2.43l1.94-.48a2 2 0 0 1 2.43 1.46l.45 1.8",
|
|
1701
|
+
key: "12cejc"
|
|
1702
|
+
}]
|
|
1703
|
+
]);
|
|
1704
|
+
/**
|
|
1705
|
+
* @license lucide-react v0.576.0 - ISC
|
|
1706
|
+
*
|
|
1707
|
+
* This source code is licensed under the ISC license.
|
|
1708
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
1709
|
+
*/
|
|
1436
1710
|
var CopyPlus = createLucideIcon("copy-plus", [
|
|
1437
1711
|
["line", {
|
|
1438
1712
|
x1: "15",
|
|
@@ -1719,6 +1993,38 @@ var Download = createLucideIcon("download", [
|
|
|
1719
1993
|
* This source code is licensed under the ISC license.
|
|
1720
1994
|
* See the LICENSE file in the root directory of this source tree.
|
|
1721
1995
|
*/
|
|
1996
|
+
var Drill = createLucideIcon("drill", [
|
|
1997
|
+
["path", {
|
|
1998
|
+
d: "M10 18a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a3 3 0 0 1-3-3 1 1 0 0 1 1-1z",
|
|
1999
|
+
key: "ioqxb1"
|
|
2000
|
+
}],
|
|
2001
|
+
["path", {
|
|
2002
|
+
d: "M13 10H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1l-.81 3.242a1 1 0 0 1-.97.758H8",
|
|
2003
|
+
key: "1rs59n"
|
|
2004
|
+
}],
|
|
2005
|
+
["path", {
|
|
2006
|
+
d: "M14 4h3a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-3",
|
|
2007
|
+
key: "105ega"
|
|
2008
|
+
}],
|
|
2009
|
+
["path", {
|
|
2010
|
+
d: "M18 6h4",
|
|
2011
|
+
key: "66u95g"
|
|
2012
|
+
}],
|
|
2013
|
+
["path", {
|
|
2014
|
+
d: "m5 10-2 8",
|
|
2015
|
+
key: "xt2lic"
|
|
2016
|
+
}],
|
|
2017
|
+
["path", {
|
|
2018
|
+
d: "m7 18 2-8",
|
|
2019
|
+
key: "1bzku2"
|
|
2020
|
+
}]
|
|
2021
|
+
]);
|
|
2022
|
+
/**
|
|
2023
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2024
|
+
*
|
|
2025
|
+
* This source code is licensed under the ISC license.
|
|
2026
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2027
|
+
*/
|
|
1722
2028
|
var Droplet = createLucideIcon("droplet", [["path", {
|
|
1723
2029
|
d: "M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z",
|
|
1724
2030
|
key: "c7niix"
|
|
@@ -1742,6 +2048,34 @@ var Droplets = createLucideIcon("droplets", [["path", {
|
|
|
1742
2048
|
* This source code is licensed under the ISC license.
|
|
1743
2049
|
* See the LICENSE file in the root directory of this source tree.
|
|
1744
2050
|
*/
|
|
2051
|
+
var Dumbbell = createLucideIcon("dumbbell", [
|
|
2052
|
+
["path", {
|
|
2053
|
+
d: "M17.596 12.768a2 2 0 1 0 2.829-2.829l-1.768-1.767a2 2 0 0 0 2.828-2.829l-2.828-2.828a2 2 0 0 0-2.829 2.828l-1.767-1.768a2 2 0 1 0-2.829 2.829z",
|
|
2054
|
+
key: "9m4mmf"
|
|
2055
|
+
}],
|
|
2056
|
+
["path", {
|
|
2057
|
+
d: "m2.5 21.5 1.4-1.4",
|
|
2058
|
+
key: "17g3f0"
|
|
2059
|
+
}],
|
|
2060
|
+
["path", {
|
|
2061
|
+
d: "m20.1 3.9 1.4-1.4",
|
|
2062
|
+
key: "1qn309"
|
|
2063
|
+
}],
|
|
2064
|
+
["path", {
|
|
2065
|
+
d: "M5.343 21.485a2 2 0 1 0 2.829-2.828l1.767 1.768a2 2 0 1 0 2.829-2.829l-6.364-6.364a2 2 0 1 0-2.829 2.829l1.768 1.767a2 2 0 0 0-2.828 2.829z",
|
|
2066
|
+
key: "1t2c92"
|
|
2067
|
+
}],
|
|
2068
|
+
["path", {
|
|
2069
|
+
d: "m9.6 14.4 4.8-4.8",
|
|
2070
|
+
key: "6umqxw"
|
|
2071
|
+
}]
|
|
2072
|
+
]);
|
|
2073
|
+
/**
|
|
2074
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2075
|
+
*
|
|
2076
|
+
* This source code is licensed under the ISC license.
|
|
2077
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2078
|
+
*/
|
|
1745
2079
|
var EllipsisVertical = createLucideIcon("ellipsis-vertical", [
|
|
1746
2080
|
["circle", {
|
|
1747
2081
|
cx: "12",
|
|
@@ -1860,6 +2194,42 @@ var Feather = createLucideIcon("feather", [
|
|
|
1860
2194
|
* This source code is licensed under the ISC license.
|
|
1861
2195
|
* See the LICENSE file in the root directory of this source tree.
|
|
1862
2196
|
*/
|
|
2197
|
+
var Fence = createLucideIcon("fence", [
|
|
2198
|
+
["path", {
|
|
2199
|
+
d: "M4 3 2 5v15c0 .6.4 1 1 1h2c.6 0 1-.4 1-1V5Z",
|
|
2200
|
+
key: "1n2rgs"
|
|
2201
|
+
}],
|
|
2202
|
+
["path", {
|
|
2203
|
+
d: "M6 8h4",
|
|
2204
|
+
key: "utf9t1"
|
|
2205
|
+
}],
|
|
2206
|
+
["path", {
|
|
2207
|
+
d: "M6 18h4",
|
|
2208
|
+
key: "12yh4b"
|
|
2209
|
+
}],
|
|
2210
|
+
["path", {
|
|
2211
|
+
d: "m12 3-2 2v15c0 .6.4 1 1 1h2c.6 0 1-.4 1-1V5Z",
|
|
2212
|
+
key: "3ha7mj"
|
|
2213
|
+
}],
|
|
2214
|
+
["path", {
|
|
2215
|
+
d: "M14 8h4",
|
|
2216
|
+
key: "1r8wg2"
|
|
2217
|
+
}],
|
|
2218
|
+
["path", {
|
|
2219
|
+
d: "M14 18h4",
|
|
2220
|
+
key: "1t3kbu"
|
|
2221
|
+
}],
|
|
2222
|
+
["path", {
|
|
2223
|
+
d: "m20 3-2 2v15c0 .6.4 1 1 1h2c.6 0 1-.4 1-1V5Z",
|
|
2224
|
+
key: "dfd4e2"
|
|
2225
|
+
}]
|
|
2226
|
+
]);
|
|
2227
|
+
/**
|
|
2228
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2229
|
+
*
|
|
2230
|
+
* This source code is licensed under the ISC license.
|
|
2231
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2232
|
+
*/
|
|
1863
2233
|
var Film = createLucideIcon("film", [
|
|
1864
2234
|
["rect", {
|
|
1865
2235
|
width: "18",
|
|
@@ -1982,6 +2352,56 @@ var FlaskConical = createLucideIcon("flask-conical", [
|
|
|
1982
2352
|
* This source code is licensed under the ISC license.
|
|
1983
2353
|
* See the LICENSE file in the root directory of this source tree.
|
|
1984
2354
|
*/
|
|
2355
|
+
var Flower = createLucideIcon("flower", [
|
|
2356
|
+
["circle", {
|
|
2357
|
+
cx: "12",
|
|
2358
|
+
cy: "12",
|
|
2359
|
+
r: "3",
|
|
2360
|
+
key: "1v7zrd"
|
|
2361
|
+
}],
|
|
2362
|
+
["path", {
|
|
2363
|
+
d: "M12 16.5A4.5 4.5 0 1 1 7.5 12 4.5 4.5 0 1 1 12 7.5a4.5 4.5 0 1 1 4.5 4.5 4.5 4.5 0 1 1-4.5 4.5",
|
|
2364
|
+
key: "14wa3c"
|
|
2365
|
+
}],
|
|
2366
|
+
["path", {
|
|
2367
|
+
d: "M12 7.5V9",
|
|
2368
|
+
key: "1oy5b0"
|
|
2369
|
+
}],
|
|
2370
|
+
["path", {
|
|
2371
|
+
d: "M7.5 12H9",
|
|
2372
|
+
key: "eltsq1"
|
|
2373
|
+
}],
|
|
2374
|
+
["path", {
|
|
2375
|
+
d: "M16.5 12H15",
|
|
2376
|
+
key: "vk5kw4"
|
|
2377
|
+
}],
|
|
2378
|
+
["path", {
|
|
2379
|
+
d: "M12 16.5V15",
|
|
2380
|
+
key: "k7eayi"
|
|
2381
|
+
}],
|
|
2382
|
+
["path", {
|
|
2383
|
+
d: "m8 8 1.88 1.88",
|
|
2384
|
+
key: "nxy4qf"
|
|
2385
|
+
}],
|
|
2386
|
+
["path", {
|
|
2387
|
+
d: "M14.12 9.88 16 8",
|
|
2388
|
+
key: "1lst6k"
|
|
2389
|
+
}],
|
|
2390
|
+
["path", {
|
|
2391
|
+
d: "m8 16 1.88-1.88",
|
|
2392
|
+
key: "h2eex1"
|
|
2393
|
+
}],
|
|
2394
|
+
["path", {
|
|
2395
|
+
d: "M14.12 14.12 16 16",
|
|
2396
|
+
key: "uqkrx3"
|
|
2397
|
+
}]
|
|
2398
|
+
]);
|
|
2399
|
+
/**
|
|
2400
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2401
|
+
*
|
|
2402
|
+
* This source code is licensed under the ISC license.
|
|
2403
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2404
|
+
*/
|
|
1985
2405
|
var Footprints = createLucideIcon("footprints", [
|
|
1986
2406
|
["path", {
|
|
1987
2407
|
d: "M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z",
|
|
@@ -2241,6 +2661,77 @@ var Hash = createLucideIcon("hash", [
|
|
|
2241
2661
|
* This source code is licensed under the ISC license.
|
|
2242
2662
|
* See the LICENSE file in the root directory of this source tree.
|
|
2243
2663
|
*/
|
|
2664
|
+
var HeartPulse = createLucideIcon("heart-pulse", [["path", {
|
|
2665
|
+
d: "M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5",
|
|
2666
|
+
key: "mvr1a0"
|
|
2667
|
+
}], ["path", {
|
|
2668
|
+
d: "M3.22 13H9.5l.5-1 2 4.5 2-7 1.5 3.5h5.27",
|
|
2669
|
+
key: "auskq0"
|
|
2670
|
+
}]]);
|
|
2671
|
+
/**
|
|
2672
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2673
|
+
*
|
|
2674
|
+
* This source code is licensed under the ISC license.
|
|
2675
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2676
|
+
*/
|
|
2677
|
+
var Heart = createLucideIcon("heart", [["path", {
|
|
2678
|
+
d: "M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5",
|
|
2679
|
+
key: "mvr1a0"
|
|
2680
|
+
}]]);
|
|
2681
|
+
/**
|
|
2682
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2683
|
+
*
|
|
2684
|
+
* This source code is licensed under the ISC license.
|
|
2685
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2686
|
+
*/
|
|
2687
|
+
var Heater = createLucideIcon("heater", [
|
|
2688
|
+
["path", {
|
|
2689
|
+
d: "M11 8c2-3-2-3 0-6",
|
|
2690
|
+
key: "1ldv5m"
|
|
2691
|
+
}],
|
|
2692
|
+
["path", {
|
|
2693
|
+
d: "M15.5 8c2-3-2-3 0-6",
|
|
2694
|
+
key: "1otqoz"
|
|
2695
|
+
}],
|
|
2696
|
+
["path", {
|
|
2697
|
+
d: "M6 10h.01",
|
|
2698
|
+
key: "1lbq93"
|
|
2699
|
+
}],
|
|
2700
|
+
["path", {
|
|
2701
|
+
d: "M6 14h.01",
|
|
2702
|
+
key: "zudwn7"
|
|
2703
|
+
}],
|
|
2704
|
+
["path", {
|
|
2705
|
+
d: "M10 16v-4",
|
|
2706
|
+
key: "1c25yv"
|
|
2707
|
+
}],
|
|
2708
|
+
["path", {
|
|
2709
|
+
d: "M14 16v-4",
|
|
2710
|
+
key: "1dkbt8"
|
|
2711
|
+
}],
|
|
2712
|
+
["path", {
|
|
2713
|
+
d: "M18 16v-4",
|
|
2714
|
+
key: "1yg9me"
|
|
2715
|
+
}],
|
|
2716
|
+
["path", {
|
|
2717
|
+
d: "M20 6a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3",
|
|
2718
|
+
key: "1ubg90"
|
|
2719
|
+
}],
|
|
2720
|
+
["path", {
|
|
2721
|
+
d: "M5 20v2",
|
|
2722
|
+
key: "1abpe8"
|
|
2723
|
+
}],
|
|
2724
|
+
["path", {
|
|
2725
|
+
d: "M19 20v2",
|
|
2726
|
+
key: "kqn6ft"
|
|
2727
|
+
}]
|
|
2728
|
+
]);
|
|
2729
|
+
/**
|
|
2730
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2731
|
+
*
|
|
2732
|
+
* This source code is licensed under the ISC license.
|
|
2733
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2734
|
+
*/
|
|
2244
2735
|
var House = createLucideIcon("house", [["path", {
|
|
2245
2736
|
d: "M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",
|
|
2246
2737
|
key: "5wwlr5"
|
|
@@ -2357,6 +2848,141 @@ var Info = createLucideIcon("info", [
|
|
|
2357
2848
|
* This source code is licensed under the ISC license.
|
|
2358
2849
|
* See the LICENSE file in the root directory of this source tree.
|
|
2359
2850
|
*/
|
|
2851
|
+
var KeyRound = createLucideIcon("key-round", [["path", {
|
|
2852
|
+
d: "M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z",
|
|
2853
|
+
key: "1s6t7t"
|
|
2854
|
+
}], ["circle", {
|
|
2855
|
+
cx: "16.5",
|
|
2856
|
+
cy: "7.5",
|
|
2857
|
+
r: ".5",
|
|
2858
|
+
fill: "currentColor",
|
|
2859
|
+
key: "w0ekpg"
|
|
2860
|
+
}]]);
|
|
2861
|
+
/**
|
|
2862
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2863
|
+
*
|
|
2864
|
+
* This source code is licensed under the ISC license.
|
|
2865
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2866
|
+
*/
|
|
2867
|
+
var Key = createLucideIcon("key", [
|
|
2868
|
+
["path", {
|
|
2869
|
+
d: "m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4",
|
|
2870
|
+
key: "g0fldk"
|
|
2871
|
+
}],
|
|
2872
|
+
["path", {
|
|
2873
|
+
d: "m21 2-9.6 9.6",
|
|
2874
|
+
key: "1j0ho8"
|
|
2875
|
+
}],
|
|
2876
|
+
["circle", {
|
|
2877
|
+
cx: "7.5",
|
|
2878
|
+
cy: "15.5",
|
|
2879
|
+
r: "5.5",
|
|
2880
|
+
key: "yqb3hr"
|
|
2881
|
+
}]
|
|
2882
|
+
]);
|
|
2883
|
+
/**
|
|
2884
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2885
|
+
*
|
|
2886
|
+
* This source code is licensed under the ISC license.
|
|
2887
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2888
|
+
*/
|
|
2889
|
+
var LampCeiling = createLucideIcon("lamp-ceiling", [
|
|
2890
|
+
["path", {
|
|
2891
|
+
d: "M12 2v5",
|
|
2892
|
+
key: "nd4vlx"
|
|
2893
|
+
}],
|
|
2894
|
+
["path", {
|
|
2895
|
+
d: "M14.829 15.998a3 3 0 1 1-5.658 0",
|
|
2896
|
+
key: "1pybiy"
|
|
2897
|
+
}],
|
|
2898
|
+
["path", {
|
|
2899
|
+
d: "M20.92 14.606A1 1 0 0 1 20 16H4a1 1 0 0 1-.92-1.394l3-7A1 1 0 0 1 7 7h10a1 1 0 0 1 .92.606z",
|
|
2900
|
+
key: "ma1wor"
|
|
2901
|
+
}]
|
|
2902
|
+
]);
|
|
2903
|
+
/**
|
|
2904
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2905
|
+
*
|
|
2906
|
+
* This source code is licensed under the ISC license.
|
|
2907
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2908
|
+
*/
|
|
2909
|
+
var LampDesk = createLucideIcon("lamp-desk", [
|
|
2910
|
+
["path", {
|
|
2911
|
+
d: "M10.293 2.293a1 1 0 0 1 1.414 0l2.5 2.5 5.994 1.227a1 1 0 0 1 .506 1.687l-7 7a1 1 0 0 1-1.687-.506l-1.227-5.994-2.5-2.5a1 1 0 0 1 0-1.414z",
|
|
2912
|
+
key: "sb8slu"
|
|
2913
|
+
}],
|
|
2914
|
+
["path", {
|
|
2915
|
+
d: "m14.207 4.793-3.414 3.414",
|
|
2916
|
+
key: "m2x3oj"
|
|
2917
|
+
}],
|
|
2918
|
+
["path", {
|
|
2919
|
+
d: "M3 20a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1z",
|
|
2920
|
+
key: "8b3myj"
|
|
2921
|
+
}],
|
|
2922
|
+
["path", {
|
|
2923
|
+
d: "m9.086 6.5-4.793 4.793a1 1 0 0 0-.18 1.17L7 18",
|
|
2924
|
+
key: "43s6cu"
|
|
2925
|
+
}]
|
|
2926
|
+
]);
|
|
2927
|
+
/**
|
|
2928
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2929
|
+
*
|
|
2930
|
+
* This source code is licensed under the ISC license.
|
|
2931
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2932
|
+
*/
|
|
2933
|
+
var LampFloor = createLucideIcon("lamp-floor", [
|
|
2934
|
+
["path", {
|
|
2935
|
+
d: "M12 10v12",
|
|
2936
|
+
key: "6ubwww"
|
|
2937
|
+
}],
|
|
2938
|
+
["path", {
|
|
2939
|
+
d: "M17.929 7.629A1 1 0 0 1 17 9H7a1 1 0 0 1-.928-1.371l2-5A1 1 0 0 1 9 2h6a1 1 0 0 1 .928.629z",
|
|
2940
|
+
key: "1o95gh"
|
|
2941
|
+
}],
|
|
2942
|
+
["path", {
|
|
2943
|
+
d: "M9 22h6",
|
|
2944
|
+
key: "1rlq3v"
|
|
2945
|
+
}]
|
|
2946
|
+
]);
|
|
2947
|
+
/**
|
|
2948
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2949
|
+
*
|
|
2950
|
+
* This source code is licensed under the ISC license.
|
|
2951
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2952
|
+
*/
|
|
2953
|
+
var Lamp = createLucideIcon("lamp", [
|
|
2954
|
+
["path", {
|
|
2955
|
+
d: "M12 12v6",
|
|
2956
|
+
key: "3ahymv"
|
|
2957
|
+
}],
|
|
2958
|
+
["path", {
|
|
2959
|
+
d: "M4.077 10.615A1 1 0 0 0 5 12h14a1 1 0 0 0 .923-1.385l-3.077-7.384A2 2 0 0 0 15 2H9a2 2 0 0 0-1.846 1.23Z",
|
|
2960
|
+
key: "1l7kg2"
|
|
2961
|
+
}],
|
|
2962
|
+
["path", {
|
|
2963
|
+
d: "M8 20a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1z",
|
|
2964
|
+
key: "1mmzpi"
|
|
2965
|
+
}]
|
|
2966
|
+
]);
|
|
2967
|
+
/**
|
|
2968
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2969
|
+
*
|
|
2970
|
+
* This source code is licensed under the ISC license.
|
|
2971
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2972
|
+
*/
|
|
2973
|
+
var Laptop = createLucideIcon("laptop", [["path", {
|
|
2974
|
+
d: "M18 5a2 2 0 0 1 2 2v8.526a2 2 0 0 0 .212.897l1.068 2.127a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45l1.068-2.127A2 2 0 0 0 4 15.526V7a2 2 0 0 1 2-2z",
|
|
2975
|
+
key: "1pdavp"
|
|
2976
|
+
}], ["path", {
|
|
2977
|
+
d: "M20.054 15.987H3.946",
|
|
2978
|
+
key: "14rxg9"
|
|
2979
|
+
}]]);
|
|
2980
|
+
/**
|
|
2981
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2982
|
+
*
|
|
2983
|
+
* This source code is licensed under the ISC license.
|
|
2984
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2985
|
+
*/
|
|
2360
2986
|
var Laugh = createLucideIcon("laugh", [
|
|
2361
2987
|
["circle", {
|
|
2362
2988
|
cx: "12",
|
|
@@ -2656,6 +3282,33 @@ var Mail = createLucideIcon("mail", [["path", {
|
|
|
2656
3282
|
* This source code is licensed under the ISC license.
|
|
2657
3283
|
* See the LICENSE file in the root directory of this source tree.
|
|
2658
3284
|
*/
|
|
3285
|
+
var Mailbox = createLucideIcon("mailbox", [
|
|
3286
|
+
["path", {
|
|
3287
|
+
d: "M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z",
|
|
3288
|
+
key: "1lbycx"
|
|
3289
|
+
}],
|
|
3290
|
+
["polyline", {
|
|
3291
|
+
points: "15,9 18,9 18,11",
|
|
3292
|
+
key: "1pm9c0"
|
|
3293
|
+
}],
|
|
3294
|
+
["path", {
|
|
3295
|
+
d: "M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2",
|
|
3296
|
+
key: "15i455"
|
|
3297
|
+
}],
|
|
3298
|
+
["line", {
|
|
3299
|
+
x1: "6",
|
|
3300
|
+
x2: "7",
|
|
3301
|
+
y1: "10",
|
|
3302
|
+
y2: "10",
|
|
3303
|
+
key: "1e2scm"
|
|
3304
|
+
}]
|
|
3305
|
+
]);
|
|
3306
|
+
/**
|
|
3307
|
+
* @license lucide-react v0.576.0 - ISC
|
|
3308
|
+
*
|
|
3309
|
+
* This source code is licensed under the ISC license.
|
|
3310
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
3311
|
+
*/
|
|
2659
3312
|
var MapPin = createLucideIcon("map-pin", [["path", {
|
|
2660
3313
|
d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0",
|
|
2661
3314
|
key: "1r0f0z"
|
|
@@ -2745,6 +3398,38 @@ var MessageSquare = createLucideIcon("message-square", [["path", {
|
|
|
2745
3398
|
* This source code is licensed under the ISC license.
|
|
2746
3399
|
* See the LICENSE file in the root directory of this source tree.
|
|
2747
3400
|
*/
|
|
3401
|
+
var MicOff = createLucideIcon("mic-off", [
|
|
3402
|
+
["path", {
|
|
3403
|
+
d: "M12 19v3",
|
|
3404
|
+
key: "npa21l"
|
|
3405
|
+
}],
|
|
3406
|
+
["path", {
|
|
3407
|
+
d: "M15 9.34V5a3 3 0 0 0-5.68-1.33",
|
|
3408
|
+
key: "1gzdoj"
|
|
3409
|
+
}],
|
|
3410
|
+
["path", {
|
|
3411
|
+
d: "M16.95 16.95A7 7 0 0 1 5 12v-2",
|
|
3412
|
+
key: "cqa7eg"
|
|
3413
|
+
}],
|
|
3414
|
+
["path", {
|
|
3415
|
+
d: "M18.89 13.23A7 7 0 0 0 19 12v-2",
|
|
3416
|
+
key: "16hl24"
|
|
3417
|
+
}],
|
|
3418
|
+
["path", {
|
|
3419
|
+
d: "m2 2 20 20",
|
|
3420
|
+
key: "1ooewy"
|
|
3421
|
+
}],
|
|
3422
|
+
["path", {
|
|
3423
|
+
d: "M9 9v3a3 3 0 0 0 5.12 2.12",
|
|
3424
|
+
key: "r2i35w"
|
|
3425
|
+
}]
|
|
3426
|
+
]);
|
|
3427
|
+
/**
|
|
3428
|
+
* @license lucide-react v0.576.0 - ISC
|
|
3429
|
+
*
|
|
3430
|
+
* This source code is licensed under the ISC license.
|
|
3431
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
3432
|
+
*/
|
|
2748
3433
|
var Mic = createLucideIcon("mic", [
|
|
2749
3434
|
["path", {
|
|
2750
3435
|
d: "M12 19v3",
|
|
@@ -2769,6 +3454,42 @@ var Mic = createLucideIcon("mic", [
|
|
|
2769
3454
|
* This source code is licensed under the ISC license.
|
|
2770
3455
|
* See the LICENSE file in the root directory of this source tree.
|
|
2771
3456
|
*/
|
|
3457
|
+
var Microwave = createLucideIcon("microwave", [
|
|
3458
|
+
["rect", {
|
|
3459
|
+
width: "20",
|
|
3460
|
+
height: "15",
|
|
3461
|
+
x: "2",
|
|
3462
|
+
y: "4",
|
|
3463
|
+
rx: "2",
|
|
3464
|
+
key: "2no95f"
|
|
3465
|
+
}],
|
|
3466
|
+
["rect", {
|
|
3467
|
+
width: "8",
|
|
3468
|
+
height: "7",
|
|
3469
|
+
x: "6",
|
|
3470
|
+
y: "8",
|
|
3471
|
+
rx: "1",
|
|
3472
|
+
key: "zh9wx"
|
|
3473
|
+
}],
|
|
3474
|
+
["path", {
|
|
3475
|
+
d: "M18 8v7",
|
|
3476
|
+
key: "o5zi4n"
|
|
3477
|
+
}],
|
|
3478
|
+
["path", {
|
|
3479
|
+
d: "M6 19v2",
|
|
3480
|
+
key: "1loha6"
|
|
3481
|
+
}],
|
|
3482
|
+
["path", {
|
|
3483
|
+
d: "M18 19v2",
|
|
3484
|
+
key: "1dawf0"
|
|
3485
|
+
}]
|
|
3486
|
+
]);
|
|
3487
|
+
/**
|
|
3488
|
+
* @license lucide-react v0.576.0 - ISC
|
|
3489
|
+
*
|
|
3490
|
+
* This source code is licensed under the ISC license.
|
|
3491
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
3492
|
+
*/
|
|
2772
3493
|
var Minimize2 = createLucideIcon("minimize-2", [
|
|
2773
3494
|
["path", {
|
|
2774
3495
|
d: "m14 10 7-7",
|
|
@@ -2831,6 +3552,36 @@ var MonitorPlay = createLucideIcon("monitor-play", [
|
|
|
2831
3552
|
* This source code is licensed under the ISC license.
|
|
2832
3553
|
* See the LICENSE file in the root directory of this source tree.
|
|
2833
3554
|
*/
|
|
3555
|
+
var Monitor = createLucideIcon("monitor", [
|
|
3556
|
+
["rect", {
|
|
3557
|
+
width: "20",
|
|
3558
|
+
height: "14",
|
|
3559
|
+
x: "2",
|
|
3560
|
+
y: "3",
|
|
3561
|
+
rx: "2",
|
|
3562
|
+
key: "48i651"
|
|
3563
|
+
}],
|
|
3564
|
+
["line", {
|
|
3565
|
+
x1: "8",
|
|
3566
|
+
x2: "16",
|
|
3567
|
+
y1: "21",
|
|
3568
|
+
y2: "21",
|
|
3569
|
+
key: "1svkeh"
|
|
3570
|
+
}],
|
|
3571
|
+
["line", {
|
|
3572
|
+
x1: "12",
|
|
3573
|
+
x2: "12",
|
|
3574
|
+
y1: "17",
|
|
3575
|
+
y2: "21",
|
|
3576
|
+
key: "vw1qmm"
|
|
3577
|
+
}]
|
|
3578
|
+
]);
|
|
3579
|
+
/**
|
|
3580
|
+
* @license lucide-react v0.576.0 - ISC
|
|
3581
|
+
*
|
|
3582
|
+
* This source code is licensed under the ISC license.
|
|
3583
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
3584
|
+
*/
|
|
2834
3585
|
var Moon = createLucideIcon("moon", [["path", {
|
|
2835
3586
|
d: "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401",
|
|
2836
3587
|
key: "kfwtm"
|
|
@@ -2841,6 +3592,16 @@ var Moon = createLucideIcon("moon", [["path", {
|
|
|
2841
3592
|
* This source code is licensed under the ISC license.
|
|
2842
3593
|
* See the LICENSE file in the root directory of this source tree.
|
|
2843
3594
|
*/
|
|
3595
|
+
var Mountain = createLucideIcon("mountain", [["path", {
|
|
3596
|
+
d: "m8 3 4 8 5-5 5 15H2L8 3z",
|
|
3597
|
+
key: "otkl63"
|
|
3598
|
+
}]]);
|
|
3599
|
+
/**
|
|
3600
|
+
* @license lucide-react v0.576.0 - ISC
|
|
3601
|
+
*
|
|
3602
|
+
* This source code is licensed under the ISC license.
|
|
3603
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
3604
|
+
*/
|
|
2844
3605
|
var MousePointerClick = createLucideIcon("mouse-pointer-click", [
|
|
2845
3606
|
["path", {
|
|
2846
3607
|
d: "M14 4.1 12 6",
|
|
@@ -3103,6 +3864,32 @@ var Pencil = createLucideIcon("pencil", [["path", {
|
|
|
3103
3864
|
* This source code is licensed under the ISC license.
|
|
3104
3865
|
* See the LICENSE file in the root directory of this source tree.
|
|
3105
3866
|
*/
|
|
3867
|
+
var PersonStanding = createLucideIcon("person-standing", [
|
|
3868
|
+
["circle", {
|
|
3869
|
+
cx: "12",
|
|
3870
|
+
cy: "5",
|
|
3871
|
+
r: "1",
|
|
3872
|
+
key: "gxeob9"
|
|
3873
|
+
}],
|
|
3874
|
+
["path", {
|
|
3875
|
+
d: "m9 20 3-6 3 6",
|
|
3876
|
+
key: "se2kox"
|
|
3877
|
+
}],
|
|
3878
|
+
["path", {
|
|
3879
|
+
d: "m6 8 6 2 6-2",
|
|
3880
|
+
key: "4o3us4"
|
|
3881
|
+
}],
|
|
3882
|
+
["path", {
|
|
3883
|
+
d: "M12 10v4",
|
|
3884
|
+
key: "1kjpxc"
|
|
3885
|
+
}]
|
|
3886
|
+
]);
|
|
3887
|
+
/**
|
|
3888
|
+
* @license lucide-react v0.576.0 - ISC
|
|
3889
|
+
*
|
|
3890
|
+
* This source code is licensed under the ISC license.
|
|
3891
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
3892
|
+
*/
|
|
3106
3893
|
var Phone = createLucideIcon("phone", [["path", {
|
|
3107
3894
|
d: "M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384",
|
|
3108
3895
|
key: "9njp5v"
|
|
@@ -3153,6 +3940,34 @@ var Play = createLucideIcon("play", [["path", {
|
|
|
3153
3940
|
* This source code is licensed under the ISC license.
|
|
3154
3941
|
* See the LICENSE file in the root directory of this source tree.
|
|
3155
3942
|
*/
|
|
3943
|
+
var PlugZap = createLucideIcon("plug-zap", [
|
|
3944
|
+
["path", {
|
|
3945
|
+
d: "M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z",
|
|
3946
|
+
key: "goz73y"
|
|
3947
|
+
}],
|
|
3948
|
+
["path", {
|
|
3949
|
+
d: "m2 22 3-3",
|
|
3950
|
+
key: "19mgm9"
|
|
3951
|
+
}],
|
|
3952
|
+
["path", {
|
|
3953
|
+
d: "M7.5 13.5 10 11",
|
|
3954
|
+
key: "7xgeeb"
|
|
3955
|
+
}],
|
|
3956
|
+
["path", {
|
|
3957
|
+
d: "M10.5 16.5 13 14",
|
|
3958
|
+
key: "10btkg"
|
|
3959
|
+
}],
|
|
3960
|
+
["path", {
|
|
3961
|
+
d: "m18 3-4 4h6l-4 4",
|
|
3962
|
+
key: "16psg9"
|
|
3963
|
+
}]
|
|
3964
|
+
]);
|
|
3965
|
+
/**
|
|
3966
|
+
* @license lucide-react v0.576.0 - ISC
|
|
3967
|
+
*
|
|
3968
|
+
* This source code is licensed under the ISC license.
|
|
3969
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
3970
|
+
*/
|
|
3156
3971
|
var Plus = createLucideIcon("plus", [["path", {
|
|
3157
3972
|
d: "M5 12h14",
|
|
3158
3973
|
key: "1ays0h"
|
|
@@ -3227,6 +4042,64 @@ var Power = createLucideIcon("power", [["path", {
|
|
|
3227
4042
|
* This source code is licensed under the ISC license.
|
|
3228
4043
|
* See the LICENSE file in the root directory of this source tree.
|
|
3229
4044
|
*/
|
|
4045
|
+
var Printer = createLucideIcon("printer", [
|
|
4046
|
+
["path", {
|
|
4047
|
+
d: "M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2",
|
|
4048
|
+
key: "143wyd"
|
|
4049
|
+
}],
|
|
4050
|
+
["path", {
|
|
4051
|
+
d: "M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6",
|
|
4052
|
+
key: "1itne7"
|
|
4053
|
+
}],
|
|
4054
|
+
["rect", {
|
|
4055
|
+
x: "6",
|
|
4056
|
+
y: "14",
|
|
4057
|
+
width: "12",
|
|
4058
|
+
height: "8",
|
|
4059
|
+
rx: "1",
|
|
4060
|
+
key: "1ue0tg"
|
|
4061
|
+
}]
|
|
4062
|
+
]);
|
|
4063
|
+
/**
|
|
4064
|
+
* @license lucide-react v0.576.0 - ISC
|
|
4065
|
+
*
|
|
4066
|
+
* This source code is licensed under the ISC license.
|
|
4067
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
4068
|
+
*/
|
|
4069
|
+
var Projector = createLucideIcon("projector", [
|
|
4070
|
+
["path", {
|
|
4071
|
+
d: "M5 7 3 5",
|
|
4072
|
+
key: "1yys58"
|
|
4073
|
+
}],
|
|
4074
|
+
["path", {
|
|
4075
|
+
d: "M9 6V3",
|
|
4076
|
+
key: "1ptz9u"
|
|
4077
|
+
}],
|
|
4078
|
+
["path", {
|
|
4079
|
+
d: "m13 7 2-2",
|
|
4080
|
+
key: "1w3vmq"
|
|
4081
|
+
}],
|
|
4082
|
+
["circle", {
|
|
4083
|
+
cx: "9",
|
|
4084
|
+
cy: "13",
|
|
4085
|
+
r: "3",
|
|
4086
|
+
key: "1mma13"
|
|
4087
|
+
}],
|
|
4088
|
+
["path", {
|
|
4089
|
+
d: "M11.83 12H20a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h2.17",
|
|
4090
|
+
key: "2frwzc"
|
|
4091
|
+
}],
|
|
4092
|
+
["path", {
|
|
4093
|
+
d: "M16 16h2",
|
|
4094
|
+
key: "dnq2od"
|
|
4095
|
+
}]
|
|
4096
|
+
]);
|
|
4097
|
+
/**
|
|
4098
|
+
* @license lucide-react v0.576.0 - ISC
|
|
4099
|
+
*
|
|
4100
|
+
* This source code is licensed under the ISC license.
|
|
4101
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
4102
|
+
*/
|
|
3230
4103
|
var QrCode$1 = createLucideIcon("qr-code", [
|
|
3231
4104
|
["rect", {
|
|
3232
4105
|
width: "5",
|
|
@@ -3429,6 +4302,26 @@ var RefreshCw = createLucideIcon("refresh-cw", [
|
|
|
3429
4302
|
* This source code is licensed under the ISC license.
|
|
3430
4303
|
* See the LICENSE file in the root directory of this source tree.
|
|
3431
4304
|
*/
|
|
4305
|
+
var Refrigerator = createLucideIcon("refrigerator", [
|
|
4306
|
+
["path", {
|
|
4307
|
+
d: "M5 6a4 4 0 0 1 4-4h6a4 4 0 0 1 4 4v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6Z",
|
|
4308
|
+
key: "fpq118"
|
|
4309
|
+
}],
|
|
4310
|
+
["path", {
|
|
4311
|
+
d: "M5 10h14",
|
|
4312
|
+
key: "elsbfy"
|
|
4313
|
+
}],
|
|
4314
|
+
["path", {
|
|
4315
|
+
d: "M15 7v6",
|
|
4316
|
+
key: "1nx30x"
|
|
4317
|
+
}]
|
|
4318
|
+
]);
|
|
4319
|
+
/**
|
|
4320
|
+
* @license lucide-react v0.576.0 - ISC
|
|
4321
|
+
*
|
|
4322
|
+
* This source code is licensed under the ISC license.
|
|
4323
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
4324
|
+
*/
|
|
3432
4325
|
var RotateCcw = createLucideIcon("rotate-ccw", [["path", {
|
|
3433
4326
|
d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",
|
|
3434
4327
|
key: "1357e3"
|
|
@@ -3455,6 +4348,66 @@ var RotateCw = createLucideIcon("rotate-cw", [["path", {
|
|
|
3455
4348
|
* This source code is licensed under the ISC license.
|
|
3456
4349
|
* See the LICENSE file in the root directory of this source tree.
|
|
3457
4350
|
*/
|
|
4351
|
+
var Route = createLucideIcon("route", [
|
|
4352
|
+
["circle", {
|
|
4353
|
+
cx: "6",
|
|
4354
|
+
cy: "19",
|
|
4355
|
+
r: "3",
|
|
4356
|
+
key: "1kj8tv"
|
|
4357
|
+
}],
|
|
4358
|
+
["path", {
|
|
4359
|
+
d: "M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15",
|
|
4360
|
+
key: "1d8sl"
|
|
4361
|
+
}],
|
|
4362
|
+
["circle", {
|
|
4363
|
+
cx: "18",
|
|
4364
|
+
cy: "5",
|
|
4365
|
+
r: "3",
|
|
4366
|
+
key: "gq8acd"
|
|
4367
|
+
}]
|
|
4368
|
+
]);
|
|
4369
|
+
/**
|
|
4370
|
+
* @license lucide-react v0.576.0 - ISC
|
|
4371
|
+
*
|
|
4372
|
+
* This source code is licensed under the ISC license.
|
|
4373
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
4374
|
+
*/
|
|
4375
|
+
var Router = createLucideIcon("router", [
|
|
4376
|
+
["rect", {
|
|
4377
|
+
width: "20",
|
|
4378
|
+
height: "8",
|
|
4379
|
+
x: "2",
|
|
4380
|
+
y: "14",
|
|
4381
|
+
rx: "2",
|
|
4382
|
+
key: "w68u3i"
|
|
4383
|
+
}],
|
|
4384
|
+
["path", {
|
|
4385
|
+
d: "M6.01 18H6",
|
|
4386
|
+
key: "19vcac"
|
|
4387
|
+
}],
|
|
4388
|
+
["path", {
|
|
4389
|
+
d: "M10.01 18H10",
|
|
4390
|
+
key: "uamcmx"
|
|
4391
|
+
}],
|
|
4392
|
+
["path", {
|
|
4393
|
+
d: "M15 10v4",
|
|
4394
|
+
key: "qjz1xs"
|
|
4395
|
+
}],
|
|
4396
|
+
["path", {
|
|
4397
|
+
d: "M17.84 7.17a4 4 0 0 0-5.66 0",
|
|
4398
|
+
key: "1rif40"
|
|
4399
|
+
}],
|
|
4400
|
+
["path", {
|
|
4401
|
+
d: "M20.66 4.34a8 8 0 0 0-11.31 0",
|
|
4402
|
+
key: "6a5xfq"
|
|
4403
|
+
}]
|
|
4404
|
+
]);
|
|
4405
|
+
/**
|
|
4406
|
+
* @license lucide-react v0.576.0 - ISC
|
|
4407
|
+
*
|
|
4408
|
+
* This source code is licensed under the ISC license.
|
|
4409
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
4410
|
+
*/
|
|
3458
4411
|
var Sailboat = createLucideIcon("sailboat", [
|
|
3459
4412
|
["path", {
|
|
3460
4413
|
d: "M10 2v15",
|
|
@@ -3928,6 +4881,64 @@ var Snowflake = createLucideIcon("snowflake", [
|
|
|
3928
4881
|
* This source code is licensed under the ISC license.
|
|
3929
4882
|
* See the LICENSE file in the root directory of this source tree.
|
|
3930
4883
|
*/
|
|
4884
|
+
var Sofa = createLucideIcon("sofa", [
|
|
4885
|
+
["path", {
|
|
4886
|
+
d: "M20 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v3",
|
|
4887
|
+
key: "1dgpiv"
|
|
4888
|
+
}],
|
|
4889
|
+
["path", {
|
|
4890
|
+
d: "M2 16a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5a2 2 0 0 0-4 0v1.5a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5V11a2 2 0 0 0-4 0z",
|
|
4891
|
+
key: "xacw8m"
|
|
4892
|
+
}],
|
|
4893
|
+
["path", {
|
|
4894
|
+
d: "M4 18v2",
|
|
4895
|
+
key: "jwo5n2"
|
|
4896
|
+
}],
|
|
4897
|
+
["path", {
|
|
4898
|
+
d: "M20 18v2",
|
|
4899
|
+
key: "1ar1qi"
|
|
4900
|
+
}],
|
|
4901
|
+
["path", {
|
|
4902
|
+
d: "M12 4v9",
|
|
4903
|
+
key: "oqhhn3"
|
|
4904
|
+
}]
|
|
4905
|
+
]);
|
|
4906
|
+
/**
|
|
4907
|
+
* @license lucide-react v0.576.0 - ISC
|
|
4908
|
+
*
|
|
4909
|
+
* This source code is licensed under the ISC license.
|
|
4910
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
4911
|
+
*/
|
|
4912
|
+
var Speaker = createLucideIcon("speaker", [
|
|
4913
|
+
["rect", {
|
|
4914
|
+
width: "16",
|
|
4915
|
+
height: "20",
|
|
4916
|
+
x: "4",
|
|
4917
|
+
y: "2",
|
|
4918
|
+
rx: "2",
|
|
4919
|
+
key: "1nb95v"
|
|
4920
|
+
}],
|
|
4921
|
+
["path", {
|
|
4922
|
+
d: "M12 6h.01",
|
|
4923
|
+
key: "1vi96p"
|
|
4924
|
+
}],
|
|
4925
|
+
["circle", {
|
|
4926
|
+
cx: "12",
|
|
4927
|
+
cy: "14",
|
|
4928
|
+
r: "4",
|
|
4929
|
+
key: "1jruaj"
|
|
4930
|
+
}],
|
|
4931
|
+
["path", {
|
|
4932
|
+
d: "M12 14h.01",
|
|
4933
|
+
key: "1etili"
|
|
4934
|
+
}]
|
|
4935
|
+
]);
|
|
4936
|
+
/**
|
|
4937
|
+
* @license lucide-react v0.576.0 - ISC
|
|
4938
|
+
*
|
|
4939
|
+
* This source code is licensed under the ISC license.
|
|
4940
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
4941
|
+
*/
|
|
3931
4942
|
var Sparkles = createLucideIcon("sparkles", [
|
|
3932
4943
|
["path", {
|
|
3933
4944
|
d: "M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",
|
|
@@ -3998,6 +5009,26 @@ var Star = createLucideIcon("star", [["path", {
|
|
|
3998
5009
|
* This source code is licensed under the ISC license.
|
|
3999
5010
|
* See the LICENSE file in the root directory of this source tree.
|
|
4000
5011
|
*/
|
|
5012
|
+
var Store = createLucideIcon("store", [
|
|
5013
|
+
["path", {
|
|
5014
|
+
d: "M15 21v-5a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v5",
|
|
5015
|
+
key: "slp6dd"
|
|
5016
|
+
}],
|
|
5017
|
+
["path", {
|
|
5018
|
+
d: "M17.774 10.31a1.12 1.12 0 0 0-1.549 0 2.5 2.5 0 0 1-3.451 0 1.12 1.12 0 0 0-1.548 0 2.5 2.5 0 0 1-3.452 0 1.12 1.12 0 0 0-1.549 0 2.5 2.5 0 0 1-3.77-3.248l2.889-4.184A2 2 0 0 1 7 2h10a2 2 0 0 1 1.653.873l2.895 4.192a2.5 2.5 0 0 1-3.774 3.244",
|
|
5019
|
+
key: "o0xfot"
|
|
5020
|
+
}],
|
|
5021
|
+
["path", {
|
|
5022
|
+
d: "M4 10.95V19a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8.05",
|
|
5023
|
+
key: "wn3emo"
|
|
5024
|
+
}]
|
|
5025
|
+
]);
|
|
5026
|
+
/**
|
|
5027
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5028
|
+
*
|
|
5029
|
+
* This source code is licensed under the ISC license.
|
|
5030
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5031
|
+
*/
|
|
4001
5032
|
var SunMedium = createLucideIcon("sun-medium", [
|
|
4002
5033
|
["circle", {
|
|
4003
5034
|
cx: "12",
|
|
@@ -4130,6 +5161,27 @@ var Sunrise = createLucideIcon("sunrise", [
|
|
|
4130
5161
|
* This source code is licensed under the ISC license.
|
|
4131
5162
|
* See the LICENSE file in the root directory of this source tree.
|
|
4132
5163
|
*/
|
|
5164
|
+
var Tablet = createLucideIcon("tablet", [["rect", {
|
|
5165
|
+
width: "16",
|
|
5166
|
+
height: "20",
|
|
5167
|
+
x: "4",
|
|
5168
|
+
y: "2",
|
|
5169
|
+
rx: "2",
|
|
5170
|
+
ry: "2",
|
|
5171
|
+
key: "76otgf"
|
|
5172
|
+
}], ["line", {
|
|
5173
|
+
x1: "12",
|
|
5174
|
+
x2: "12.01",
|
|
5175
|
+
y1: "18",
|
|
5176
|
+
y2: "18",
|
|
5177
|
+
key: "1dp563"
|
|
5178
|
+
}]]);
|
|
5179
|
+
/**
|
|
5180
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5181
|
+
*
|
|
5182
|
+
* This source code is licensed under the ISC license.
|
|
5183
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5184
|
+
*/
|
|
4133
5185
|
var Tag = createLucideIcon("tag", [["path", {
|
|
4134
5186
|
d: "M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z",
|
|
4135
5187
|
key: "vktsd0"
|
|
@@ -4172,6 +5224,46 @@ var Target = createLucideIcon("target", [
|
|
|
4172
5224
|
* This source code is licensed under the ISC license.
|
|
4173
5225
|
* See the LICENSE file in the root directory of this source tree.
|
|
4174
5226
|
*/
|
|
5227
|
+
var ThermometerSnowflake = createLucideIcon("thermometer-snowflake", [
|
|
5228
|
+
["path", {
|
|
5229
|
+
d: "m10 20-1.25-2.5L6 18",
|
|
5230
|
+
key: "18frcb"
|
|
5231
|
+
}],
|
|
5232
|
+
["path", {
|
|
5233
|
+
d: "M10 4 8.75 6.5 6 6",
|
|
5234
|
+
key: "7mghy3"
|
|
5235
|
+
}],
|
|
5236
|
+
["path", {
|
|
5237
|
+
d: "M10.585 15H10",
|
|
5238
|
+
key: "4nqulp"
|
|
5239
|
+
}],
|
|
5240
|
+
["path", {
|
|
5241
|
+
d: "M2 12h6.5L10 9",
|
|
5242
|
+
key: "kv9z4n"
|
|
5243
|
+
}],
|
|
5244
|
+
["path", {
|
|
5245
|
+
d: "M20 14.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0z",
|
|
5246
|
+
key: "yu0u2z"
|
|
5247
|
+
}],
|
|
5248
|
+
["path", {
|
|
5249
|
+
d: "m4 10 1.5 2L4 14",
|
|
5250
|
+
key: "k9enpj"
|
|
5251
|
+
}],
|
|
5252
|
+
["path", {
|
|
5253
|
+
d: "m7 21 3-6-1.5-3",
|
|
5254
|
+
key: "j8hb9u"
|
|
5255
|
+
}],
|
|
5256
|
+
["path", {
|
|
5257
|
+
d: "m7 3 3 6h2",
|
|
5258
|
+
key: "1bbqgq"
|
|
5259
|
+
}]
|
|
5260
|
+
]);
|
|
5261
|
+
/**
|
|
5262
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5263
|
+
*
|
|
5264
|
+
* This source code is licensed under the ISC license.
|
|
5265
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5266
|
+
*/
|
|
4175
5267
|
var Thermometer = createLucideIcon("thermometer", [["path", {
|
|
4176
5268
|
d: "M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z",
|
|
4177
5269
|
key: "17jzev"
|
|
@@ -4182,6 +5274,66 @@ var Thermometer = createLucideIcon("thermometer", [["path", {
|
|
|
4182
5274
|
* This source code is licensed under the ISC license.
|
|
4183
5275
|
* See the LICENSE file in the root directory of this source tree.
|
|
4184
5276
|
*/
|
|
5277
|
+
var ThermometerSun = createLucideIcon("thermometer-sun", [
|
|
5278
|
+
["path", {
|
|
5279
|
+
d: "M12 2v2",
|
|
5280
|
+
key: "tus03m"
|
|
5281
|
+
}],
|
|
5282
|
+
["path", {
|
|
5283
|
+
d: "M12 8a4 4 0 0 0-1.645 7.647",
|
|
5284
|
+
key: "wz5p04"
|
|
5285
|
+
}],
|
|
5286
|
+
["path", {
|
|
5287
|
+
d: "M2 12h2",
|
|
5288
|
+
key: "1t8f8n"
|
|
5289
|
+
}],
|
|
5290
|
+
["path", {
|
|
5291
|
+
d: "M20 14.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0z",
|
|
5292
|
+
key: "yu0u2z"
|
|
5293
|
+
}],
|
|
5294
|
+
["path", {
|
|
5295
|
+
d: "m4.93 4.93 1.41 1.41",
|
|
5296
|
+
key: "149t6j"
|
|
5297
|
+
}],
|
|
5298
|
+
["path", {
|
|
5299
|
+
d: "m6.34 17.66-1.41 1.41",
|
|
5300
|
+
key: "1m8zz5"
|
|
5301
|
+
}]
|
|
5302
|
+
]);
|
|
5303
|
+
/**
|
|
5304
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5305
|
+
*
|
|
5306
|
+
* This source code is licensed under the ISC license.
|
|
5307
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5308
|
+
*/
|
|
5309
|
+
var Timer = createLucideIcon("timer", [
|
|
5310
|
+
["line", {
|
|
5311
|
+
x1: "10",
|
|
5312
|
+
x2: "14",
|
|
5313
|
+
y1: "2",
|
|
5314
|
+
y2: "2",
|
|
5315
|
+
key: "14vaq8"
|
|
5316
|
+
}],
|
|
5317
|
+
["line", {
|
|
5318
|
+
x1: "12",
|
|
5319
|
+
x2: "15",
|
|
5320
|
+
y1: "14",
|
|
5321
|
+
y2: "11",
|
|
5322
|
+
key: "17fdiu"
|
|
5323
|
+
}],
|
|
5324
|
+
["circle", {
|
|
5325
|
+
cx: "12",
|
|
5326
|
+
cy: "14",
|
|
5327
|
+
r: "8",
|
|
5328
|
+
key: "1e1u0o"
|
|
5329
|
+
}]
|
|
5330
|
+
]);
|
|
5331
|
+
/**
|
|
5332
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5333
|
+
*
|
|
5334
|
+
* This source code is licensed under the ISC license.
|
|
5335
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5336
|
+
*/
|
|
4185
5337
|
var ToggleLeft = createLucideIcon("toggle-left", [["circle", {
|
|
4186
5338
|
cx: "9",
|
|
4187
5339
|
cy: "12",
|
|
@@ -4328,6 +5480,43 @@ var Trash2 = createLucideIcon("trash-2", [
|
|
|
4328
5480
|
* This source code is licensed under the ISC license.
|
|
4329
5481
|
* See the LICENSE file in the root directory of this source tree.
|
|
4330
5482
|
*/
|
|
5483
|
+
var TreePine = createLucideIcon("tree-pine", [["path", {
|
|
5484
|
+
d: "m17 14 3 3.3a1 1 0 0 1-.7 1.7H4.7a1 1 0 0 1-.7-1.7L7 14h-.3a1 1 0 0 1-.7-1.7L9 9h-.2A1 1 0 0 1 8 7.3L12 3l4 4.3a1 1 0 0 1-.8 1.7H15l3 3.3a1 1 0 0 1-.7 1.7H17Z",
|
|
5485
|
+
key: "cpyugq"
|
|
5486
|
+
}], ["path", {
|
|
5487
|
+
d: "M12 22v-3",
|
|
5488
|
+
key: "kmzjlo"
|
|
5489
|
+
}]]);
|
|
5490
|
+
/**
|
|
5491
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5492
|
+
*
|
|
5493
|
+
* This source code is licensed under the ISC license.
|
|
5494
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5495
|
+
*/
|
|
5496
|
+
var Trees = createLucideIcon("trees", [
|
|
5497
|
+
["path", {
|
|
5498
|
+
d: "M10 10v.2A3 3 0 0 1 8.9 16H5a3 3 0 0 1-1-5.8V10a3 3 0 0 1 6 0Z",
|
|
5499
|
+
key: "1l6gj6"
|
|
5500
|
+
}],
|
|
5501
|
+
["path", {
|
|
5502
|
+
d: "M7 16v6",
|
|
5503
|
+
key: "1a82de"
|
|
5504
|
+
}],
|
|
5505
|
+
["path", {
|
|
5506
|
+
d: "M13 19v3",
|
|
5507
|
+
key: "13sx9i"
|
|
5508
|
+
}],
|
|
5509
|
+
["path", {
|
|
5510
|
+
d: "M12 19h8.3a1 1 0 0 0 .7-1.7L18 14h.3a1 1 0 0 0 .7-1.7L16 9h.2a1 1 0 0 0 .8-1.7L13 3l-1.4 1.5",
|
|
5511
|
+
key: "1sj9kv"
|
|
5512
|
+
}]
|
|
5513
|
+
]);
|
|
5514
|
+
/**
|
|
5515
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5516
|
+
*
|
|
5517
|
+
* This source code is licensed under the ISC license.
|
|
5518
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5519
|
+
*/
|
|
4331
5520
|
var TrendingUp = createLucideIcon("trending-up", [["path", {
|
|
4332
5521
|
d: "M16 7h6v6",
|
|
4333
5522
|
key: "box55l"
|
|
@@ -4406,6 +5595,23 @@ var Truck = createLucideIcon("truck", [
|
|
|
4406
5595
|
* This source code is licensed under the ISC license.
|
|
4407
5596
|
* See the LICENSE file in the root directory of this source tree.
|
|
4408
5597
|
*/
|
|
5598
|
+
var Tv = createLucideIcon("tv", [["path", {
|
|
5599
|
+
d: "m17 2-5 5-5-5",
|
|
5600
|
+
key: "16satq"
|
|
5601
|
+
}], ["rect", {
|
|
5602
|
+
width: "20",
|
|
5603
|
+
height: "15",
|
|
5604
|
+
x: "2",
|
|
5605
|
+
y: "7",
|
|
5606
|
+
rx: "2",
|
|
5607
|
+
key: "1e6viu"
|
|
5608
|
+
}]]);
|
|
5609
|
+
/**
|
|
5610
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5611
|
+
*
|
|
5612
|
+
* This source code is licensed under the ISC license.
|
|
5613
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5614
|
+
*/
|
|
4409
5615
|
var Type = createLucideIcon("type", [
|
|
4410
5616
|
["path", {
|
|
4411
5617
|
d: "M12 4v16",
|
|
@@ -4426,6 +5632,26 @@ var Type = createLucideIcon("type", [
|
|
|
4426
5632
|
* This source code is licensed under the ISC license.
|
|
4427
5633
|
* See the LICENSE file in the root directory of this source tree.
|
|
4428
5634
|
*/
|
|
5635
|
+
var Umbrella = createLucideIcon("umbrella", [
|
|
5636
|
+
["path", {
|
|
5637
|
+
d: "M12 13v7a2 2 0 0 0 4 0",
|
|
5638
|
+
key: "rpgb42"
|
|
5639
|
+
}],
|
|
5640
|
+
["path", {
|
|
5641
|
+
d: "M12 2v2",
|
|
5642
|
+
key: "tus03m"
|
|
5643
|
+
}],
|
|
5644
|
+
["path", {
|
|
5645
|
+
d: "M20.992 13a1 1 0 0 0 .97-1.274 10.284 10.284 0 0 0-19.923 0A1 1 0 0 0 3 13z",
|
|
5646
|
+
key: "124nyo"
|
|
5647
|
+
}]
|
|
5648
|
+
]);
|
|
5649
|
+
/**
|
|
5650
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5651
|
+
*
|
|
5652
|
+
* This source code is licensed under the ISC license.
|
|
5653
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5654
|
+
*/
|
|
4429
5655
|
var Undo2 = createLucideIcon("undo-2", [["path", {
|
|
4430
5656
|
d: "M9 14 4 9l5-5",
|
|
4431
5657
|
key: "102s5s"
|
|
@@ -4556,6 +5782,26 @@ var UtensilsCrossed = createLucideIcon("utensils-crossed", [
|
|
|
4556
5782
|
* This source code is licensed under the ISC license.
|
|
4557
5783
|
* See the LICENSE file in the root directory of this source tree.
|
|
4558
5784
|
*/
|
|
5785
|
+
var Utensils = createLucideIcon("utensils", [
|
|
5786
|
+
["path", {
|
|
5787
|
+
d: "M3 2v7c0 1.1.9 2 2 2h4a2 2 0 0 0 2-2V2",
|
|
5788
|
+
key: "cjf0a3"
|
|
5789
|
+
}],
|
|
5790
|
+
["path", {
|
|
5791
|
+
d: "M7 2v20",
|
|
5792
|
+
key: "1473qp"
|
|
5793
|
+
}],
|
|
5794
|
+
["path", {
|
|
5795
|
+
d: "M21 15V2a5 5 0 0 0-5 5v6c0 1.1.9 2 2 2h3Zm0 0v7",
|
|
5796
|
+
key: "j28e5"
|
|
5797
|
+
}]
|
|
5798
|
+
]);
|
|
5799
|
+
/**
|
|
5800
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5801
|
+
*
|
|
5802
|
+
* This source code is licensed under the ISC license.
|
|
5803
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5804
|
+
*/
|
|
4559
5805
|
var Vibrate = createLucideIcon("vibrate", [
|
|
4560
5806
|
["path", {
|
|
4561
5807
|
d: "m2 8 2 2-2 2 2 2-2 2",
|
|
@@ -4580,6 +5826,43 @@ var Vibrate = createLucideIcon("vibrate", [
|
|
|
4580
5826
|
* This source code is licensed under the ISC license.
|
|
4581
5827
|
* See the LICENSE file in the root directory of this source tree.
|
|
4582
5828
|
*/
|
|
5829
|
+
var VideoOff = createLucideIcon("video-off", [
|
|
5830
|
+
["path", {
|
|
5831
|
+
d: "M10.66 6H14a2 2 0 0 1 2 2v2.5l5.248-3.062A.5.5 0 0 1 22 7.87v8.196",
|
|
5832
|
+
key: "w8jjjt"
|
|
5833
|
+
}],
|
|
5834
|
+
["path", {
|
|
5835
|
+
d: "M16 16a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2",
|
|
5836
|
+
key: "1xawa7"
|
|
5837
|
+
}],
|
|
5838
|
+
["path", {
|
|
5839
|
+
d: "m2 2 20 20",
|
|
5840
|
+
key: "1ooewy"
|
|
5841
|
+
}]
|
|
5842
|
+
]);
|
|
5843
|
+
/**
|
|
5844
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5845
|
+
*
|
|
5846
|
+
* This source code is licensed under the ISC license.
|
|
5847
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5848
|
+
*/
|
|
5849
|
+
var Video = createLucideIcon("video", [["path", {
|
|
5850
|
+
d: "m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5",
|
|
5851
|
+
key: "ftymec"
|
|
5852
|
+
}], ["rect", {
|
|
5853
|
+
x: "2",
|
|
5854
|
+
y: "6",
|
|
5855
|
+
width: "14",
|
|
5856
|
+
height: "12",
|
|
5857
|
+
rx: "2",
|
|
5858
|
+
key: "158x01"
|
|
5859
|
+
}]]);
|
|
5860
|
+
/**
|
|
5861
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5862
|
+
*
|
|
5863
|
+
* This source code is licensed under the ISC license.
|
|
5864
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5865
|
+
*/
|
|
4583
5866
|
var Volume2 = createLucideIcon("volume-2", [
|
|
4584
5867
|
["path", {
|
|
4585
5868
|
d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z",
|
|
@@ -4626,6 +5909,92 @@ var VolumeX = createLucideIcon("volume-x", [
|
|
|
4626
5909
|
* This source code is licensed under the ISC license.
|
|
4627
5910
|
* See the LICENSE file in the root directory of this source tree.
|
|
4628
5911
|
*/
|
|
5912
|
+
var Warehouse = createLucideIcon("warehouse", [
|
|
5913
|
+
["path", {
|
|
5914
|
+
d: "M18 21V10a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v11",
|
|
5915
|
+
key: "pb2vm6"
|
|
5916
|
+
}],
|
|
5917
|
+
["path", {
|
|
5918
|
+
d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 1.132-1.803l7.95-3.974a2 2 0 0 1 1.837 0l7.948 3.974A2 2 0 0 1 22 8z",
|
|
5919
|
+
key: "doq5xv"
|
|
5920
|
+
}],
|
|
5921
|
+
["path", {
|
|
5922
|
+
d: "M6 13h12",
|
|
5923
|
+
key: "yf64js"
|
|
5924
|
+
}],
|
|
5925
|
+
["path", {
|
|
5926
|
+
d: "M6 17h12",
|
|
5927
|
+
key: "1jwigz"
|
|
5928
|
+
}]
|
|
5929
|
+
]);
|
|
5930
|
+
/**
|
|
5931
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5932
|
+
*
|
|
5933
|
+
* This source code is licensed under the ISC license.
|
|
5934
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5935
|
+
*/
|
|
5936
|
+
var WashingMachine = createLucideIcon("washing-machine", [
|
|
5937
|
+
["path", {
|
|
5938
|
+
d: "M3 6h3",
|
|
5939
|
+
key: "155dbl"
|
|
5940
|
+
}],
|
|
5941
|
+
["path", {
|
|
5942
|
+
d: "M17 6h.01",
|
|
5943
|
+
key: "e2y6kg"
|
|
5944
|
+
}],
|
|
5945
|
+
["rect", {
|
|
5946
|
+
width: "18",
|
|
5947
|
+
height: "20",
|
|
5948
|
+
x: "3",
|
|
5949
|
+
y: "2",
|
|
5950
|
+
rx: "2",
|
|
5951
|
+
key: "od3kk9"
|
|
5952
|
+
}],
|
|
5953
|
+
["circle", {
|
|
5954
|
+
cx: "12",
|
|
5955
|
+
cy: "13",
|
|
5956
|
+
r: "5",
|
|
5957
|
+
key: "nlbqau"
|
|
5958
|
+
}],
|
|
5959
|
+
["path", {
|
|
5960
|
+
d: "M12 18a2.5 2.5 0 0 0 0-5 2.5 2.5 0 0 1 0-5",
|
|
5961
|
+
key: "17lach"
|
|
5962
|
+
}]
|
|
5963
|
+
]);
|
|
5964
|
+
/**
|
|
5965
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5966
|
+
*
|
|
5967
|
+
* This source code is licensed under the ISC license.
|
|
5968
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5969
|
+
*/
|
|
5970
|
+
var WavesLadder = createLucideIcon("waves-ladder", [
|
|
5971
|
+
["path", {
|
|
5972
|
+
d: "M19 5a2 2 0 0 0-2 2v11",
|
|
5973
|
+
key: "s41o68"
|
|
5974
|
+
}],
|
|
5975
|
+
["path", {
|
|
5976
|
+
d: "M2 18c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1",
|
|
5977
|
+
key: "rd2r6e"
|
|
5978
|
+
}],
|
|
5979
|
+
["path", {
|
|
5980
|
+
d: "M7 13h10",
|
|
5981
|
+
key: "1rwob1"
|
|
5982
|
+
}],
|
|
5983
|
+
["path", {
|
|
5984
|
+
d: "M7 9h10",
|
|
5985
|
+
key: "12czzb"
|
|
5986
|
+
}],
|
|
5987
|
+
["path", {
|
|
5988
|
+
d: "M9 5a2 2 0 0 0-2 2v11",
|
|
5989
|
+
key: "x0q4gh"
|
|
5990
|
+
}]
|
|
5991
|
+
]);
|
|
5992
|
+
/**
|
|
5993
|
+
* @license lucide-react v0.576.0 - ISC
|
|
5994
|
+
*
|
|
5995
|
+
* This source code is licensed under the ISC license.
|
|
5996
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
5997
|
+
*/
|
|
4629
5998
|
var Waves = createLucideIcon("waves", [
|
|
4630
5999
|
["path", {
|
|
4631
6000
|
d: "M2 6c.6.5 1.2 1 2.5 1C7 7 7 5 9.5 5c2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1",
|
|
@@ -7215,6 +8584,8 @@ var useVideoclipsListSources = trpc.videoclips.listSources.useQuery;
|
|
|
7215
8584
|
var useVideoclipsGetClipPlayback = trpc.videoclips.getClipPlayback.useQuery;
|
|
7216
8585
|
/** Generated alias around `trpc.videoclips.readClipBytes.useQuery`. */
|
|
7217
8586
|
var useVideoclipsReadClipBytes = trpc.videoclips.readClipBytes.useQuery;
|
|
8587
|
+
/** Generated alias around `trpc.videoclips.dialClipStream.useQuery`. */
|
|
8588
|
+
var useVideoclipsDialClipStream = trpc.videoclips.dialClipStream.useQuery;
|
|
7218
8589
|
/** Generated alias around `trpc.waterHeater.setTargetTemp.useMutation`. */
|
|
7219
8590
|
var useWaterHeaterSetTargetTemp = trpc.waterHeater.setTargetTemp.useMutation;
|
|
7220
8591
|
/** Generated alias around `trpc.waterHeater.setOperationMode.useMutation`. */
|
|
@@ -17686,38 +19057,6 @@ var cva = (base, config) => (props) => {
|
|
|
17686
19057
|
}, []), props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);
|
|
17687
19058
|
};
|
|
17688
19059
|
//#endregion
|
|
17689
|
-
//#region src/primitives/badge.tsx
|
|
17690
|
-
var badgeVariants = cva("inline-flex items-center rounded-full text-xs font-medium px-2 py-0.5", {
|
|
17691
|
-
variants: {
|
|
17692
|
-
variant: {
|
|
17693
|
-
default: "bg-surface-hover text-foreground",
|
|
17694
|
-
success: "bg-success/15 text-success",
|
|
17695
|
-
warning: "bg-warning/15 text-warning",
|
|
17696
|
-
danger: "bg-danger/15 text-danger",
|
|
17697
|
-
info: "bg-info/15 text-info"
|
|
17698
|
-
},
|
|
17699
|
-
styleVariant: {
|
|
17700
|
-
solid: "",
|
|
17701
|
-
outline: "border bg-transparent"
|
|
17702
|
-
}
|
|
17703
|
-
},
|
|
17704
|
-
defaultVariants: {
|
|
17705
|
-
variant: "default",
|
|
17706
|
-
styleVariant: "solid"
|
|
17707
|
-
}
|
|
17708
|
-
});
|
|
17709
|
-
var Badge = (0, react$1.forwardRef)(({ className, variant, style, ...props }, ref) => {
|
|
17710
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
17711
|
-
ref,
|
|
17712
|
-
className: cn(badgeVariants({
|
|
17713
|
-
variant,
|
|
17714
|
-
styleVariant: style
|
|
17715
|
-
}), className),
|
|
17716
|
-
...props
|
|
17717
|
-
});
|
|
17718
|
-
});
|
|
17719
|
-
Badge.displayName = "Badge";
|
|
17720
|
-
//#endregion
|
|
17721
19060
|
//#region src/primitives/button.tsx
|
|
17722
19061
|
var buttonVariants = cva("inline-flex items-center justify-center rounded-md font-medium transition-colors cursor-pointer disabled:opacity-50 disabled:pointer-events-none", {
|
|
17723
19062
|
variants: {
|
|
@@ -17993,590 +19332,123 @@ function axisTicks(fromMs, toMs, count) {
|
|
|
17993
19332
|
return ticks;
|
|
17994
19333
|
}
|
|
17995
19334
|
//#endregion
|
|
17996
|
-
//#region src/
|
|
17997
|
-
var
|
|
17998
|
-
|
|
17999
|
-
|
|
18000
|
-
|
|
18001
|
-
|
|
18002
|
-
|
|
18003
|
-
|
|
18004
|
-
|
|
18005
|
-
|
|
18006
|
-
|
|
18007
|
-
|
|
18008
|
-
|
|
18009
|
-
|
|
18010
|
-
|
|
18011
|
-
|
|
18012
|
-
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
18017
|
-
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
|
|
18022
|
-
|
|
18023
|
-
|
|
18024
|
-
|
|
18025
|
-
|
|
18026
|
-
|
|
18027
|
-
|
|
18028
|
-
|
|
18029
|
-
}
|
|
18030
|
-
|
|
18031
|
-
|
|
18032
|
-
|
|
18033
|
-
|
|
18034
|
-
|
|
18035
|
-
* 768px ≤ width < 1024px. Use to drive layouts that should pack denser
|
|
18036
|
-
* than full-desktop but more legibly than mobile (e.g. icons-only
|
|
18037
|
-
* sidebar rail, narrower stat tiles).
|
|
18038
|
-
*/
|
|
18039
|
-
function useIsMidWidth() {
|
|
18040
|
-
return (0, react$1.useSyncExternalStore)(subscribeQuery(MID_QUERY), getSnapshot(MID_QUERY), getServerSnapshot);
|
|
18041
|
-
}
|
|
18042
|
-
//#endregion
|
|
18043
|
-
//#region src/composites/data-table-layout.ts
|
|
18044
|
-
/**
|
|
18045
|
-
* At this many columns and above, `auto` switches a narrow viewport to cards.
|
|
18046
|
-
* Three columns still fit a phone at a readable size; four do not.
|
|
18047
|
-
*/
|
|
18048
|
-
var CARD_MODE_MIN_COLUMNS = 4;
|
|
18049
|
-
function resolveTableLayout({ mode, columnCount, isNarrow }) {
|
|
18050
|
-
if (!isNarrow) return "table";
|
|
18051
|
-
if (mode === "scroll") return "table";
|
|
18052
|
-
if (mode === "cards") return "cards";
|
|
18053
|
-
if (columnCount === 0) return "table";
|
|
18054
|
-
return columnCount >= 4 ? "cards" : "table";
|
|
19335
|
+
//#region src/composites/device-list/pagination.tsx
|
|
19336
|
+
var NEIGHBORS = 2;
|
|
19337
|
+
function computePageButtons(currentPage, totalPages) {
|
|
19338
|
+
if (totalPages <= 1) return [{
|
|
19339
|
+
kind: "page",
|
|
19340
|
+
n: 1
|
|
19341
|
+
}];
|
|
19342
|
+
if (totalPages <= 7) {
|
|
19343
|
+
const all = [];
|
|
19344
|
+
for (let i = 1; i <= totalPages; i++) all.push({
|
|
19345
|
+
kind: "page",
|
|
19346
|
+
n: i
|
|
19347
|
+
});
|
|
19348
|
+
return all;
|
|
19349
|
+
}
|
|
19350
|
+
const result = [];
|
|
19351
|
+
const leftBound = Math.max(2, currentPage - NEIGHBORS);
|
|
19352
|
+
const rightBound = Math.min(totalPages - 1, currentPage + NEIGHBORS);
|
|
19353
|
+
result.push({
|
|
19354
|
+
kind: "page",
|
|
19355
|
+
n: 1
|
|
19356
|
+
});
|
|
19357
|
+
if (leftBound > 2) result.push({
|
|
19358
|
+
kind: "ellipsis",
|
|
19359
|
+
side: "left"
|
|
19360
|
+
});
|
|
19361
|
+
for (let i = leftBound; i <= rightBound; i++) result.push({
|
|
19362
|
+
kind: "page",
|
|
19363
|
+
n: i
|
|
19364
|
+
});
|
|
19365
|
+
if (rightBound < totalPages - 1) result.push({
|
|
19366
|
+
kind: "ellipsis",
|
|
19367
|
+
side: "right"
|
|
19368
|
+
});
|
|
19369
|
+
result.push({
|
|
19370
|
+
kind: "page",
|
|
19371
|
+
n: totalPages
|
|
19372
|
+
});
|
|
19373
|
+
return result;
|
|
18055
19374
|
}
|
|
18056
|
-
|
|
18057
|
-
|
|
18058
|
-
|
|
18059
|
-
|
|
18060
|
-
*
|
|
18061
|
-
|
|
18062
|
-
* the right token from this file instead of hand-coding breakpoints.
|
|
18063
|
-
* Adopting the tokens uniformly is what makes the app feel consistent
|
|
18064
|
-
* across operator devices (mobile / tablet / desktop) and what unlocks
|
|
18065
|
-
* targeted tuning (e.g. shrink every label by 1px in one place).
|
|
18066
|
-
*
|
|
18067
|
-
* Tier definitions (Tailwind breakpoints):
|
|
18068
|
-
*
|
|
18069
|
-
* • **L1 — Compact** (`< 640px`, default = mobile portrait)
|
|
18070
|
-
* Single column, tight padding, smallest text, icon-only buttons
|
|
18071
|
-
* where labels can be inferred. The operator can still see every
|
|
18072
|
-
* stat / action — no horizontal scroll, no clipped text. The user
|
|
18073
|
-
* directive: "molta informazione in poco spazio quando la width è
|
|
18074
|
-
* bassa."
|
|
18075
|
-
*
|
|
18076
|
-
* • **L2 — Standard** (`sm` — `640–1023px`, tablet + small laptop)
|
|
18077
|
-
* 2-column grids, side panels become visible, button labels return.
|
|
18078
|
-
* Targets ~768px–1024px viewports — the most common operator sizes.
|
|
18079
|
-
*
|
|
18080
|
-
* • **L3 — Spacious** (`lg+` — `≥ 1024px`, desktop)
|
|
18081
|
-
* 4-column grids, generous padding, full-width header text,
|
|
18082
|
-
* side-by-side composite layouts. The operator has elbow room.
|
|
18083
|
-
*
|
|
18084
|
-
* Conventions:
|
|
18085
|
-
* - **Always start from L1** — the bare class is always the compact
|
|
18086
|
-
* variant, then `sm:` adds room at 640px, `lg:` at 1024px. No
|
|
18087
|
-
* desktop-first breakpoints; no max-width queries.
|
|
18088
|
-
* - **Token strings are static** — kept as `as const` so callers can
|
|
18089
|
-
* compose them via `cn()` / template literals without running a
|
|
18090
|
-
* class generator at runtime.
|
|
18091
|
-
* - **Pair the token with a comment** — the comment in the file
|
|
18092
|
-
* names every breakpoint it crosses, so a reader can predict the
|
|
18093
|
-
* layout without going to Tailwind docs.
|
|
18094
|
-
*/
|
|
18095
|
-
/** Container padding for a card-style section (header + body). */
|
|
18096
|
-
var SECTION_CARD = "rounded-lg border border-border bg-surface overflow-hidden";
|
|
18097
|
-
/** Header strip inside a card (between border-b lines). */
|
|
18098
|
-
var SECTION_HEADER = "border-b border-border px-3 sm:px-4 py-1.5 sm:py-2";
|
|
18099
|
-
/** Body padding for a card-style section. */
|
|
18100
|
-
var SECTION_BODY = "p-2 sm:p-3 lg:p-4";
|
|
18101
|
-
/** Gap between cards in a vertical stack. */
|
|
18102
|
-
var STACK_GAP = "space-y-3 sm:space-y-4";
|
|
18103
|
-
/** Gap between cards in a grid (4 cells / row at lg). */
|
|
18104
|
-
var GRID_GAP = "gap-2 sm:gap-3";
|
|
18105
|
-
/** Row padding inside a divided list (e.g. InfoRow). */
|
|
18106
|
-
var LIST_ROW = "px-3 sm:px-4 py-1.5 sm:py-2";
|
|
18107
|
-
/** 1-up at L1, 2-up at L2, 4-up at L3 — quick-stat row. */
|
|
18108
|
-
var GRID_QUICK_STATS = "grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-4";
|
|
18109
|
-
/** 1-up at L1, 2-up at L2+ — paired panels (motion + detection rules). */
|
|
18110
|
-
var GRID_PAIRED = "grid grid-cols-1 lg:grid-cols-2";
|
|
18111
|
-
/** Side-by-side layout that stacks below md (canvas + side panel). */
|
|
18112
|
-
var SPLIT_PANEL_OUTER = "flex flex-col-reverse md:flex-row";
|
|
18113
|
-
/** Sidebar narrow lane in a SPLIT_PANEL_OUTER layout. */
|
|
18114
|
-
var SPLIT_PANEL_SIDE = "w-full md:w-44 lg:w-52 md:flex-shrink-0 border-b md:border-b-0 md:border-l border-border";
|
|
18115
|
-
/**
|
|
18116
|
-
* The tier boundary at which a settings label/value row stops stacking.
|
|
18117
|
-
*
|
|
18118
|
-
* Below it (L1 — compact) the row is ONE column: label on its own line, value
|
|
18119
|
-
* on the next at full width. Above it the row is label-left / value-right as
|
|
18120
|
-
* before. The two-column form needs `SETTING_ROW_LABEL`'s lane plus a readable
|
|
18121
|
-
* value beside it; a phone has neither, and the failure mode is not "cramped"
|
|
18122
|
-
* but "the value is the part that gets cut" — the one thing the operator
|
|
18123
|
-
* opened the page to read.
|
|
18124
|
-
*/
|
|
18125
|
-
var SETTING_ROW_STACK_BREAKPOINT = "sm";
|
|
18126
|
-
/** Outer row: single column at L1, label/value columns from `sm` up. */
|
|
18127
|
-
var SETTING_ROW = "flex min-w-0 flex-col gap-0.5 py-1.5 sm:flex-row sm:items-center";
|
|
18128
|
-
/** Label lane. Full width (and free to wrap) at L1; a fixed lane from `sm` up. */
|
|
18129
|
-
var SETTING_ROW_LABEL = "min-w-0 text-[11px] leading-tight text-foreground-subtle sm:w-32 sm:shrink-0 sm:pr-2";
|
|
18130
|
-
/**
|
|
18131
|
-
* Value lane — the value text plus its trailing affordances (copy / reveal).
|
|
18132
|
-
* `w-full` at L1 so the value owns the whole row once the label is above it;
|
|
18133
|
-
* `sm:flex-1` makes it share the row again from `sm` up.
|
|
18134
|
-
*/
|
|
18135
|
-
var SETTING_ROW_VALUE = "flex w-full min-w-0 items-center gap-1 sm:flex-1";
|
|
18136
|
-
/**
|
|
18137
|
-
* The value text itself. `break-all` lets an opaque token (a key, a URL) wrap
|
|
18138
|
-
* at L1 instead of being cut mid-word; `sm:truncate` restores the single-line
|
|
18139
|
-
* desktop form, where the row is wide enough for truncation to be a choice
|
|
18140
|
-
* rather than data loss.
|
|
18141
|
-
*/
|
|
18142
|
-
var SETTING_ROW_VALUE_TEXT = "min-w-0 break-all text-xs text-foreground sm:truncate";
|
|
18143
|
-
/** Section header label (uppercase tracking-wider). */
|
|
18144
|
-
var TEXT_SECTION_LABEL = "text-[10px] sm:text-[11px] font-semibold text-foreground uppercase tracking-wider";
|
|
18145
|
-
/** Field label inside a row. */
|
|
18146
|
-
var TEXT_FIELD_LABEL = "text-[11px] sm:text-xs text-foreground";
|
|
18147
|
-
/** Sub-label / hint text. */
|
|
18148
|
-
var TEXT_HINT = "text-[10px] text-foreground-subtle";
|
|
18149
|
-
/** Primary value (a metric, badge, …). */
|
|
18150
|
-
var TEXT_VALUE = "text-[11px] sm:text-xs font-medium text-foreground";
|
|
18151
|
-
/** Big metric value (StatCard). */
|
|
18152
|
-
var TEXT_METRIC = "text-base sm:text-lg font-semibold leading-tight text-foreground";
|
|
18153
|
-
/** Default compact button. */
|
|
18154
|
-
var BTN_COMPACT = "inline-flex items-center justify-center gap-1 rounded border border-border bg-surface px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-foreground hover:bg-surface-hover transition-colors disabled:opacity-40";
|
|
18155
|
-
/** Primary compact button. */
|
|
18156
|
-
var BTN_COMPACT_PRIMARY = "inline-flex items-center justify-center gap-1 rounded border border-primary/40 bg-primary/15 px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-primary hover:bg-primary/25 transition-colors disabled:opacity-40";
|
|
18157
|
-
/** Danger compact button. */
|
|
18158
|
-
var BTN_COMPACT_DANGER = "inline-flex items-center justify-center gap-1 rounded border border-danger/40 bg-danger/10 px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-danger hover:bg-danger/20 transition-colors disabled:opacity-40";
|
|
18159
|
-
/** Warning compact button (e.g. cancel-drawing). */
|
|
18160
|
-
var BTN_COMPACT_WARNING = "inline-flex items-center justify-center gap-1 rounded border border-warning/40 bg-warning/10 px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-warning hover:bg-warning/20 transition-colors disabled:opacity-40";
|
|
18161
|
-
/** Compact text/select/number input. */
|
|
18162
|
-
var INPUT_COMPACT = "w-full rounded border border-border bg-background px-1.5 py-0.5 sm:px-2 sm:py-1 text-[11px] sm:text-xs text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none";
|
|
18163
|
-
/** Chip-style toggle (zone / class selectors in ZoneRulesEditor). */
|
|
18164
|
-
var CHIP_BASE = "inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-[10px] font-medium transition-colors";
|
|
18165
|
-
var CHIP_ACTIVE = "border-primary/50 bg-primary/15 text-primary";
|
|
18166
|
-
var CHIP_INACTIVE = "border-border bg-surface text-foreground-subtle hover:bg-surface-hover";
|
|
18167
|
-
//#endregion
|
|
18168
|
-
//#region src/composites/setting-row.tsx
|
|
18169
|
-
function SettingRow({ label, children, actions, elements = "plain", className, valueClassName }) {
|
|
18170
|
-
const LabelTag = elements === "description" ? "dt" : "span";
|
|
18171
|
-
const ValueTag = elements === "description" ? "dd" : "div";
|
|
19375
|
+
function Pagination({ currentPage, totalItems, pageSize, onPageChange, itemNoun = "devices" }) {
|
|
19376
|
+
if (totalItems <= pageSize) return null;
|
|
19377
|
+
const totalPages = Math.ceil(totalItems / pageSize);
|
|
19378
|
+
const startIdx = (currentPage - 1) * pageSize + 1;
|
|
19379
|
+
const endIdx = Math.min(currentPage * pageSize, totalItems);
|
|
19380
|
+
const buttons = computePageButtons(currentPage, totalPages);
|
|
18172
19381
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
18173
|
-
"
|
|
18174
|
-
|
|
18175
|
-
|
|
18176
|
-
|
|
18177
|
-
|
|
18178
|
-
|
|
18179
|
-
|
|
18180
|
-
|
|
18181
|
-
|
|
18182
|
-
|
|
18183
|
-
|
|
18184
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
18187
|
-
|
|
18188
|
-
|
|
18189
|
-
|
|
19382
|
+
className: "flex items-center justify-between gap-4 flex-wrap pt-2 text-[11px] text-foreground-subtle",
|
|
19383
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
19384
|
+
"Showing ",
|
|
19385
|
+
startIdx,
|
|
19386
|
+
"–",
|
|
19387
|
+
endIdx,
|
|
19388
|
+
" of ",
|
|
19389
|
+
totalItems,
|
|
19390
|
+
" ",
|
|
19391
|
+
itemNoun
|
|
19392
|
+
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
19393
|
+
className: "inline-flex items-center gap-1",
|
|
19394
|
+
children: [
|
|
19395
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
19396
|
+
type: "button",
|
|
19397
|
+
"aria-label": "Previous page",
|
|
19398
|
+
disabled: currentPage === 1,
|
|
19399
|
+
onClick: () => onPageChange(currentPage - 1),
|
|
19400
|
+
className: cn("inline-flex items-center justify-center h-7 w-7 rounded border border-border bg-surface text-foreground-subtle", "hover:text-foreground hover:bg-surface-hover transition-colors", "disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-surface"),
|
|
19401
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronLeft, { className: "h-3 w-3" })
|
|
19402
|
+
}),
|
|
19403
|
+
buttons.map((b, i) => {
|
|
19404
|
+
if (b.kind === "ellipsis") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
19405
|
+
className: "px-1 text-foreground-subtle/60 select-none",
|
|
19406
|
+
children: "…"
|
|
19407
|
+
}, `e-${i}`);
|
|
19408
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
19409
|
+
type: "button",
|
|
19410
|
+
onClick: () => onPageChange(b.n),
|
|
19411
|
+
className: cn("inline-flex items-center justify-center h-7 min-w-7 px-1.5 rounded border text-[10.5px] font-medium transition-colors", b.n === currentPage ? "border-primary/40 bg-primary/10 text-primary" : "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface-hover"),
|
|
19412
|
+
children: b.n
|
|
19413
|
+
}, `p-${b.n}`);
|
|
19414
|
+
}),
|
|
19415
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
19416
|
+
type: "button",
|
|
19417
|
+
"aria-label": "Next page",
|
|
19418
|
+
disabled: currentPage === totalPages,
|
|
19419
|
+
onClick: () => onPageChange(currentPage + 1),
|
|
19420
|
+
className: cn("inline-flex items-center justify-center h-7 w-7 rounded border border-border bg-surface text-foreground-subtle", "hover:text-foreground hover:bg-surface-hover transition-colors", "disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-surface"),
|
|
19421
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRight, { className: "h-3 w-3" })
|
|
19422
|
+
})
|
|
19423
|
+
]
|
|
18190
19424
|
})]
|
|
18191
19425
|
});
|
|
18192
19426
|
}
|
|
18193
19427
|
//#endregion
|
|
18194
|
-
//#region src/composites/
|
|
18195
|
-
function
|
|
18196
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
18197
|
-
|
|
18198
|
-
|
|
18199
|
-
|
|
18200
|
-
|
|
18201
|
-
|
|
18202
|
-
}
|
|
18203
|
-
|
|
18204
|
-
|
|
18205
|
-
|
|
18206
|
-
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
|
|
18210
|
-
|
|
18211
|
-
|
|
18212
|
-
|
|
18213
|
-
|
|
18214
|
-
|
|
18215
|
-
|
|
18216
|
-
|
|
18217
|
-
|
|
18218
|
-
if (!sameColumn || currentDir === null) return "asc";
|
|
18219
|
-
return currentDir === "asc" ? "desc" : null;
|
|
18220
|
-
}
|
|
18221
|
-
/**
|
|
18222
|
-
* Toggle a column's sort on header click: none → asc → desc → none.
|
|
18223
|
-
* Clicking a DIFFERENT column starts it fresh at `asc`.
|
|
18224
|
-
*/
|
|
18225
|
-
function nextTableSort(current, columnKey) {
|
|
18226
|
-
const dir = nextSortDirection(current !== null && current.columnKey === columnKey, current === null ? null : current.dir);
|
|
18227
|
-
return dir === null ? null : {
|
|
18228
|
-
columnKey,
|
|
18229
|
-
dir
|
|
18230
|
-
};
|
|
18231
|
-
}
|
|
18232
|
-
/**
|
|
18233
|
-
* Ascending comparison of two cell values.
|
|
18234
|
-
*
|
|
18235
|
-
* Numbers compare numerically and strings compare with `localeCompare` — a
|
|
18236
|
-
* numeric column must never be ordered as text ("100" before "9"), and a text
|
|
18237
|
-
* column must respect the locale's collation. A mixed column (one row a
|
|
18238
|
-
* number, another a string) falls back to string comparison of both, which is
|
|
18239
|
-
* the only total order available; it is not a shape any caller should build on
|
|
18240
|
-
* purpose.
|
|
18241
|
-
*
|
|
18242
|
-
* `null` is NOT handled here — {@link sortRowsByColumn} pulls it out first,
|
|
18243
|
-
* because its placement is direction-independent and a comparator that is
|
|
18244
|
-
* multiplied by the direction factor cannot express that.
|
|
18245
|
-
*/
|
|
18246
|
-
function compareValues(a, b) {
|
|
18247
|
-
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
18248
|
-
return String(a).localeCompare(String(b));
|
|
18249
|
-
}
|
|
18250
|
-
/**
|
|
18251
|
-
* Return a NEW array of `rows` ordered by `sort`.
|
|
18252
|
-
*
|
|
18253
|
-
* `valueOf` is the column's `sortValue` — it is called once per row per
|
|
18254
|
-
* comparison, so it must be cheap (read a field, do not format a string).
|
|
18255
|
-
* `sort === null` ⇒ a fresh shallow copy in the incoming order.
|
|
18256
|
-
*/
|
|
18257
|
-
function sortRowsByColumn(rows, sort, valueOf) {
|
|
18258
|
-
if (sort === null) return [...rows];
|
|
18259
|
-
const factor = sort.dir === "asc" ? 1 : -1;
|
|
18260
|
-
return rows.toSorted((a, b) => {
|
|
18261
|
-
const av = valueOf(a);
|
|
18262
|
-
const bv = valueOf(b);
|
|
18263
|
-
if (av === null || bv === null) {
|
|
18264
|
-
if (av === bv) return 0;
|
|
18265
|
-
return av === null ? 1 : -1;
|
|
18266
|
-
}
|
|
18267
|
-
return factor * compareValues(av, bv);
|
|
18268
|
-
});
|
|
18269
|
-
}
|
|
18270
|
-
/** Derive the `<th>` `aria-sort` for `columnKey` from the table's sort state. */
|
|
18271
|
-
function ariaSortForColumn(sort, columnKey) {
|
|
18272
|
-
if (sort === null || sort.columnKey !== columnKey) return "none";
|
|
18273
|
-
return sort.dir === "asc" ? "ascending" : "descending";
|
|
18274
|
-
}
|
|
18275
|
-
//#endregion
|
|
18276
|
-
//#region src/composites/data-table.tsx
|
|
18277
|
-
/**
|
|
18278
|
-
* DataTable — shared responsive table for admin pages.
|
|
18279
|
-
*
|
|
18280
|
-
* Recipes baked in:
|
|
18281
|
-
* - `overflow-x-auto` wrapper so narrow viewports don't clip the
|
|
18282
|
-
* right edge (the rounded border survives the scroll).
|
|
18283
|
-
* - `min-w-[Npx]` on the inner table so columns don't squash below
|
|
18284
|
-
* readable width before the scroll kicks in.
|
|
18285
|
-
* - `whitespace-nowrap` on headers — column titles never wrap to
|
|
18286
|
-
* two lines (`AUTO-KILL` was the regression that triggered this).
|
|
18287
|
-
* - Themed `bg-surface` + `border-border` so contrast works in both
|
|
18288
|
-
* light and dark themes.
|
|
18289
|
-
*
|
|
18290
|
-
* Column model: caller supplies `columns: DataColumn<T>[]` and
|
|
18291
|
-
* `rows: T[]`. Each column declares `header`, optional `align`,
|
|
18292
|
-
* optional `nowrap`, and a `render(row) => ReactNode` that produces
|
|
18293
|
-
* the cell content.
|
|
18294
|
-
*
|
|
18295
|
-
* <DataTable
|
|
18296
|
-
* columns={[
|
|
18297
|
-
* { key: 'username', header: 'Username', render: (u) => u.username },
|
|
18298
|
-
* { key: 'role', header: 'Role', render: (u) => <RoleBadge .../> },
|
|
18299
|
-
* { key: 'actions', header: '', align: 'right', render: (u) => <Actions .../> },
|
|
18300
|
-
* ]}
|
|
18301
|
-
* rows={users}
|
|
18302
|
-
* minWidthPx={560}
|
|
18303
|
-
* />
|
|
18304
|
-
*
|
|
18305
|
-
* If finer header markup is needed, pass `headerRender` instead of
|
|
18306
|
-
* `header` (e.g. for a sortable click affordance).
|
|
18307
|
-
*
|
|
18308
|
-
* ── Narrow viewports ──────────────────────────────────────────────────────
|
|
18309
|
-
* The table stays a table. The wrapper is `overflow-x-auto`, so a phone
|
|
18310
|
-
* scrolls the columns sideways INSIDE the table — the page body never moves
|
|
18311
|
-
* sideways, and the cells stay one line (`whitespace-nowrap`). That is the
|
|
18312
|
-
* default (`mobileMode="scroll"`). Cards are opt-in via `mobileMode="cards"`;
|
|
18313
|
-
* `auto` still applies the old column-count rule for a surface that wants it.
|
|
18314
|
-
* See `resolveTableLayout`.
|
|
18315
|
-
*
|
|
18316
|
-
* ── Scrolling the BODY, not the page ──────────────────────────────────────
|
|
18317
|
-
* `maxBodyHeight` caps the table and scrolls its rows INSIDE the container
|
|
18318
|
-
* with the column header pinned. It is ONE prop, not two, because "this list
|
|
18319
|
-
* scrolls internally" and "its header stays visible" are the same decision: a
|
|
18320
|
-
* capped table whose header scrolls away is a list of unlabelled cells.
|
|
18321
|
-
*
|
|
18322
|
-
* Use it wherever the row count is open-ended (rules, targets, delivery
|
|
18323
|
-
* history) so the page keeps its own header, filters and actions in view while
|
|
18324
|
-
* the operator moves through the rows.
|
|
18325
|
-
*/
|
|
18326
|
-
/**
|
|
18327
|
-
* The default cap for an internally-scrolling table.
|
|
18328
|
-
*
|
|
18329
|
-
* `60vh` leaves the page header, its filters and its actions on screen at
|
|
18330
|
-
* every viewport while still showing ~10 rows — and it is a fraction, so it
|
|
18331
|
-
* does not need a breakpoint of its own.
|
|
18332
|
-
*/
|
|
18333
|
-
var TABLE_SCROLL_MAX_H = "max-h-[60vh]";
|
|
18334
|
-
var ALIGN_CLASS = {
|
|
18335
|
-
left: "text-left",
|
|
18336
|
-
right: "text-right",
|
|
18337
|
-
center: "text-center"
|
|
18338
|
-
};
|
|
18339
|
-
function DataTable({ columns, rows, rowKey, onRowClick, minWidthPx = 480, emptyMessage, className, bordered = true, rowClassName, mobileMode = "scroll", maxBodyHeight, defaultSort }) {
|
|
18340
|
-
const isNarrow = useIsMobile();
|
|
18341
|
-
const [sort, setSort] = (0, react$1.useState)(defaultSort ?? null);
|
|
18342
|
-
const layout = resolveTableLayout({
|
|
18343
|
-
mode: mobileMode,
|
|
18344
|
-
columnCount: columns.length,
|
|
18345
|
-
isNarrow
|
|
18346
|
-
});
|
|
18347
|
-
const scrolls = maxBodyHeight !== void 0;
|
|
18348
|
-
const sortedColumn = (0, react$1.useMemo)(() => sort === null ? void 0 : columns.find((col) => col.key === sort.columnKey && col.sortValue !== void 0), [columns, sort]);
|
|
18349
|
-
const sortValue = sortedColumn?.sortValue;
|
|
18350
|
-
const displayRows = (0, react$1.useMemo)(() => sortValue === void 0 ? rows : sortRowsByColumn(rows, sort, sortValue), [
|
|
18351
|
-
rows,
|
|
18352
|
-
sort,
|
|
18353
|
-
sortValue
|
|
18354
|
-
]);
|
|
18355
|
-
const activeSort = sortedColumn === void 0 ? null : sort;
|
|
18356
|
-
if (rows.length === 0 && emptyMessage) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
18357
|
-
className: `rounded-lg ${bordered ? "border border-border" : ""} bg-surface px-3 py-4 text-xs text-foreground-subtle text-center ${className ?? ""}`,
|
|
18358
|
-
children: emptyMessage
|
|
18359
|
-
});
|
|
18360
|
-
if (rows.length === 0) return null;
|
|
18361
|
-
if (layout === "cards") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
18362
|
-
className: `space-y-2 ${scrolls ? `${maxBodyHeight} overflow-y-auto` : ""} ${className ?? ""}`,
|
|
18363
|
-
children: displayRows.map((row, rowIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableCard, {
|
|
18364
|
-
row,
|
|
18365
|
-
rowIndex,
|
|
18366
|
-
columns,
|
|
18367
|
-
bordered,
|
|
18368
|
-
onRowClick,
|
|
18369
|
-
rowClassName
|
|
18370
|
-
}, rowKey ? rowKey(row, rowIndex) : rowIndex))
|
|
18371
|
-
});
|
|
18372
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
18373
|
-
className: [
|
|
18374
|
-
"rounded-lg",
|
|
18375
|
-
bordered ? "border border-border" : "",
|
|
18376
|
-
"bg-surface overflow-x-auto",
|
|
18377
|
-
scrolls ? `${maxBodyHeight} overflow-y-auto` : "",
|
|
18378
|
-
className ?? ""
|
|
18379
|
-
].filter(Boolean).join(" "),
|
|
18380
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
18381
|
-
className: `w-full text-xs ${scrolls ? "border-separate border-spacing-0" : ""}`,
|
|
18382
|
-
style: { minWidth: `${minWidthPx}px` },
|
|
18383
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", {
|
|
18384
|
-
className: "bg-surface",
|
|
18385
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: columns.map((col) => {
|
|
18386
|
-
const align = col.align ?? "left";
|
|
18387
|
-
const nowrap = col.nowrap ?? true;
|
|
18388
|
-
const sortable = col.sortValue !== void 0;
|
|
18389
|
-
const isSorted = activeSort !== null && activeSort.columnKey === col.key;
|
|
18390
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
18391
|
-
"aria-sort": sortable ? ariaSortForColumn(activeSort, col.key) : void 0,
|
|
18392
|
-
title: sortable ? void 0 : col.notSortableReason,
|
|
18393
|
-
className: [
|
|
18394
|
-
"px-3 py-2 text-foreground-subtle font-medium bg-surface border-b border-border",
|
|
18395
|
-
scrolls ? "sticky top-0 z-10" : "",
|
|
18396
|
-
ALIGN_CLASS[align],
|
|
18397
|
-
nowrap ? "whitespace-nowrap" : "",
|
|
18398
|
-
col.width ?? ""
|
|
18399
|
-
].filter(Boolean).join(" "),
|
|
18400
|
-
children: sortable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SortHeaderButton, {
|
|
18401
|
-
label: col.headerRender ? col.headerRender() : col.header,
|
|
18402
|
-
active: isSorted,
|
|
18403
|
-
dir: isSorted && activeSort !== null ? activeSort.dir : "asc",
|
|
18404
|
-
onSort: () => setSort((prev) => nextTableSort(prev, col.key))
|
|
18405
|
-
}) : col.headerRender ? col.headerRender() : col.header
|
|
18406
|
-
}, col.key);
|
|
18407
|
-
}) })
|
|
18408
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: displayRows.map((row, rowIndex) => {
|
|
18409
|
-
const key = rowKey ? rowKey(row, rowIndex) : rowIndex;
|
|
18410
|
-
const extra = rowClassName?.(row, rowIndex) ?? "";
|
|
18411
|
-
const interactive = onRowClick !== void 0;
|
|
18412
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
|
|
18413
|
-
onClick: interactive ? () => onRowClick(row, rowIndex) : void 0,
|
|
18414
|
-
className: [interactive ? "cursor-pointer hover:bg-primary/5" : "hover:bg-primary/5", extra].filter(Boolean).join(" "),
|
|
18415
|
-
children: columns.map((col) => {
|
|
18416
|
-
const align = col.align ?? "left";
|
|
18417
|
-
const nowrap = col.nowrap ?? true;
|
|
18418
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
18419
|
-
className: [
|
|
18420
|
-
"px-3 py-2 text-foreground border-b border-border",
|
|
18421
|
-
ALIGN_CLASS[align],
|
|
18422
|
-
nowrap ? "whitespace-nowrap" : ""
|
|
18423
|
-
].filter(Boolean).join(" "),
|
|
18424
|
-
children: col.render(row, rowIndex)
|
|
18425
|
-
}, col.key);
|
|
18426
|
-
})
|
|
18427
|
-
}, key);
|
|
18428
|
-
}) })]
|
|
18429
|
-
})
|
|
18430
|
-
});
|
|
18431
|
-
}
|
|
18432
|
-
/**
|
|
18433
|
-
* One row as a card. Columns that carry a plain-text `header` become labelled
|
|
18434
|
-
* `SettingRow`s — the label is the column title and the value gets the full
|
|
18435
|
-
* card width. Columns with no text header (an actions column, a
|
|
18436
|
-
* `headerRender`-only column) have no label to show, so they render as a
|
|
18437
|
-
* full-width strip at the foot of the card.
|
|
18438
|
-
*/
|
|
18439
|
-
function DataTableCard({ row, rowIndex, columns, bordered, onRowClick, rowClassName }) {
|
|
18440
|
-
const labelled = columns.filter((col) => col.header !== void 0 && col.header !== "");
|
|
18441
|
-
const unlabelled = columns.filter((col) => col.header === void 0 || col.header === "");
|
|
18442
|
-
const interactive = onRowClick !== void 0;
|
|
18443
|
-
const extra = rowClassName?.(row, rowIndex) ?? "";
|
|
18444
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
18445
|
-
"data-data-table-card": "",
|
|
18446
|
-
onClick: interactive ? () => onRowClick(row, rowIndex) : void 0,
|
|
18447
|
-
className: [
|
|
18448
|
-
"rounded-lg bg-surface px-3 py-1.5 divide-y divide-border-subtle",
|
|
18449
|
-
bordered ? "border border-border" : "",
|
|
18450
|
-
interactive ? "cursor-pointer hover:bg-primary/5" : "",
|
|
18451
|
-
extra
|
|
18452
|
-
].filter(Boolean).join(" "),
|
|
18453
|
-
children: [labelled.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
|
|
18454
|
-
label: col.header,
|
|
18455
|
-
children: col.render(row, rowIndex)
|
|
18456
|
-
}, col.key)), unlabelled.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
18457
|
-
className: "flex flex-wrap items-center justify-end gap-2 py-1.5",
|
|
18458
|
-
children: unlabelled.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: col.render(row, rowIndex) }, col.key))
|
|
18459
|
-
})]
|
|
18460
|
-
});
|
|
18461
|
-
}
|
|
18462
|
-
//#endregion
|
|
18463
|
-
//#region src/composites/device-list/pagination.tsx
|
|
18464
|
-
var NEIGHBORS = 2;
|
|
18465
|
-
function computePageButtons(currentPage, totalPages) {
|
|
18466
|
-
if (totalPages <= 1) return [{
|
|
18467
|
-
kind: "page",
|
|
18468
|
-
n: 1
|
|
18469
|
-
}];
|
|
18470
|
-
if (totalPages <= 7) {
|
|
18471
|
-
const all = [];
|
|
18472
|
-
for (let i = 1; i <= totalPages; i++) all.push({
|
|
18473
|
-
kind: "page",
|
|
18474
|
-
n: i
|
|
18475
|
-
});
|
|
18476
|
-
return all;
|
|
18477
|
-
}
|
|
18478
|
-
const result = [];
|
|
18479
|
-
const leftBound = Math.max(2, currentPage - NEIGHBORS);
|
|
18480
|
-
const rightBound = Math.min(totalPages - 1, currentPage + NEIGHBORS);
|
|
18481
|
-
result.push({
|
|
18482
|
-
kind: "page",
|
|
18483
|
-
n: 1
|
|
18484
|
-
});
|
|
18485
|
-
if (leftBound > 2) result.push({
|
|
18486
|
-
kind: "ellipsis",
|
|
18487
|
-
side: "left"
|
|
18488
|
-
});
|
|
18489
|
-
for (let i = leftBound; i <= rightBound; i++) result.push({
|
|
18490
|
-
kind: "page",
|
|
18491
|
-
n: i
|
|
18492
|
-
});
|
|
18493
|
-
if (rightBound < totalPages - 1) result.push({
|
|
18494
|
-
kind: "ellipsis",
|
|
18495
|
-
side: "right"
|
|
18496
|
-
});
|
|
18497
|
-
result.push({
|
|
18498
|
-
kind: "page",
|
|
18499
|
-
n: totalPages
|
|
18500
|
-
});
|
|
18501
|
-
return result;
|
|
18502
|
-
}
|
|
18503
|
-
function Pagination({ currentPage, totalItems, pageSize, onPageChange, itemNoun = "devices" }) {
|
|
18504
|
-
if (totalItems <= pageSize) return null;
|
|
18505
|
-
const totalPages = Math.ceil(totalItems / pageSize);
|
|
18506
|
-
const startIdx = (currentPage - 1) * pageSize + 1;
|
|
18507
|
-
const endIdx = Math.min(currentPage * pageSize, totalItems);
|
|
18508
|
-
const buttons = computePageButtons(currentPage, totalPages);
|
|
18509
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
18510
|
-
className: "flex items-center justify-between gap-4 flex-wrap pt-2 text-[11px] text-foreground-subtle",
|
|
18511
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
18512
|
-
"Showing ",
|
|
18513
|
-
startIdx,
|
|
18514
|
-
"–",
|
|
18515
|
-
endIdx,
|
|
18516
|
-
" of ",
|
|
18517
|
-
totalItems,
|
|
18518
|
-
" ",
|
|
18519
|
-
itemNoun
|
|
18520
|
-
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
18521
|
-
className: "inline-flex items-center gap-1",
|
|
18522
|
-
children: [
|
|
18523
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
18524
|
-
type: "button",
|
|
18525
|
-
"aria-label": "Previous page",
|
|
18526
|
-
disabled: currentPage === 1,
|
|
18527
|
-
onClick: () => onPageChange(currentPage - 1),
|
|
18528
|
-
className: cn("inline-flex items-center justify-center h-7 w-7 rounded border border-border bg-surface text-foreground-subtle", "hover:text-foreground hover:bg-surface-hover transition-colors", "disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-surface"),
|
|
18529
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronLeft, { className: "h-3 w-3" })
|
|
18530
|
-
}),
|
|
18531
|
-
buttons.map((b, i) => {
|
|
18532
|
-
if (b.kind === "ellipsis") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
18533
|
-
className: "px-1 text-foreground-subtle/60 select-none",
|
|
18534
|
-
children: "…"
|
|
18535
|
-
}, `e-${i}`);
|
|
18536
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
18537
|
-
type: "button",
|
|
18538
|
-
onClick: () => onPageChange(b.n),
|
|
18539
|
-
className: cn("inline-flex items-center justify-center h-7 min-w-7 px-1.5 rounded border text-[10.5px] font-medium transition-colors", b.n === currentPage ? "border-primary/40 bg-primary/10 text-primary" : "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface-hover"),
|
|
18540
|
-
children: b.n
|
|
18541
|
-
}, `p-${b.n}`);
|
|
18542
|
-
}),
|
|
18543
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
18544
|
-
type: "button",
|
|
18545
|
-
"aria-label": "Next page",
|
|
18546
|
-
disabled: currentPage === totalPages,
|
|
18547
|
-
onClick: () => onPageChange(currentPage + 1),
|
|
18548
|
-
className: cn("inline-flex items-center justify-center h-7 w-7 rounded border border-border bg-surface text-foreground-subtle", "hover:text-foreground hover:bg-surface-hover transition-colors", "disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-surface"),
|
|
18549
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRight, { className: "h-3 w-3" })
|
|
18550
|
-
})
|
|
18551
|
-
]
|
|
18552
|
-
})]
|
|
18553
|
-
});
|
|
18554
|
-
}
|
|
18555
|
-
//#endregion
|
|
18556
|
-
//#region src/composites/empty-state.tsx
|
|
18557
|
-
function EmptyState({ icon: Icon, title, description, action, className }) {
|
|
18558
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
18559
|
-
className: cn("flex flex-col items-center justify-center gap-3 py-12", className),
|
|
18560
|
-
children: [
|
|
18561
|
-
Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
18562
|
-
className: "h-12 w-12 text-foreground-subtle",
|
|
18563
|
-
"aria-hidden": "true"
|
|
18564
|
-
}),
|
|
18565
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
18566
|
-
className: "flex flex-col items-center gap-1 text-center",
|
|
18567
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
18568
|
-
className: "text-foreground-muted text-sm font-medium",
|
|
18569
|
-
children: title
|
|
18570
|
-
}), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
18571
|
-
className: "text-foreground-subtle text-xs max-w-xs",
|
|
18572
|
-
children: description
|
|
18573
|
-
})]
|
|
18574
|
-
}),
|
|
18575
|
-
action && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
18576
|
-
className: "mt-1",
|
|
18577
|
-
children: action
|
|
18578
|
-
})
|
|
18579
|
-
]
|
|
19428
|
+
//#region src/composites/empty-state.tsx
|
|
19429
|
+
function EmptyState({ icon: Icon, title, description, action, className }) {
|
|
19430
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19431
|
+
className: cn("flex flex-col items-center justify-center gap-3 py-12", className),
|
|
19432
|
+
children: [
|
|
19433
|
+
Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
19434
|
+
className: "h-12 w-12 text-foreground-subtle",
|
|
19435
|
+
"aria-hidden": "true"
|
|
19436
|
+
}),
|
|
19437
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19438
|
+
className: "flex flex-col items-center gap-1 text-center",
|
|
19439
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
19440
|
+
className: "text-foreground-muted text-sm font-medium",
|
|
19441
|
+
children: title
|
|
19442
|
+
}), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
19443
|
+
className: "text-foreground-subtle text-xs max-w-xs",
|
|
19444
|
+
children: description
|
|
19445
|
+
})]
|
|
19446
|
+
}),
|
|
19447
|
+
action && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19448
|
+
className: "mt-1",
|
|
19449
|
+
children: action
|
|
19450
|
+
})
|
|
19451
|
+
]
|
|
18580
19452
|
});
|
|
18581
19453
|
}
|
|
18582
19454
|
//#endregion
|
|
@@ -19047,6 +19919,38 @@ function ClipPanel({ deviceId, clip, profile, onProfileChange }) {
|
|
|
19047
19919
|
});
|
|
19048
19920
|
}
|
|
19049
19921
|
//#endregion
|
|
19922
|
+
//#region src/primitives/badge.tsx
|
|
19923
|
+
var badgeVariants = cva("inline-flex items-center rounded-full text-xs font-medium px-2 py-0.5", {
|
|
19924
|
+
variants: {
|
|
19925
|
+
variant: {
|
|
19926
|
+
default: "bg-surface-hover text-foreground",
|
|
19927
|
+
success: "bg-success/15 text-success",
|
|
19928
|
+
warning: "bg-warning/15 text-warning",
|
|
19929
|
+
danger: "bg-danger/15 text-danger",
|
|
19930
|
+
info: "bg-info/15 text-info"
|
|
19931
|
+
},
|
|
19932
|
+
styleVariant: {
|
|
19933
|
+
solid: "",
|
|
19934
|
+
outline: "border bg-transparent"
|
|
19935
|
+
}
|
|
19936
|
+
},
|
|
19937
|
+
defaultVariants: {
|
|
19938
|
+
variant: "default",
|
|
19939
|
+
styleVariant: "solid"
|
|
19940
|
+
}
|
|
19941
|
+
});
|
|
19942
|
+
var Badge = (0, react$1.forwardRef)(({ className, variant, style, ...props }, ref) => {
|
|
19943
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
19944
|
+
ref,
|
|
19945
|
+
className: cn(badgeVariants({
|
|
19946
|
+
variant,
|
|
19947
|
+
styleVariant: style
|
|
19948
|
+
}), className),
|
|
19949
|
+
...props
|
|
19950
|
+
});
|
|
19951
|
+
});
|
|
19952
|
+
Badge.displayName = "Badge";
|
|
19953
|
+
//#endregion
|
|
19050
19954
|
//#region src/lib/format-duration.ts
|
|
19051
19955
|
/**
|
|
19052
19956
|
* How long something lasted, the way this product says it.
|
|
@@ -19560,24 +20464,6 @@ function describeSource(source) {
|
|
|
19560
20464
|
answered: copy.answered
|
|
19561
20465
|
};
|
|
19562
20466
|
}
|
|
19563
|
-
var MINUTE_MS$1 = 6e4;
|
|
19564
|
-
var HOUR_MS$1 = 36e5;
|
|
19565
|
-
var DAY_MS$2 = 864e5;
|
|
19566
|
-
/**
|
|
19567
|
-
* How old a source's catalog is, or null when it did not say.
|
|
19568
|
-
*
|
|
19569
|
-
* Null is NOT "now". A source that omits `catalogAsOf` was read live and has
|
|
19570
|
-
* no age to show; inventing "just now" for a source that never told us would
|
|
19571
|
-
* be the same lie as drawing a cached list as current (D549 decision 13).
|
|
19572
|
-
*/
|
|
19573
|
-
function formatCatalogAge(catalogAsOf, now) {
|
|
19574
|
-
if (catalogAsOf === void 0) return null;
|
|
19575
|
-
const age = Math.max(0, now - catalogAsOf);
|
|
19576
|
-
if (age < MINUTE_MS$1) return "under a minute ago";
|
|
19577
|
-
if (age < HOUR_MS$1) return `${String(Math.floor(age / MINUTE_MS$1))} min ago`;
|
|
19578
|
-
if (age < DAY_MS$2) return `${String(Math.floor(age / HOUR_MS$1))} h ago`;
|
|
19579
|
-
return `${String(Math.floor(age / DAY_MS$2))} d ago`;
|
|
19580
|
-
}
|
|
19581
20467
|
//#endregion
|
|
19582
20468
|
//#region src/composites/clips/clip-view-state.ts
|
|
19583
20469
|
function resolveClipsView(input) {
|
|
@@ -19731,56 +20617,6 @@ function resolveClipsView(input) {
|
|
|
19731
20617
|
var CLIP_PAGE_LIMIT = 500;
|
|
19732
20618
|
/** Tiles per page. The rest are a `Pagination` click away, never dropped. */
|
|
19733
20619
|
var TILES_PER_PAGE = 60;
|
|
19734
|
-
var TONE_BADGE = {
|
|
19735
|
-
ok: "success",
|
|
19736
|
-
warn: "warning",
|
|
19737
|
-
error: "danger"
|
|
19738
|
-
};
|
|
19739
|
-
var SOURCE_COLUMNS = [
|
|
19740
|
-
{
|
|
19741
|
-
key: "source",
|
|
19742
|
-
header: "Source",
|
|
19743
|
-
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19744
|
-
className: "min-w-0",
|
|
19745
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19746
|
-
className: "font-medium text-foreground",
|
|
19747
|
-
children: row.notice.label
|
|
19748
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19749
|
-
className: "text-[10px] text-foreground-subtle",
|
|
19750
|
-
children: row.notice.source
|
|
19751
|
-
})]
|
|
19752
|
-
})
|
|
19753
|
-
},
|
|
19754
|
-
{
|
|
19755
|
-
key: "state",
|
|
19756
|
-
header: "State",
|
|
19757
|
-
nowrap: false,
|
|
19758
|
-
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
19759
|
-
variant: TONE_BADGE[row.notice.tone],
|
|
19760
|
-
children: row.notice.headline
|
|
19761
|
-
})
|
|
19762
|
-
},
|
|
19763
|
-
{
|
|
19764
|
-
key: "reason",
|
|
19765
|
-
header: "Reason",
|
|
19766
|
-
nowrap: false,
|
|
19767
|
-
render: (row) => row.notice.detail === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
19768
|
-
className: "text-foreground-subtle",
|
|
19769
|
-
children: "—"
|
|
19770
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
19771
|
-
className: "break-words",
|
|
19772
|
-
children: row.notice.detail
|
|
19773
|
-
})
|
|
19774
|
-
},
|
|
19775
|
-
{
|
|
19776
|
-
key: "read",
|
|
19777
|
-
header: "Catalog read",
|
|
19778
|
-
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
19779
|
-
className: "text-foreground-subtle",
|
|
19780
|
-
children: row.catalogAge ?? "live"
|
|
19781
|
-
})
|
|
19782
|
-
}
|
|
19783
|
-
];
|
|
19784
20620
|
/** The sources that contributed NOTHING to what is on screen, and why. */
|
|
19785
20621
|
function SilentSources({ silent }) {
|
|
19786
20622
|
if (silent.length === 0) return null;
|
|
@@ -19867,7 +20703,6 @@ function ClipsSection({ deviceId }) {
|
|
|
19867
20703
|
...clips.error ? { clipsError: clips.error.message } : {},
|
|
19868
20704
|
...sources.error ? { sourcesError: sources.error.message } : {}
|
|
19869
20705
|
});
|
|
19870
|
-
const now = Date.now();
|
|
19871
20706
|
const options = (0, react$1.useMemo)(() => {
|
|
19872
20707
|
const byAddon = /* @__PURE__ */ new Map();
|
|
19873
20708
|
for (const row of sourceRows) {
|
|
@@ -19882,10 +20717,6 @@ function ClipsSection({ deviceId }) {
|
|
|
19882
20717
|
label: labels.join(" · ")
|
|
19883
20718
|
}));
|
|
19884
20719
|
}, [sourceRows]);
|
|
19885
|
-
const tableRows = (0, react$1.useMemo)(() => sourceRows.map((row) => ({
|
|
19886
|
-
notice: describeSource(row),
|
|
19887
|
-
catalogAge: formatCatalogAge(row.availability.catalogAsOf, now)
|
|
19888
|
-
})), [sourceRows]);
|
|
19889
20720
|
(0, react$1.useEffect)(() => {
|
|
19890
20721
|
setPage(1);
|
|
19891
20722
|
}, [band.fromMs, band.toMs]);
|
|
@@ -19991,19 +20822,6 @@ function ClipsSection({ deviceId }) {
|
|
|
19991
20822
|
onSelectBucket: handleSelectBucket,
|
|
19992
20823
|
densityLoading: density.status === "pending"
|
|
19993
20824
|
}),
|
|
19994
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
19995
|
-
"aria-label": "Clip sources",
|
|
19996
|
-
className: "space-y-1",
|
|
19997
|
-
children: sources.status === "pending" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, { className: "h-16 w-full" }) : tableRows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {
|
|
19998
|
-
icon: Film,
|
|
19999
|
-
title: "No provider answered for this camera",
|
|
20000
|
-
description: "Nothing claims to hold clips for it."
|
|
20001
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTable, {
|
|
20002
|
-
columns: SOURCE_COLUMNS,
|
|
20003
|
-
rows: [...tableRows],
|
|
20004
|
-
minWidthPx: 520
|
|
20005
|
-
})
|
|
20006
|
-
}),
|
|
20007
20825
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
20008
20826
|
"aria-label": "Clips",
|
|
20009
20827
|
className: "space-y-2",
|
|
@@ -22977,7 +23795,7 @@ function RecordingPanel({ deviceId }) {
|
|
|
22977
23795
|
* Module-Federation `WidgetRegistry`. ids are namespaced `host/<name>`. */
|
|
22978
23796
|
var HOST_WIDGETS = {
|
|
22979
23797
|
"host/motion-zones-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./MotionZonesSettings-BK9R9NIr.cjs")).then((m) => ({ default: m.MotionZonesSettings }))),
|
|
22980
|
-
"host/privacy-mask-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-
|
|
23798
|
+
"host/privacy-mask-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-BqjXsXZR.cjs")).then((m) => ({ default: m.PrivacyMaskSettings }))),
|
|
22981
23799
|
"host/ptz-panel": PtzPanel,
|
|
22982
23800
|
"host/navigation-panel": NavigationPanel,
|
|
22983
23801
|
"host/consumables-panel": ConsumablesPanel,
|
|
@@ -27682,6 +28500,53 @@ function TooltipContent({ className, children, ...props }) {
|
|
|
27682
28500
|
});
|
|
27683
28501
|
}
|
|
27684
28502
|
//#endregion
|
|
28503
|
+
//#region src/hooks/use-is-mobile.ts
|
|
28504
|
+
var MOBILE_QUERY = "(max-width: 767px)";
|
|
28505
|
+
/**
|
|
28506
|
+
* Mid breakpoint — below this width the sidebar collapses to an
|
|
28507
|
+
* icons-only rail to reclaim canvas space. Above it (and outside
|
|
28508
|
+
* mobile) the full sidebar renders. Threshold matches Tailwind `lg`
|
|
28509
|
+
* so sidebar fans out at the same point grids switch to multi-column.
|
|
28510
|
+
*/
|
|
28511
|
+
var MID_QUERY = "(min-width: 768px) and (max-width: 1023px)";
|
|
28512
|
+
/**
|
|
28513
|
+
* `matchMedia` is universal in browsers but absent in bare DOM environments.
|
|
28514
|
+
* These hooks are called from shared primitives (`DataTable` picks its narrow
|
|
28515
|
+
* layout with one), so throwing here takes down the whole page that merely
|
|
28516
|
+
* rendered a table. Where the capability is missing, report the desktop
|
|
28517
|
+
* layout — a widescreen rendering is wrong-looking; a crashed page is gone.
|
|
28518
|
+
*/
|
|
28519
|
+
function matchQuery(query) {
|
|
28520
|
+
if (typeof window.matchMedia !== "function") return null;
|
|
28521
|
+
return window.matchMedia(query);
|
|
28522
|
+
}
|
|
28523
|
+
function subscribeQuery(query) {
|
|
28524
|
+
return (callback) => {
|
|
28525
|
+
const mql = matchQuery(query);
|
|
28526
|
+
if (mql === null) return () => {};
|
|
28527
|
+
mql.addEventListener("change", callback);
|
|
28528
|
+
return () => mql.removeEventListener("change", callback);
|
|
28529
|
+
};
|
|
28530
|
+
}
|
|
28531
|
+
function getSnapshot(query) {
|
|
28532
|
+
return () => matchQuery(query)?.matches ?? false;
|
|
28533
|
+
}
|
|
28534
|
+
function getServerSnapshot() {
|
|
28535
|
+
return false;
|
|
28536
|
+
}
|
|
28537
|
+
function useIsMobile() {
|
|
28538
|
+
return (0, react$1.useSyncExternalStore)(subscribeQuery(MOBILE_QUERY), getSnapshot(MOBILE_QUERY), getServerSnapshot);
|
|
28539
|
+
}
|
|
28540
|
+
/**
|
|
28541
|
+
* True when the viewport is in the medium (`md`) Tailwind range —
|
|
28542
|
+
* 768px ≤ width < 1024px. Use to drive layouts that should pack denser
|
|
28543
|
+
* than full-desktop but more legibly than mobile (e.g. icons-only
|
|
28544
|
+
* sidebar rail, narrower stat tiles).
|
|
28545
|
+
*/
|
|
28546
|
+
function useIsMidWidth() {
|
|
28547
|
+
return (0, react$1.useSyncExternalStore)(subscribeQuery(MID_QUERY), getSnapshot(MID_QUERY), getServerSnapshot);
|
|
28548
|
+
}
|
|
28549
|
+
//#endregion
|
|
27685
28550
|
//#region src/primitives/bottom-sheet.tsx
|
|
27686
28551
|
function BottomSheet({ open, onClose, title, children, className }) {
|
|
27687
28552
|
(0, react$1.useEffect)(() => {
|
|
@@ -30830,290 +31695,916 @@ function ToolbarButton$1({ onClick, title, children }) {
|
|
|
30830
31695
|
children
|
|
30831
31696
|
});
|
|
30832
31697
|
}
|
|
30833
|
-
async function waitForIceGathering(pc) {
|
|
30834
|
-
if (pc.iceGatheringState === "complete") return;
|
|
30835
|
-
return new Promise((resolve) => {
|
|
30836
|
-
let settled = false;
|
|
30837
|
-
let poll;
|
|
30838
|
-
const finish = () => {
|
|
30839
|
-
if (settled) return;
|
|
30840
|
-
settled = true;
|
|
30841
|
-
if (poll) clearInterval(poll);
|
|
30842
|
-
pc.removeEventListener("icegatheringstatechange", handler);
|
|
30843
|
-
resolve();
|
|
30844
|
-
};
|
|
30845
|
-
const handler = () => {
|
|
30846
|
-
if (pc.iceGatheringState === "complete") finish();
|
|
30847
|
-
};
|
|
30848
|
-
pc.addEventListener("icegatheringstatechange", handler);
|
|
30849
|
-
const start = Date.now();
|
|
30850
|
-
poll = setInterval(() => {
|
|
30851
|
-
const sdp = pc.localDescription?.sdp ?? "";
|
|
30852
|
-
if (/ typ host/.test(sdp) && Date.now() - start >= 600) finish();
|
|
30853
|
-
}, 100);
|
|
30854
|
-
setTimeout(finish, 2500);
|
|
31698
|
+
async function waitForIceGathering(pc) {
|
|
31699
|
+
if (pc.iceGatheringState === "complete") return;
|
|
31700
|
+
return new Promise((resolve) => {
|
|
31701
|
+
let settled = false;
|
|
31702
|
+
let poll;
|
|
31703
|
+
const finish = () => {
|
|
31704
|
+
if (settled) return;
|
|
31705
|
+
settled = true;
|
|
31706
|
+
if (poll) clearInterval(poll);
|
|
31707
|
+
pc.removeEventListener("icegatheringstatechange", handler);
|
|
31708
|
+
resolve();
|
|
31709
|
+
};
|
|
31710
|
+
const handler = () => {
|
|
31711
|
+
if (pc.iceGatheringState === "complete") finish();
|
|
31712
|
+
};
|
|
31713
|
+
pc.addEventListener("icegatheringstatechange", handler);
|
|
31714
|
+
const start = Date.now();
|
|
31715
|
+
poll = setInterval(() => {
|
|
31716
|
+
const sdp = pc.localDescription?.sdp ?? "";
|
|
31717
|
+
if (/ typ host/.test(sdp) && Date.now() - start >= 600) finish();
|
|
31718
|
+
}, 100);
|
|
31719
|
+
setTimeout(finish, 2500);
|
|
31720
|
+
});
|
|
31721
|
+
}
|
|
31722
|
+
//#endregion
|
|
31723
|
+
//#region src/composites/cap-settings/scene-monitor-copy.ts
|
|
31724
|
+
/** The lighting variants an operator is OFFERED. The resolver can also report
|
|
31725
|
+
* `night`, `dawn` and `dusk`; those are shown when present but never demanded,
|
|
31726
|
+
* because most installs only ever see day and IR. */
|
|
31727
|
+
var SCENE_CAPTURE_VARIANTS = ["day", "ir"];
|
|
31728
|
+
var VARIANT_LABEL = {
|
|
31729
|
+
day: "Daylight",
|
|
31730
|
+
ir: "Night (IR)",
|
|
31731
|
+
night: "Night",
|
|
31732
|
+
dawn: "Dawn",
|
|
31733
|
+
dusk: "Dusk"
|
|
31734
|
+
};
|
|
31735
|
+
function variantLabel(condition) {
|
|
31736
|
+
return VARIANT_LABEL[condition] ?? condition;
|
|
31737
|
+
}
|
|
31738
|
+
/**
|
|
31739
|
+
* "Daylight ✓ · Night (IR) ✗ — optional; checks in night (ir) are skipped."
|
|
31740
|
+
*
|
|
31741
|
+
* Converts the silent cross-condition fallback this engine used to have into an
|
|
31742
|
+
* operator-visible fact. Without it, a scene with only a daylight reference
|
|
31743
|
+
* simply stops answering after sunset and nothing anywhere says why.
|
|
31744
|
+
*
|
|
31745
|
+
* It used to end *"reports 'can't tell' in night (ir)"*, which was accurate and
|
|
31746
|
+
* read as a defect: the operator saw a ✗ and a complaint for a scene that was
|
|
31747
|
+
* configured exactly as they meant it. The missing half is now named as what it
|
|
31748
|
+
* is — the part you do not have to do — and the sentence says what actually
|
|
31749
|
+
* happens instead, which differs by the scene's own policy.
|
|
31750
|
+
*/
|
|
31751
|
+
function coverageLine(monitor) {
|
|
31752
|
+
const covered = new Set(monitor.coveredConditions);
|
|
31753
|
+
const parts = SCENE_CAPTURE_VARIANTS.map((v) => `${variantLabel(v)} ${covered.has(v) ? "✓" : "✗"}`);
|
|
31754
|
+
const extra = [...covered].filter((c) => !SCENE_CAPTURE_VARIANTS.some((v) => v === c));
|
|
31755
|
+
for (const c of extra) parts.push(`${variantLabel(c)} ✓`);
|
|
31756
|
+
const missing = SCENE_CAPTURE_VARIANTS.filter((v) => !covered.has(v));
|
|
31757
|
+
if (missing.length === 0) return parts.join(" · ");
|
|
31758
|
+
if (covered.size === 0) return `${parts.join(" · ")} — nothing captured yet, so this scene is not watching.`;
|
|
31759
|
+
const names = missing.map((v) => variantLabel(v).toLowerCase()).join(" and ");
|
|
31760
|
+
return monitor.onUncoveredCondition === "judge-anyway" ? `${parts.join(" · ")} — ${names} judged against the references you do have.` : `${parts.join(" · ")} — optional; checks in ${names} are skipped.`;
|
|
31761
|
+
}
|
|
31762
|
+
/**
|
|
31763
|
+
* The calm sentence for a scene that is sitting the current light out — `null`
|
|
31764
|
+
* whenever it is checking normally.
|
|
31765
|
+
*
|
|
31766
|
+
* Separate from {@link statusLine} because it is not a verdict and must not
|
|
31767
|
+
* read like one. A paused scene has not failed, has not lost its flag, and
|
|
31768
|
+
* needs nothing from the operator; the only thing worth saying is which light
|
|
31769
|
+
* it is waiting out and that it will pick itself back up.
|
|
31770
|
+
*/
|
|
31771
|
+
function suspensionLine(monitor) {
|
|
31772
|
+
if (monitor.suspendedCondition === null) return null;
|
|
31773
|
+
const light = variantLabel(monitor.suspendedCondition).toLowerCase();
|
|
31774
|
+
return `Paused — nothing was captured in ${light}, so checks are on hold until the light changes back. Capture ${light} if you want it watched around the clock.`;
|
|
31775
|
+
}
|
|
31776
|
+
var UNAVAILABLE_SENTENCE = {
|
|
31777
|
+
"no-reference-for-condition": "can’t tell — nothing has been captured for this scene yet. Press “Capture daylight” to photograph what normal looks like.",
|
|
31778
|
+
"view-shifted": "can’t tell — the camera view has moved since the reference was captured. Re-capture it.",
|
|
31779
|
+
"no-vision-profile": "can’t tell — the vision model is unreachable, so the change is on hold.",
|
|
31780
|
+
"encoder-model-changed": "can’t tell — the embedding model changed, so the stored references are unreadable. Re-capture them.",
|
|
31781
|
+
"no-snapshot": "can’t tell — this camera did not return a frame."
|
|
31782
|
+
};
|
|
31783
|
+
/**
|
|
31784
|
+
* The one line under a scene's name. It answers, in order: can it judge, what
|
|
31785
|
+
* does it say, and how long has it said it.
|
|
31786
|
+
*/
|
|
31787
|
+
function statusLine(monitor, now) {
|
|
31788
|
+
if (monitor.states.length === 0) return "Not armed yet — capture the initial state to start watching.";
|
|
31789
|
+
if (monitor.verdict === "unknown") return monitor.unavailable !== null ? UNAVAILABLE_SENTENCE[monitor.unavailable] : "can’t tell right now.";
|
|
31790
|
+
const since = monitor.verdict === "diverged" ? monitor.divergedAt : monitor.armedAt;
|
|
31791
|
+
const held = since !== null ? ` for ${humanDuration(Math.max(0, now - since))}` : "";
|
|
31792
|
+
if (monitor.latched) return monitor.verdict === "diverged" ? `CHANGED${held} — press Reset when you have dealt with it.` : `Back to normal, but still flagged as changed${held}. Press Reset to clear it.`;
|
|
31793
|
+
const paused = suspensionLine(monitor);
|
|
31794
|
+
if (paused !== null) return paused;
|
|
31795
|
+
return monitor.verdict === "diverged" ? `CHANGED${held}` : `Normal${held}`;
|
|
31796
|
+
}
|
|
31797
|
+
/** The badge word. `latched` wins over `verdict`, because the latch is the
|
|
31798
|
+
* thing the operator asked to be told and it outlives the live state.
|
|
31799
|
+
*
|
|
31800
|
+
* A scene sitting out an uncovered light reads `Paused`, in the NEUTRAL tone —
|
|
31801
|
+
* never `alarm`, and never the `danger` variant the page maps that to. A
|
|
31802
|
+
* missing IR reference must not be able to paint a red badge on a scene that
|
|
31803
|
+
* is doing exactly what it was configured to do. */
|
|
31804
|
+
function badgeFor(monitor) {
|
|
31805
|
+
if (monitor.states.length === 0) return {
|
|
31806
|
+
text: "Not armed",
|
|
31807
|
+
tone: "unknown"
|
|
31808
|
+
};
|
|
31809
|
+
if (monitor.latched) return {
|
|
31810
|
+
text: "Changed",
|
|
31811
|
+
tone: "alarm"
|
|
31812
|
+
};
|
|
31813
|
+
if (monitor.suspendedCondition !== null) return {
|
|
31814
|
+
text: "Paused",
|
|
31815
|
+
tone: "unknown"
|
|
31816
|
+
};
|
|
31817
|
+
if (monitor.verdict === "unknown") return {
|
|
31818
|
+
text: "Can’t tell",
|
|
31819
|
+
tone: "unknown"
|
|
31820
|
+
};
|
|
31821
|
+
return monitor.verdict === "diverged" ? {
|
|
31822
|
+
text: "Changed",
|
|
31823
|
+
tone: "alarm"
|
|
31824
|
+
} : {
|
|
31825
|
+
text: "Normal",
|
|
31826
|
+
tone: "ok"
|
|
31827
|
+
};
|
|
31828
|
+
}
|
|
31829
|
+
/** A rect covering most of the frame is dominated by weather, sky and parked
|
|
31830
|
+
* cars: the bin is 2 % of the pixels and CLIP will not notice it leaving. The
|
|
31831
|
+
* failure is silent INSENSITIVITY, which is the worst kind — hence a warning
|
|
31832
|
+
* rather than a refusal. */
|
|
31833
|
+
var ROI_AREA_WARN_FRACTION = .4;
|
|
31834
|
+
function roiTooLarge(roi) {
|
|
31835
|
+
return roi.width * roi.height > ROI_AREA_WARN_FRACTION;
|
|
31836
|
+
}
|
|
31837
|
+
function humanDuration(ms) {
|
|
31838
|
+
const s = Math.floor(ms / 1e3);
|
|
31839
|
+
if (s < 60) return `${s}s`;
|
|
31840
|
+
const m = Math.floor(s / 60);
|
|
31841
|
+
if (m < 60) return `${m}m`;
|
|
31842
|
+
const h = Math.floor(m / 60);
|
|
31843
|
+
if (h < 48) return `${h}h`;
|
|
31844
|
+
return `${Math.floor(h / 24)}d`;
|
|
31845
|
+
}
|
|
31846
|
+
//#endregion
|
|
31847
|
+
//#region src/composites/code-block.tsx
|
|
31848
|
+
function CodeBlock({ children, maxHeight = 300, className }) {
|
|
31849
|
+
const [copied, setCopied] = (0, react$1.useState)(false);
|
|
31850
|
+
const handleCopy = (0, react$1.useCallback)(() => {
|
|
31851
|
+
navigator.clipboard.writeText(children).then(() => {
|
|
31852
|
+
setCopied(true);
|
|
31853
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
31854
|
+
});
|
|
31855
|
+
}, [children]);
|
|
31856
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
31857
|
+
className: cn("relative group", className),
|
|
31858
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollArea, {
|
|
31859
|
+
style: { maxHeight },
|
|
31860
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
31861
|
+
className: "font-mono text-xs bg-surface p-3 rounded-md border border-border-subtle",
|
|
31862
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children })
|
|
31863
|
+
})
|
|
31864
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
31865
|
+
className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity",
|
|
31866
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
|
|
31867
|
+
icon: copied ? Check : Copy,
|
|
31868
|
+
"aria-label": "Copy code",
|
|
31869
|
+
variant: "ghost",
|
|
31870
|
+
size: "sm",
|
|
31871
|
+
onClick: handleCopy
|
|
31872
|
+
})
|
|
31873
|
+
})]
|
|
31874
|
+
});
|
|
31875
|
+
}
|
|
31876
|
+
//#endregion
|
|
31877
|
+
//#region src/composites/confirm-action-button.tsx
|
|
31878
|
+
/**
|
|
31879
|
+
* ConfirmActionButton — destructive-action button with a modal confirm.
|
|
31880
|
+
*
|
|
31881
|
+
* Two-step UX for any operation operators shouldn't trigger by accident:
|
|
31882
|
+
* reboot device, restart addon, regenerate token, etc. The trigger is a
|
|
31883
|
+
* normal Button that opens a Dialog; confirming runs the async action,
|
|
31884
|
+
* the trigger spinner-disables itself for the duration, and the dialog
|
|
31885
|
+
* closes on success. Errors surface inline at the dialog's bottom.
|
|
31886
|
+
*
|
|
31887
|
+
* Generic on `TResult` so callers don't have to discard the return
|
|
31888
|
+
* value. Pass an `icon` to render it inside the trigger (e.g. RotateCw
|
|
31889
|
+
* for reboot, RefreshCw for restart).
|
|
31890
|
+
*/
|
|
31891
|
+
function ConfirmActionButton({ label, icon: Icon, title = "Confirm action", description, confirmLabel, triggerVariant = "outline", confirmVariant = "danger", size = "sm", disabled, className, action, onSuccess }) {
|
|
31892
|
+
const [open, setOpen] = (0, react$1.useState)(false);
|
|
31893
|
+
const [running, setRunning] = (0, react$1.useState)(false);
|
|
31894
|
+
const [error, setError] = (0, react$1.useState)(null);
|
|
31895
|
+
const handleConfirm = async () => {
|
|
31896
|
+
setError(null);
|
|
31897
|
+
setRunning(true);
|
|
31898
|
+
try {
|
|
31899
|
+
const result = await action();
|
|
31900
|
+
onSuccess?.(result);
|
|
31901
|
+
setOpen(false);
|
|
31902
|
+
} catch (err) {
|
|
31903
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
31904
|
+
} finally {
|
|
31905
|
+
setRunning(false);
|
|
31906
|
+
}
|
|
31907
|
+
};
|
|
31908
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
31909
|
+
type: "button",
|
|
31910
|
+
variant: triggerVariant,
|
|
31911
|
+
size,
|
|
31912
|
+
disabled,
|
|
31913
|
+
onClick: () => {
|
|
31914
|
+
setError(null);
|
|
31915
|
+
setOpen(true);
|
|
31916
|
+
},
|
|
31917
|
+
className,
|
|
31918
|
+
children: [Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "h-3.5 w-3.5" }), label]
|
|
31919
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
|
|
31920
|
+
open,
|
|
31921
|
+
onOpenChange: (next) => !running && setOpen(next),
|
|
31922
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogContent, { children: [
|
|
31923
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogTitle, {
|
|
31924
|
+
className: "flex items-center gap-2",
|
|
31925
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TriangleAlert, { className: "h-4 w-4 text-amber-400" }), title]
|
|
31926
|
+
}), typeof description === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogDescription, { children: description }) : description] }),
|
|
31927
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
31928
|
+
className: cn("mt-2 px-3 py-2 rounded border border-danger/40 bg-danger/10", "text-xs text-danger"),
|
|
31929
|
+
children: error
|
|
31930
|
+
}),
|
|
31931
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
31932
|
+
type: "button",
|
|
31933
|
+
variant: "ghost",
|
|
31934
|
+
size,
|
|
31935
|
+
disabled: running,
|
|
31936
|
+
onClick: () => setOpen(false),
|
|
31937
|
+
children: "Cancel"
|
|
31938
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
31939
|
+
type: "button",
|
|
31940
|
+
variant: confirmVariant,
|
|
31941
|
+
size,
|
|
31942
|
+
disabled: running,
|
|
31943
|
+
onClick: () => {
|
|
31944
|
+
handleConfirm();
|
|
31945
|
+
},
|
|
31946
|
+
children: [running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), confirmLabel ?? label]
|
|
31947
|
+
})] })
|
|
31948
|
+
] })
|
|
31949
|
+
})] });
|
|
31950
|
+
}
|
|
31951
|
+
//#endregion
|
|
31952
|
+
//#region src/composites/copy-button.tsx
|
|
31953
|
+
/**
|
|
31954
|
+
* `CopyButton` — a compact button that copies a string `value` to the
|
|
31955
|
+
* clipboard via `navigator.clipboard.writeText`, showing a brief
|
|
31956
|
+
* "copied" check state. Addon-agnostic; used wherever a secret / URL
|
|
31957
|
+
* needs a one-click copy affordance (export setup panels, etc.).
|
|
31958
|
+
*/
|
|
31959
|
+
var COPIED_RESET_MS = 2e3;
|
|
31960
|
+
function CopyButton({ value, label, srLabel, className, disabled }) {
|
|
31961
|
+
const [copied, setCopied] = (0, react$1.useState)(false);
|
|
31962
|
+
const handleCopy = (0, react$1.useCallback)(() => {
|
|
31963
|
+
if (!value) return;
|
|
31964
|
+
navigator.clipboard.writeText(value).then(() => {
|
|
31965
|
+
setCopied(true);
|
|
31966
|
+
setTimeout(() => setCopied(false), COPIED_RESET_MS);
|
|
31967
|
+
});
|
|
31968
|
+
}, [value]);
|
|
31969
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
31970
|
+
size: "sm",
|
|
31971
|
+
variant: "ghost",
|
|
31972
|
+
type: "button",
|
|
31973
|
+
disabled: disabled || value.length === 0,
|
|
31974
|
+
onClick: handleCopy,
|
|
31975
|
+
className: cn(className),
|
|
31976
|
+
"aria-label": copied ? "Copied" : `Copy ${srLabel ?? label ?? "value"}`,
|
|
31977
|
+
children: [copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Check, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Copy, { className: "h-3.5 w-3.5" }), label ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31978
|
+
className: "ml-1",
|
|
31979
|
+
children: copied ? "Copied" : label
|
|
31980
|
+
}) : null]
|
|
31981
|
+
});
|
|
31982
|
+
}
|
|
31983
|
+
//#endregion
|
|
31984
|
+
//#region src/composites/data-table-layout.ts
|
|
31985
|
+
/**
|
|
31986
|
+
* At this many columns and above, `auto` switches a narrow viewport to cards.
|
|
31987
|
+
* Three columns still fit a phone at a readable size; four do not.
|
|
31988
|
+
*/
|
|
31989
|
+
var CARD_MODE_MIN_COLUMNS = 4;
|
|
31990
|
+
function resolveTableLayout({ mode, columnCount, isNarrow }) {
|
|
31991
|
+
if (!isNarrow) return "table";
|
|
31992
|
+
if (mode === "scroll") return "table";
|
|
31993
|
+
if (mode === "cards") return "cards";
|
|
31994
|
+
if (columnCount === 0) return "table";
|
|
31995
|
+
return columnCount >= 4 ? "cards" : "table";
|
|
31996
|
+
}
|
|
31997
|
+
//#endregion
|
|
31998
|
+
//#region src/lib/responsive.ts
|
|
31999
|
+
/**
|
|
32000
|
+
* Responsive design tokens — three-tier system.
|
|
32001
|
+
*
|
|
32002
|
+
* The admin-ui targets three width tiers; every component should pick
|
|
32003
|
+
* the right token from this file instead of hand-coding breakpoints.
|
|
32004
|
+
* Adopting the tokens uniformly is what makes the app feel consistent
|
|
32005
|
+
* across operator devices (mobile / tablet / desktop) and what unlocks
|
|
32006
|
+
* targeted tuning (e.g. shrink every label by 1px in one place).
|
|
32007
|
+
*
|
|
32008
|
+
* Tier definitions (Tailwind breakpoints):
|
|
32009
|
+
*
|
|
32010
|
+
* • **L1 — Compact** (`< 640px`, default = mobile portrait)
|
|
32011
|
+
* Single column, tight padding, smallest text, icon-only buttons
|
|
32012
|
+
* where labels can be inferred. The operator can still see every
|
|
32013
|
+
* stat / action — no horizontal scroll, no clipped text. The user
|
|
32014
|
+
* directive: "molta informazione in poco spazio quando la width è
|
|
32015
|
+
* bassa."
|
|
32016
|
+
*
|
|
32017
|
+
* • **L2 — Standard** (`sm` — `640–1023px`, tablet + small laptop)
|
|
32018
|
+
* 2-column grids, side panels become visible, button labels return.
|
|
32019
|
+
* Targets ~768px–1024px viewports — the most common operator sizes.
|
|
32020
|
+
*
|
|
32021
|
+
* • **L3 — Spacious** (`lg+` — `≥ 1024px`, desktop)
|
|
32022
|
+
* 4-column grids, generous padding, full-width header text,
|
|
32023
|
+
* side-by-side composite layouts. The operator has elbow room.
|
|
32024
|
+
*
|
|
32025
|
+
* Conventions:
|
|
32026
|
+
* - **Always start from L1** — the bare class is always the compact
|
|
32027
|
+
* variant, then `sm:` adds room at 640px, `lg:` at 1024px. No
|
|
32028
|
+
* desktop-first breakpoints; no max-width queries.
|
|
32029
|
+
* - **Token strings are static** — kept as `as const` so callers can
|
|
32030
|
+
* compose them via `cn()` / template literals without running a
|
|
32031
|
+
* class generator at runtime.
|
|
32032
|
+
* - **Pair the token with a comment** — the comment in the file
|
|
32033
|
+
* names every breakpoint it crosses, so a reader can predict the
|
|
32034
|
+
* layout without going to Tailwind docs.
|
|
32035
|
+
*/
|
|
32036
|
+
/** Container padding for a card-style section (header + body). */
|
|
32037
|
+
var SECTION_CARD = "rounded-lg border border-border bg-surface overflow-hidden";
|
|
32038
|
+
/** Header strip inside a card (between border-b lines). */
|
|
32039
|
+
var SECTION_HEADER = "border-b border-border px-3 sm:px-4 py-1.5 sm:py-2";
|
|
32040
|
+
/** Body padding for a card-style section. */
|
|
32041
|
+
var SECTION_BODY = "p-2 sm:p-3 lg:p-4";
|
|
32042
|
+
/** Gap between cards in a vertical stack. */
|
|
32043
|
+
var STACK_GAP = "space-y-3 sm:space-y-4";
|
|
32044
|
+
/** Gap between cards in a grid (4 cells / row at lg). */
|
|
32045
|
+
var GRID_GAP = "gap-2 sm:gap-3";
|
|
32046
|
+
/** Row padding inside a divided list (e.g. InfoRow). */
|
|
32047
|
+
var LIST_ROW = "px-3 sm:px-4 py-1.5 sm:py-2";
|
|
32048
|
+
/** 1-up at L1, 2-up at L2, 4-up at L3 — quick-stat row. */
|
|
32049
|
+
var GRID_QUICK_STATS = "grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-4";
|
|
32050
|
+
/** 1-up at L1, 2-up at L2+ — paired panels (motion + detection rules). */
|
|
32051
|
+
var GRID_PAIRED = "grid grid-cols-1 lg:grid-cols-2";
|
|
32052
|
+
/** Side-by-side layout that stacks below md (canvas + side panel). */
|
|
32053
|
+
var SPLIT_PANEL_OUTER = "flex flex-col-reverse md:flex-row";
|
|
32054
|
+
/** Sidebar narrow lane in a SPLIT_PANEL_OUTER layout. */
|
|
32055
|
+
var SPLIT_PANEL_SIDE = "w-full md:w-44 lg:w-52 md:flex-shrink-0 border-b md:border-b-0 md:border-l border-border";
|
|
32056
|
+
/**
|
|
32057
|
+
* The tier boundary at which a settings label/value row stops stacking.
|
|
32058
|
+
*
|
|
32059
|
+
* Below it (L1 — compact) the row is ONE column: label on its own line, value
|
|
32060
|
+
* on the next at full width. Above it the row is label-left / value-right as
|
|
32061
|
+
* before. The two-column form needs `SETTING_ROW_LABEL`'s lane plus a readable
|
|
32062
|
+
* value beside it; a phone has neither, and the failure mode is not "cramped"
|
|
32063
|
+
* but "the value is the part that gets cut" — the one thing the operator
|
|
32064
|
+
* opened the page to read.
|
|
32065
|
+
*/
|
|
32066
|
+
var SETTING_ROW_STACK_BREAKPOINT = "sm";
|
|
32067
|
+
/** Outer row: single column at L1, label/value columns from `sm` up. */
|
|
32068
|
+
var SETTING_ROW = "flex min-w-0 flex-col gap-0.5 py-1.5 sm:flex-row sm:items-center";
|
|
32069
|
+
/** Label lane. Full width (and free to wrap) at L1; a fixed lane from `sm` up. */
|
|
32070
|
+
var SETTING_ROW_LABEL = "min-w-0 text-[11px] leading-tight text-foreground-subtle sm:w-32 sm:shrink-0 sm:pr-2";
|
|
32071
|
+
/**
|
|
32072
|
+
* Value lane — the value text plus its trailing affordances (copy / reveal).
|
|
32073
|
+
* `w-full` at L1 so the value owns the whole row once the label is above it;
|
|
32074
|
+
* `sm:flex-1` makes it share the row again from `sm` up.
|
|
32075
|
+
*/
|
|
32076
|
+
var SETTING_ROW_VALUE = "flex w-full min-w-0 items-center gap-1 sm:flex-1";
|
|
32077
|
+
/**
|
|
32078
|
+
* The value text itself. `break-all` lets an opaque token (a key, a URL) wrap
|
|
32079
|
+
* at L1 instead of being cut mid-word; `sm:truncate` restores the single-line
|
|
32080
|
+
* desktop form, where the row is wide enough for truncation to be a choice
|
|
32081
|
+
* rather than data loss.
|
|
32082
|
+
*/
|
|
32083
|
+
var SETTING_ROW_VALUE_TEXT = "min-w-0 break-all text-xs text-foreground sm:truncate";
|
|
32084
|
+
/** Section header label (uppercase tracking-wider). */
|
|
32085
|
+
var TEXT_SECTION_LABEL = "text-[10px] sm:text-[11px] font-semibold text-foreground uppercase tracking-wider";
|
|
32086
|
+
/** Field label inside a row. */
|
|
32087
|
+
var TEXT_FIELD_LABEL = "text-[11px] sm:text-xs text-foreground";
|
|
32088
|
+
/** Sub-label / hint text. */
|
|
32089
|
+
var TEXT_HINT = "text-[10px] text-foreground-subtle";
|
|
32090
|
+
/** Primary value (a metric, badge, …). */
|
|
32091
|
+
var TEXT_VALUE = "text-[11px] sm:text-xs font-medium text-foreground";
|
|
32092
|
+
/** Big metric value (StatCard). */
|
|
32093
|
+
var TEXT_METRIC = "text-base sm:text-lg font-semibold leading-tight text-foreground";
|
|
32094
|
+
/** Default compact button. */
|
|
32095
|
+
var BTN_COMPACT = "inline-flex items-center justify-center gap-1 rounded border border-border bg-surface px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-foreground hover:bg-surface-hover transition-colors disabled:opacity-40";
|
|
32096
|
+
/** Primary compact button. */
|
|
32097
|
+
var BTN_COMPACT_PRIMARY = "inline-flex items-center justify-center gap-1 rounded border border-primary/40 bg-primary/15 px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-primary hover:bg-primary/25 transition-colors disabled:opacity-40";
|
|
32098
|
+
/** Danger compact button. */
|
|
32099
|
+
var BTN_COMPACT_DANGER = "inline-flex items-center justify-center gap-1 rounded border border-danger/40 bg-danger/10 px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-danger hover:bg-danger/20 transition-colors disabled:opacity-40";
|
|
32100
|
+
/** Warning compact button (e.g. cancel-drawing). */
|
|
32101
|
+
var BTN_COMPACT_WARNING = "inline-flex items-center justify-center gap-1 rounded border border-warning/40 bg-warning/10 px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-warning hover:bg-warning/20 transition-colors disabled:opacity-40";
|
|
32102
|
+
/** Compact text/select/number input. */
|
|
32103
|
+
var INPUT_COMPACT = "w-full rounded border border-border bg-background px-1.5 py-0.5 sm:px-2 sm:py-1 text-[11px] sm:text-xs text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none";
|
|
32104
|
+
/** Chip-style toggle (zone / class selectors in ZoneRulesEditor). */
|
|
32105
|
+
var CHIP_BASE = "inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-[10px] font-medium transition-colors";
|
|
32106
|
+
var CHIP_ACTIVE = "border-primary/50 bg-primary/15 text-primary";
|
|
32107
|
+
var CHIP_INACTIVE = "border-border bg-surface text-foreground-subtle hover:bg-surface-hover";
|
|
32108
|
+
//#endregion
|
|
32109
|
+
//#region src/composites/setting-row.tsx
|
|
32110
|
+
function SettingRow({ label, children, actions, elements = "plain", className, valueClassName }) {
|
|
32111
|
+
const LabelTag = elements === "description" ? "dt" : "span";
|
|
32112
|
+
const ValueTag = elements === "description" ? "dd" : "div";
|
|
32113
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
32114
|
+
"data-setting-row": "",
|
|
32115
|
+
className: cn(SETTING_ROW, className),
|
|
32116
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LabelTag, {
|
|
32117
|
+
"data-setting-row-label": "",
|
|
32118
|
+
className: SETTING_ROW_LABEL,
|
|
32119
|
+
children: label
|
|
32120
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ValueTag, {
|
|
32121
|
+
"data-setting-row-value": "",
|
|
32122
|
+
className: SETTING_ROW_VALUE,
|
|
32123
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
32124
|
+
"data-setting-row-value-text": "",
|
|
32125
|
+
className: cn(SETTING_ROW_VALUE_TEXT, valueClassName),
|
|
32126
|
+
children
|
|
32127
|
+
}), actions !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
32128
|
+
className: "flex shrink-0 items-center gap-0.5",
|
|
32129
|
+
children: actions
|
|
32130
|
+
})]
|
|
32131
|
+
})]
|
|
32132
|
+
});
|
|
32133
|
+
}
|
|
32134
|
+
//#endregion
|
|
32135
|
+
//#region src/composites/sort-header-button.tsx
|
|
32136
|
+
function SortHeaderButton({ label, active, dir, onSort }) {
|
|
32137
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
32138
|
+
type: "button",
|
|
32139
|
+
onClick: onSort,
|
|
32140
|
+
className: "group/sort inline-flex items-center gap-0.5 hover:text-foreground transition-colors",
|
|
32141
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), active ? dir === "asc" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronUp, { className: "h-3 w-3 flex-shrink-0" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3 w-3 flex-shrink-0" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronUp, { className: cn("h-3 w-3 flex-shrink-0 opacity-0 transition-opacity", "group-hover/sort:opacity-30") })]
|
|
32142
|
+
});
|
|
32143
|
+
}
|
|
32144
|
+
//#endregion
|
|
32145
|
+
//#region src/composites/table-sort.ts
|
|
32146
|
+
/**
|
|
32147
|
+
* The toggle rule, and the only copy of it.
|
|
32148
|
+
*
|
|
32149
|
+
* none → asc → desc → none, and a click on a DIFFERENT column starts that
|
|
32150
|
+
* column fresh at `asc`. `sameColumn` says whether the clicked column is the
|
|
32151
|
+
* one currently sorted; `currentDir` is its direction (`null` when nothing is
|
|
32152
|
+
* sorted). Returns the next direction, or `null` for "unsorted".
|
|
32153
|
+
*
|
|
32154
|
+
* Split out from {@link nextTableSort} so the device list — whose sort state
|
|
32155
|
+
* keys columns by `columnId`, not `columnKey` — can reuse the rule without a
|
|
32156
|
+
* cast and without a second copy of it.
|
|
32157
|
+
*/
|
|
32158
|
+
function nextSortDirection(sameColumn, currentDir) {
|
|
32159
|
+
if (!sameColumn || currentDir === null) return "asc";
|
|
32160
|
+
return currentDir === "asc" ? "desc" : null;
|
|
32161
|
+
}
|
|
32162
|
+
/**
|
|
32163
|
+
* Toggle a column's sort on header click: none → asc → desc → none.
|
|
32164
|
+
* Clicking a DIFFERENT column starts it fresh at `asc`.
|
|
32165
|
+
*/
|
|
32166
|
+
function nextTableSort(current, columnKey) {
|
|
32167
|
+
const dir = nextSortDirection(current !== null && current.columnKey === columnKey, current === null ? null : current.dir);
|
|
32168
|
+
return dir === null ? null : {
|
|
32169
|
+
columnKey,
|
|
32170
|
+
dir
|
|
32171
|
+
};
|
|
32172
|
+
}
|
|
32173
|
+
/**
|
|
32174
|
+
* Ascending comparison of two cell values.
|
|
32175
|
+
*
|
|
32176
|
+
* Numbers compare numerically and strings compare with `localeCompare` — a
|
|
32177
|
+
* numeric column must never be ordered as text ("100" before "9"), and a text
|
|
32178
|
+
* column must respect the locale's collation. A mixed column (one row a
|
|
32179
|
+
* number, another a string) falls back to string comparison of both, which is
|
|
32180
|
+
* the only total order available; it is not a shape any caller should build on
|
|
32181
|
+
* purpose.
|
|
32182
|
+
*
|
|
32183
|
+
* `null` is NOT handled here — {@link sortRowsByColumn} pulls it out first,
|
|
32184
|
+
* because its placement is direction-independent and a comparator that is
|
|
32185
|
+
* multiplied by the direction factor cannot express that.
|
|
32186
|
+
*/
|
|
32187
|
+
function compareValues(a, b) {
|
|
32188
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
32189
|
+
return String(a).localeCompare(String(b));
|
|
32190
|
+
}
|
|
32191
|
+
/**
|
|
32192
|
+
* Return a NEW array of `rows` ordered by `sort`.
|
|
32193
|
+
*
|
|
32194
|
+
* `valueOf` is the column's `sortValue` — it is called once per row per
|
|
32195
|
+
* comparison, so it must be cheap (read a field, do not format a string).
|
|
32196
|
+
* `sort === null` ⇒ a fresh shallow copy in the incoming order.
|
|
32197
|
+
*/
|
|
32198
|
+
function sortRowsByColumn(rows, sort, valueOf) {
|
|
32199
|
+
if (sort === null) return [...rows];
|
|
32200
|
+
const factor = sort.dir === "asc" ? 1 : -1;
|
|
32201
|
+
return rows.toSorted((a, b) => {
|
|
32202
|
+
const av = valueOf(a);
|
|
32203
|
+
const bv = valueOf(b);
|
|
32204
|
+
if (av === null || bv === null) {
|
|
32205
|
+
if (av === bv) return 0;
|
|
32206
|
+
return av === null ? 1 : -1;
|
|
32207
|
+
}
|
|
32208
|
+
return factor * compareValues(av, bv);
|
|
32209
|
+
});
|
|
32210
|
+
}
|
|
32211
|
+
/** Derive the `<th>` `aria-sort` for `columnKey` from the table's sort state. */
|
|
32212
|
+
function ariaSortForColumn(sort, columnKey) {
|
|
32213
|
+
if (sort === null || sort.columnKey !== columnKey) return "none";
|
|
32214
|
+
return sort.dir === "asc" ? "ascending" : "descending";
|
|
32215
|
+
}
|
|
32216
|
+
//#endregion
|
|
32217
|
+
//#region src/composites/data-table.tsx
|
|
32218
|
+
/**
|
|
32219
|
+
* DataTable — shared responsive table for admin pages.
|
|
32220
|
+
*
|
|
32221
|
+
* Recipes baked in:
|
|
32222
|
+
* - `overflow-x-auto` wrapper so narrow viewports don't clip the
|
|
32223
|
+
* right edge (the rounded border survives the scroll).
|
|
32224
|
+
* - `min-w-[Npx]` on the inner table so columns don't squash below
|
|
32225
|
+
* readable width before the scroll kicks in.
|
|
32226
|
+
* - `whitespace-nowrap` on headers — column titles never wrap to
|
|
32227
|
+
* two lines (`AUTO-KILL` was the regression that triggered this).
|
|
32228
|
+
* - Themed `bg-surface` + `border-border` so contrast works in both
|
|
32229
|
+
* light and dark themes.
|
|
32230
|
+
*
|
|
32231
|
+
* Column model: caller supplies `columns: DataColumn<T>[]` and
|
|
32232
|
+
* `rows: T[]`. Each column declares `header`, optional `align`,
|
|
32233
|
+
* optional `nowrap`, and a `render(row) => ReactNode` that produces
|
|
32234
|
+
* the cell content.
|
|
32235
|
+
*
|
|
32236
|
+
* <DataTable
|
|
32237
|
+
* columns={[
|
|
32238
|
+
* { key: 'username', header: 'Username', render: (u) => u.username },
|
|
32239
|
+
* { key: 'role', header: 'Role', render: (u) => <RoleBadge .../> },
|
|
32240
|
+
* { key: 'actions', header: '', align: 'right', render: (u) => <Actions .../> },
|
|
32241
|
+
* ]}
|
|
32242
|
+
* rows={users}
|
|
32243
|
+
* minWidthPx={560}
|
|
32244
|
+
* />
|
|
32245
|
+
*
|
|
32246
|
+
* If finer header markup is needed, pass `headerRender` instead of
|
|
32247
|
+
* `header` (e.g. for a sortable click affordance).
|
|
32248
|
+
*
|
|
32249
|
+
* ── Narrow viewports ──────────────────────────────────────────────────────
|
|
32250
|
+
* The table stays a table. The wrapper is `overflow-x-auto`, so a phone
|
|
32251
|
+
* scrolls the columns sideways INSIDE the table — the page body never moves
|
|
32252
|
+
* sideways, and the cells stay one line (`whitespace-nowrap`). That is the
|
|
32253
|
+
* default (`mobileMode="scroll"`). Cards are opt-in via `mobileMode="cards"`;
|
|
32254
|
+
* `auto` still applies the old column-count rule for a surface that wants it.
|
|
32255
|
+
* See `resolveTableLayout`.
|
|
32256
|
+
*
|
|
32257
|
+
* ── Scrolling the BODY, not the page ──────────────────────────────────────
|
|
32258
|
+
* `maxBodyHeight` caps the table and scrolls its rows INSIDE the container
|
|
32259
|
+
* with the column header pinned. It is ONE prop, not two, because "this list
|
|
32260
|
+
* scrolls internally" and "its header stays visible" are the same decision: a
|
|
32261
|
+
* capped table whose header scrolls away is a list of unlabelled cells.
|
|
32262
|
+
*
|
|
32263
|
+
* Use it wherever the row count is open-ended (rules, targets, delivery
|
|
32264
|
+
* history) so the page keeps its own header, filters and actions in view while
|
|
32265
|
+
* the operator moves through the rows.
|
|
32266
|
+
*/
|
|
32267
|
+
/**
|
|
32268
|
+
* The default cap for an internally-scrolling table.
|
|
32269
|
+
*
|
|
32270
|
+
* `60vh` leaves the page header, its filters and its actions on screen at
|
|
32271
|
+
* every viewport while still showing ~10 rows — and it is a fraction, so it
|
|
32272
|
+
* does not need a breakpoint of its own.
|
|
32273
|
+
*/
|
|
32274
|
+
var TABLE_SCROLL_MAX_H = "max-h-[60vh]";
|
|
32275
|
+
var ALIGN_CLASS = {
|
|
32276
|
+
left: "text-left",
|
|
32277
|
+
right: "text-right",
|
|
32278
|
+
center: "text-center"
|
|
32279
|
+
};
|
|
32280
|
+
function DataTable({ columns, rows, rowKey, onRowClick, minWidthPx = 480, emptyMessage, className, bordered = true, rowClassName, mobileMode = "scroll", maxBodyHeight, defaultSort }) {
|
|
32281
|
+
const isNarrow = useIsMobile();
|
|
32282
|
+
const [sort, setSort] = (0, react$1.useState)(defaultSort ?? null);
|
|
32283
|
+
const layout = resolveTableLayout({
|
|
32284
|
+
mode: mobileMode,
|
|
32285
|
+
columnCount: columns.length,
|
|
32286
|
+
isNarrow
|
|
32287
|
+
});
|
|
32288
|
+
const scrolls = maxBodyHeight !== void 0;
|
|
32289
|
+
const sortedColumn = (0, react$1.useMemo)(() => sort === null ? void 0 : columns.find((col) => col.key === sort.columnKey && col.sortValue !== void 0), [columns, sort]);
|
|
32290
|
+
const sortValue = sortedColumn?.sortValue;
|
|
32291
|
+
const displayRows = (0, react$1.useMemo)(() => sortValue === void 0 ? rows : sortRowsByColumn(rows, sort, sortValue), [
|
|
32292
|
+
rows,
|
|
32293
|
+
sort,
|
|
32294
|
+
sortValue
|
|
32295
|
+
]);
|
|
32296
|
+
const activeSort = sortedColumn === void 0 ? null : sort;
|
|
32297
|
+
if (rows.length === 0 && emptyMessage) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
32298
|
+
className: `rounded-lg ${bordered ? "border border-border" : ""} bg-surface px-3 py-4 text-xs text-foreground-subtle text-center ${className ?? ""}`,
|
|
32299
|
+
children: emptyMessage
|
|
32300
|
+
});
|
|
32301
|
+
if (rows.length === 0) return null;
|
|
32302
|
+
if (layout === "cards") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
32303
|
+
className: `space-y-2 ${scrolls ? `${maxBodyHeight} overflow-y-auto` : ""} ${className ?? ""}`,
|
|
32304
|
+
children: displayRows.map((row, rowIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableCard, {
|
|
32305
|
+
row,
|
|
32306
|
+
rowIndex,
|
|
32307
|
+
columns,
|
|
32308
|
+
bordered,
|
|
32309
|
+
onRowClick,
|
|
32310
|
+
rowClassName
|
|
32311
|
+
}, rowKey ? rowKey(row, rowIndex) : rowIndex))
|
|
32312
|
+
});
|
|
32313
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
32314
|
+
className: [
|
|
32315
|
+
"rounded-lg",
|
|
32316
|
+
bordered ? "border border-border" : "",
|
|
32317
|
+
"bg-surface overflow-x-auto",
|
|
32318
|
+
scrolls ? `${maxBodyHeight} overflow-y-auto` : "",
|
|
32319
|
+
className ?? ""
|
|
32320
|
+
].filter(Boolean).join(" "),
|
|
32321
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
32322
|
+
className: `w-full text-xs ${scrolls ? "border-separate border-spacing-0" : ""}`,
|
|
32323
|
+
style: { minWidth: `${minWidthPx}px` },
|
|
32324
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", {
|
|
32325
|
+
className: "bg-surface",
|
|
32326
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: columns.map((col) => {
|
|
32327
|
+
const align = col.align ?? "left";
|
|
32328
|
+
const nowrap = col.nowrap ?? true;
|
|
32329
|
+
const sortable = col.sortValue !== void 0;
|
|
32330
|
+
const isSorted = activeSort !== null && activeSort.columnKey === col.key;
|
|
32331
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
32332
|
+
"aria-sort": sortable ? ariaSortForColumn(activeSort, col.key) : void 0,
|
|
32333
|
+
title: sortable ? void 0 : col.notSortableReason,
|
|
32334
|
+
className: [
|
|
32335
|
+
"px-3 py-2 text-foreground-subtle font-medium bg-surface border-b border-border",
|
|
32336
|
+
scrolls ? "sticky top-0 z-10" : "",
|
|
32337
|
+
ALIGN_CLASS[align],
|
|
32338
|
+
nowrap ? "whitespace-nowrap" : "",
|
|
32339
|
+
col.width ?? ""
|
|
32340
|
+
].filter(Boolean).join(" "),
|
|
32341
|
+
children: sortable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SortHeaderButton, {
|
|
32342
|
+
label: col.headerRender ? col.headerRender() : col.header,
|
|
32343
|
+
active: isSorted,
|
|
32344
|
+
dir: isSorted && activeSort !== null ? activeSort.dir : "asc",
|
|
32345
|
+
onSort: () => setSort((prev) => nextTableSort(prev, col.key))
|
|
32346
|
+
}) : col.headerRender ? col.headerRender() : col.header
|
|
32347
|
+
}, col.key);
|
|
32348
|
+
}) })
|
|
32349
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: displayRows.map((row, rowIndex) => {
|
|
32350
|
+
const key = rowKey ? rowKey(row, rowIndex) : rowIndex;
|
|
32351
|
+
const extra = rowClassName?.(row, rowIndex) ?? "";
|
|
32352
|
+
const interactive = onRowClick !== void 0;
|
|
32353
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
|
|
32354
|
+
onClick: interactive ? () => onRowClick(row, rowIndex) : void 0,
|
|
32355
|
+
className: [interactive ? "cursor-pointer hover:bg-primary/5" : "hover:bg-primary/5", extra].filter(Boolean).join(" "),
|
|
32356
|
+
children: columns.map((col) => {
|
|
32357
|
+
const align = col.align ?? "left";
|
|
32358
|
+
const nowrap = col.nowrap ?? true;
|
|
32359
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
32360
|
+
className: [
|
|
32361
|
+
"px-3 py-2 text-foreground border-b border-border",
|
|
32362
|
+
ALIGN_CLASS[align],
|
|
32363
|
+
nowrap ? "whitespace-nowrap" : ""
|
|
32364
|
+
].filter(Boolean).join(" "),
|
|
32365
|
+
children: col.render(row, rowIndex)
|
|
32366
|
+
}, col.key);
|
|
32367
|
+
})
|
|
32368
|
+
}, key);
|
|
32369
|
+
}) })]
|
|
32370
|
+
})
|
|
32371
|
+
});
|
|
32372
|
+
}
|
|
32373
|
+
/**
|
|
32374
|
+
* One row as a card. Columns that carry a plain-text `header` become labelled
|
|
32375
|
+
* `SettingRow`s — the label is the column title and the value gets the full
|
|
32376
|
+
* card width. Columns with no text header (an actions column, a
|
|
32377
|
+
* `headerRender`-only column) have no label to show, so they render as a
|
|
32378
|
+
* full-width strip at the foot of the card.
|
|
32379
|
+
*/
|
|
32380
|
+
function DataTableCard({ row, rowIndex, columns, bordered, onRowClick, rowClassName }) {
|
|
32381
|
+
const labelled = columns.filter((col) => col.header !== void 0 && col.header !== "");
|
|
32382
|
+
const unlabelled = columns.filter((col) => col.header === void 0 || col.header === "");
|
|
32383
|
+
const interactive = onRowClick !== void 0;
|
|
32384
|
+
const extra = rowClassName?.(row, rowIndex) ?? "";
|
|
32385
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
32386
|
+
"data-data-table-card": "",
|
|
32387
|
+
onClick: interactive ? () => onRowClick(row, rowIndex) : void 0,
|
|
32388
|
+
className: [
|
|
32389
|
+
"rounded-lg bg-surface px-3 py-1.5 divide-y divide-border-subtle",
|
|
32390
|
+
bordered ? "border border-border" : "",
|
|
32391
|
+
interactive ? "cursor-pointer hover:bg-primary/5" : "",
|
|
32392
|
+
extra
|
|
32393
|
+
].filter(Boolean).join(" "),
|
|
32394
|
+
children: [labelled.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
|
|
32395
|
+
label: col.header,
|
|
32396
|
+
children: col.render(row, rowIndex)
|
|
32397
|
+
}, col.key)), unlabelled.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
32398
|
+
className: "flex flex-wrap items-center justify-end gap-2 py-1.5",
|
|
32399
|
+
children: unlabelled.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: col.render(row, rowIndex) }, col.key))
|
|
32400
|
+
})]
|
|
30855
32401
|
});
|
|
30856
32402
|
}
|
|
30857
32403
|
//#endregion
|
|
30858
|
-
//#region src/composites/
|
|
30859
|
-
/** The lighting variants an operator is OFFERED. The resolver can also report
|
|
30860
|
-
* `night`, `dawn` and `dusk`; those are shown when present but never demanded,
|
|
30861
|
-
* because most installs only ever see day and IR. */
|
|
30862
|
-
var SCENE_CAPTURE_VARIANTS = ["day", "ir"];
|
|
30863
|
-
var VARIANT_LABEL = {
|
|
30864
|
-
day: "Daylight",
|
|
30865
|
-
ir: "Night (IR)",
|
|
30866
|
-
night: "Night",
|
|
30867
|
-
dawn: "Dawn",
|
|
30868
|
-
dusk: "Dusk"
|
|
30869
|
-
};
|
|
30870
|
-
function variantLabel(condition) {
|
|
30871
|
-
return VARIANT_LABEL[condition] ?? condition;
|
|
30872
|
-
}
|
|
32404
|
+
//#region src/composites/device-list/fuzzy-match.ts
|
|
30873
32405
|
/**
|
|
30874
|
-
*
|
|
32406
|
+
* fuzzy-match — case and separator-insensitive text search helper.
|
|
30875
32407
|
*
|
|
30876
|
-
*
|
|
30877
|
-
*
|
|
30878
|
-
*
|
|
32408
|
+
* Normalization rule: lowercase + strip all runs of hyphens, underscores and
|
|
32409
|
+
* whitespace entirely. This makes `Tapparella cucina` and `tapparella-cucina`
|
|
32410
|
+
* both normalize to `tapparellacucina`, so a substring match succeeds in
|
|
32411
|
+
* either direction.
|
|
32412
|
+
*/
|
|
32413
|
+
/**
|
|
32414
|
+
* Strips hyphens, underscores and whitespace runs from `s` and lowercases it.
|
|
30879
32415
|
*
|
|
30880
|
-
*
|
|
30881
|
-
*
|
|
30882
|
-
*
|
|
30883
|
-
*
|
|
30884
|
-
* happens instead, which differs by the scene's own policy.
|
|
32416
|
+
* @example
|
|
32417
|
+
* normalizeForSearch('Tapparella cucina') // 'tapparellacucina'
|
|
32418
|
+
* normalizeForSearch('tapparella-cucina') // 'tapparellacucina'
|
|
32419
|
+
* normalizeForSearch('tapparella_cucina') // 'tapparellacucina'
|
|
30885
32420
|
*/
|
|
30886
|
-
function
|
|
30887
|
-
|
|
30888
|
-
const parts = SCENE_CAPTURE_VARIANTS.map((v) => `${variantLabel(v)} ${covered.has(v) ? "✓" : "✗"}`);
|
|
30889
|
-
const extra = [...covered].filter((c) => !SCENE_CAPTURE_VARIANTS.some((v) => v === c));
|
|
30890
|
-
for (const c of extra) parts.push(`${variantLabel(c)} ✓`);
|
|
30891
|
-
const missing = SCENE_CAPTURE_VARIANTS.filter((v) => !covered.has(v));
|
|
30892
|
-
if (missing.length === 0) return parts.join(" · ");
|
|
30893
|
-
if (covered.size === 0) return `${parts.join(" · ")} — nothing captured yet, so this scene is not watching.`;
|
|
30894
|
-
const names = missing.map((v) => variantLabel(v).toLowerCase()).join(" and ");
|
|
30895
|
-
return monitor.onUncoveredCondition === "judge-anyway" ? `${parts.join(" · ")} — ${names} judged against the references you do have.` : `${parts.join(" · ")} — optional; checks in ${names} are skipped.`;
|
|
32421
|
+
function normalizeForSearch(s) {
|
|
32422
|
+
return s.toLowerCase().replace(/[-_\s]+/g, "");
|
|
30896
32423
|
}
|
|
30897
32424
|
/**
|
|
30898
|
-
*
|
|
30899
|
-
*
|
|
32425
|
+
* Returns `true` when the normalized form of `target` contains the normalized
|
|
32426
|
+
* form of `query`. An empty query always matches.
|
|
30900
32427
|
*
|
|
30901
|
-
*
|
|
30902
|
-
*
|
|
30903
|
-
*
|
|
30904
|
-
*
|
|
32428
|
+
* @example
|
|
32429
|
+
* fuzzyMatch('Tapparella cucina', 'tapparella-cucina') // true
|
|
32430
|
+
* fuzzyMatch('tapparella cucina', 'Tapparella-Cucina') // true
|
|
32431
|
+
* fuzzyMatch('xyz', 'tapparella-cucina') // false
|
|
30905
32432
|
*/
|
|
30906
|
-
function
|
|
30907
|
-
|
|
30908
|
-
|
|
30909
|
-
return
|
|
32433
|
+
function fuzzyMatch(query, target) {
|
|
32434
|
+
const nq = normalizeForSearch(query);
|
|
32435
|
+
if (nq === "") return true;
|
|
32436
|
+
return normalizeForSearch(target).includes(nq);
|
|
30910
32437
|
}
|
|
30911
|
-
|
|
30912
|
-
|
|
30913
|
-
|
|
30914
|
-
|
|
30915
|
-
|
|
30916
|
-
|
|
32438
|
+
//#endregion
|
|
32439
|
+
//#region src/composites/data-table-filter.ts
|
|
32440
|
+
/**
|
|
32441
|
+
* Per-instance filtering for a `DataTable` (or any row list): a search box
|
|
32442
|
+
* and a set of facet chips, both DERIVED from the rows the instance renders.
|
|
32443
|
+
*
|
|
32444
|
+
* Pure — no React. The state lives with the caller (`useDataTableFilter`), so
|
|
32445
|
+
* two tables on one page each carry their own filter, and the caller keeps the
|
|
32446
|
+
* FILTERED set it renders: a table whose selection acts on "what is on
|
|
32447
|
+
* screen" has to know what that is, which a filter hidden inside the table
|
|
32448
|
+
* could not tell it.
|
|
32449
|
+
*
|
|
32450
|
+
* Two rules, both load-bearing:
|
|
32451
|
+
* - **Facets are derived from the rows present**, most common first. A
|
|
32452
|
+
* hand-listed set goes stale the day a provider adds a type and offers
|
|
32453
|
+
* chips that match nothing.
|
|
32454
|
+
* - **A selected facet no row carries is ignored**, not applied. The chip is
|
|
32455
|
+
* not drawn (there is no row to derive it from), and an invisible filter
|
|
32456
|
+
* that empties the table is a table that looks broken.
|
|
32457
|
+
*/
|
|
32458
|
+
var EMPTY_DATA_TABLE_FILTER = {
|
|
32459
|
+
search: "",
|
|
32460
|
+
facets: []
|
|
30917
32461
|
};
|
|
30918
32462
|
/**
|
|
30919
|
-
* The
|
|
30920
|
-
*
|
|
32463
|
+
* The facet chips for these rows: one per distinct value, most common first,
|
|
32464
|
+
* ties by value.
|
|
30921
32465
|
*/
|
|
30922
|
-
function
|
|
30923
|
-
if (
|
|
30924
|
-
|
|
30925
|
-
const
|
|
30926
|
-
|
|
30927
|
-
|
|
30928
|
-
|
|
30929
|
-
|
|
30930
|
-
|
|
30931
|
-
|
|
30932
|
-
|
|
30933
|
-
* thing the operator asked to be told and it outlives the live state.
|
|
30934
|
-
*
|
|
30935
|
-
* A scene sitting out an uncovered light reads `Paused`, in the NEUTRAL tone —
|
|
30936
|
-
* never `alarm`, and never the `danger` variant the page maps that to. A
|
|
30937
|
-
* missing IR reference must not be able to paint a red badge on a scene that
|
|
30938
|
-
* is doing exactly what it was configured to do. */
|
|
30939
|
-
function badgeFor(monitor) {
|
|
30940
|
-
if (monitor.states.length === 0) return {
|
|
30941
|
-
text: "Not armed",
|
|
30942
|
-
tone: "unknown"
|
|
30943
|
-
};
|
|
30944
|
-
if (monitor.latched) return {
|
|
30945
|
-
text: "Changed",
|
|
30946
|
-
tone: "alarm"
|
|
30947
|
-
};
|
|
30948
|
-
if (monitor.suspendedCondition !== null) return {
|
|
30949
|
-
text: "Paused",
|
|
30950
|
-
tone: "unknown"
|
|
30951
|
-
};
|
|
30952
|
-
if (monitor.verdict === "unknown") return {
|
|
30953
|
-
text: "Can’t tell",
|
|
30954
|
-
tone: "unknown"
|
|
30955
|
-
};
|
|
30956
|
-
return monitor.verdict === "diverged" ? {
|
|
30957
|
-
text: "Changed",
|
|
30958
|
-
tone: "alarm"
|
|
30959
|
-
} : {
|
|
30960
|
-
text: "Normal",
|
|
30961
|
-
tone: "ok"
|
|
30962
|
-
};
|
|
32466
|
+
function dataTableFacets(rows, facetValue) {
|
|
32467
|
+
if (facetValue === void 0) return [];
|
|
32468
|
+
const counts = /* @__PURE__ */ new Map();
|
|
32469
|
+
for (const row of rows) {
|
|
32470
|
+
const value = facetValue(row);
|
|
32471
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
32472
|
+
}
|
|
32473
|
+
return [...counts.entries()].map(([value, count]) => ({
|
|
32474
|
+
value,
|
|
32475
|
+
count
|
|
32476
|
+
})).toSorted((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
30963
32477
|
}
|
|
30964
|
-
|
|
30965
|
-
|
|
30966
|
-
* failure is silent INSENSITIVITY, which is the worst kind — hence a warning
|
|
30967
|
-
* rather than a refusal. */
|
|
30968
|
-
var ROI_AREA_WARN_FRACTION = .4;
|
|
30969
|
-
function roiTooLarge(roi) {
|
|
30970
|
-
return roi.width * roi.height > ROI_AREA_WARN_FRACTION;
|
|
32478
|
+
function isDataTableFilterActive(state) {
|
|
32479
|
+
return state.search.trim() !== "" || state.facets.length > 0;
|
|
30971
32480
|
}
|
|
30972
|
-
|
|
30973
|
-
|
|
30974
|
-
|
|
30975
|
-
|
|
30976
|
-
|
|
30977
|
-
|
|
30978
|
-
if (h < 48) return `${h}h`;
|
|
30979
|
-
return `${Math.floor(h / 24)}d`;
|
|
32481
|
+
/** A new state with `value` added to, or removed from, the selected facets. */
|
|
32482
|
+
function toggleDataTableFacet(state, value) {
|
|
32483
|
+
return {
|
|
32484
|
+
...state,
|
|
32485
|
+
facets: state.facets.includes(value) ? state.facets.filter((v) => v !== value) : [...state.facets, value]
|
|
32486
|
+
};
|
|
30980
32487
|
}
|
|
30981
|
-
|
|
30982
|
-
|
|
30983
|
-
|
|
30984
|
-
|
|
30985
|
-
|
|
30986
|
-
|
|
30987
|
-
|
|
30988
|
-
|
|
30989
|
-
|
|
30990
|
-
|
|
30991
|
-
|
|
30992
|
-
|
|
30993
|
-
|
|
30994
|
-
|
|
30995
|
-
|
|
30996
|
-
className: "font-mono text-xs bg-surface p-3 rounded-md border border-border-subtle",
|
|
30997
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children })
|
|
30998
|
-
})
|
|
30999
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
31000
|
-
className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity",
|
|
31001
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
|
|
31002
|
-
icon: copied ? Check : Copy,
|
|
31003
|
-
"aria-label": "Copy code",
|
|
31004
|
-
variant: "ghost",
|
|
31005
|
-
size: "sm",
|
|
31006
|
-
onClick: handleCopy
|
|
31007
|
-
})
|
|
31008
|
-
})]
|
|
32488
|
+
/**
|
|
32489
|
+
* The rows that survive the filter, in their incoming order.
|
|
32490
|
+
*
|
|
32491
|
+
* A selected facet that none of `rows` carries is dropped from the effective
|
|
32492
|
+
* selection (see the header), so a chip that is not on screen never filters.
|
|
32493
|
+
*/
|
|
32494
|
+
function filterDataTableRows(rows, state, config) {
|
|
32495
|
+
const needle = state.search.trim();
|
|
32496
|
+
const facetValue = config.facetValue;
|
|
32497
|
+
const present = new Set(dataTableFacets(rows, facetValue).map((f) => f.value));
|
|
32498
|
+
const selected = new Set(state.facets.filter((v) => present.has(v)));
|
|
32499
|
+
return rows.filter((row) => {
|
|
32500
|
+
if (selected.size > 0 && facetValue !== void 0 && !selected.has(facetValue(row))) return false;
|
|
32501
|
+
if (needle !== "" && !config.searchText(row).some((text) => fuzzyMatch(needle, text))) return false;
|
|
32502
|
+
return true;
|
|
31009
32503
|
});
|
|
31010
32504
|
}
|
|
31011
32505
|
//#endregion
|
|
31012
|
-
//#region src/composites/
|
|
32506
|
+
//#region src/composites/data-table-filter-bar.tsx
|
|
31013
32507
|
/**
|
|
31014
|
-
*
|
|
32508
|
+
* The controls of one table's filter: a search box and the facet chips.
|
|
31015
32509
|
*
|
|
31016
|
-
*
|
|
31017
|
-
*
|
|
31018
|
-
*
|
|
31019
|
-
* the trigger spinner-disables itself for the duration, and the dialog
|
|
31020
|
-
* closes on success. Errors surface inline at the dialog's bottom.
|
|
32510
|
+
* Controlled — the state comes from `useDataTableFilter` and goes back through
|
|
32511
|
+
* the two callbacks; this component owns nothing, so it can sit above a
|
|
32512
|
+
* `DataTable`, a card's list, or anything else that renders the filtered rows.
|
|
31021
32513
|
*
|
|
31022
|
-
*
|
|
31023
|
-
*
|
|
31024
|
-
* for reboot, RefreshCw for restart).
|
|
32514
|
+
* Every visible string is a prop: this library carries no translations, and
|
|
32515
|
+
* the caller's i18n does.
|
|
31025
32516
|
*/
|
|
31026
|
-
|
|
31027
|
-
|
|
31028
|
-
|
|
31029
|
-
const
|
|
31030
|
-
const
|
|
31031
|
-
|
|
31032
|
-
|
|
31033
|
-
|
|
31034
|
-
|
|
31035
|
-
|
|
31036
|
-
|
|
31037
|
-
|
|
31038
|
-
|
|
31039
|
-
|
|
31040
|
-
|
|
31041
|
-
|
|
31042
|
-
|
|
31043
|
-
|
|
31044
|
-
type: "button",
|
|
31045
|
-
variant: triggerVariant,
|
|
31046
|
-
size,
|
|
31047
|
-
disabled,
|
|
31048
|
-
onClick: () => {
|
|
31049
|
-
setError(null);
|
|
31050
|
-
setOpen(true);
|
|
31051
|
-
},
|
|
31052
|
-
className,
|
|
31053
|
-
children: [Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "h-3.5 w-3.5" }), label]
|
|
31054
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
|
|
31055
|
-
open,
|
|
31056
|
-
onOpenChange: (next) => !running && setOpen(next),
|
|
31057
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogContent, { children: [
|
|
31058
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogTitle, {
|
|
31059
|
-
className: "flex items-center gap-2",
|
|
31060
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TriangleAlert, { className: "h-4 w-4 text-amber-400" }), title]
|
|
31061
|
-
}), typeof description === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogDescription, { children: description }) : description] }),
|
|
31062
|
-
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
31063
|
-
className: cn("mt-2 px-3 py-2 rounded border border-danger/40 bg-danger/10", "text-xs text-danger"),
|
|
31064
|
-
children: error
|
|
32517
|
+
var CHIP_ON = "rounded-full border border-primary bg-primary/10 px-2 py-0.5 text-[10px] text-foreground";
|
|
32518
|
+
var CHIP_OFF = "rounded-full border border-border px-2 py-0.5 text-[10px] text-foreground-muted hover:bg-surface-hover";
|
|
32519
|
+
function DataTableFilterBar({ state, facets, labels, onSearch, onToggleFacet, onClear, facetLabel }) {
|
|
32520
|
+
const active = state.search.trim() !== "" || state.facets.length > 0;
|
|
32521
|
+
const showFacets = facets.length > 1;
|
|
32522
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
32523
|
+
className: "flex flex-wrap items-center gap-2",
|
|
32524
|
+
children: [
|
|
32525
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
32526
|
+
className: "relative min-w-[12rem] flex-1",
|
|
32527
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Search, { className: "pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-foreground-subtle" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
32528
|
+
type: "search",
|
|
32529
|
+
value: state.search,
|
|
32530
|
+
onChange: (e) => onSearch(e.target.value),
|
|
32531
|
+
placeholder: labels.searchPlaceholder,
|
|
32532
|
+
"aria-label": labels.searchLabel,
|
|
32533
|
+
className: "w-full rounded border border-border bg-background py-1.5 pl-7 pr-2 text-xs text-foreground"
|
|
32534
|
+
})]
|
|
31065
32535
|
}),
|
|
31066
|
-
|
|
31067
|
-
|
|
31068
|
-
|
|
31069
|
-
|
|
31070
|
-
|
|
31071
|
-
|
|
31072
|
-
|
|
31073
|
-
|
|
32536
|
+
showFacets && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
32537
|
+
role: "group",
|
|
32538
|
+
"aria-label": labels.facetsLabel,
|
|
32539
|
+
className: "flex flex-wrap gap-1",
|
|
32540
|
+
children: facets.map((facet) => {
|
|
32541
|
+
const pressed = state.facets.includes(facet.value);
|
|
32542
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
32543
|
+
type: "button",
|
|
32544
|
+
onClick: () => onToggleFacet(facet.value),
|
|
32545
|
+
"aria-pressed": pressed,
|
|
32546
|
+
className: pressed ? CHIP_ON : CHIP_OFF,
|
|
32547
|
+
children: [
|
|
32548
|
+
facetLabel ? facetLabel(facet.value) : facet.value,
|
|
32549
|
+
" ",
|
|
32550
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
32551
|
+
className: "text-foreground-subtle tabular-nums",
|
|
32552
|
+
children: facet.count
|
|
32553
|
+
})
|
|
32554
|
+
]
|
|
32555
|
+
}, facet.value);
|
|
32556
|
+
})
|
|
32557
|
+
}),
|
|
32558
|
+
active && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
31074
32559
|
type: "button",
|
|
31075
|
-
|
|
31076
|
-
|
|
31077
|
-
|
|
31078
|
-
|
|
31079
|
-
|
|
31080
|
-
|
|
31081
|
-
children: [running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), confirmLabel ?? label]
|
|
31082
|
-
})] })
|
|
31083
|
-
] })
|
|
31084
|
-
})] });
|
|
32560
|
+
onClick: onClear,
|
|
32561
|
+
className: "flex items-center gap-1 text-[11px] text-foreground-subtle hover:text-foreground",
|
|
32562
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(X, { className: "h-3 w-3" }), labels.clearLabel]
|
|
32563
|
+
})
|
|
32564
|
+
]
|
|
32565
|
+
});
|
|
31085
32566
|
}
|
|
31086
32567
|
//#endregion
|
|
31087
|
-
//#region src/composites/
|
|
32568
|
+
//#region src/composites/use-data-table-filter.ts
|
|
31088
32569
|
/**
|
|
31089
|
-
*
|
|
31090
|
-
*
|
|
31091
|
-
*
|
|
31092
|
-
*
|
|
32570
|
+
* The React half of `data-table-filter.ts`: one filter state per call site,
|
|
32571
|
+
* so every table on a page filters on its own.
|
|
32572
|
+
*
|
|
32573
|
+
* The caller renders `rows` (the filtered set) and hands `state` + the two
|
|
32574
|
+
* setters to a `DataTableFilterBar`. It keeps the filtered set because
|
|
32575
|
+
* anything that acts on "the rows on screen" — a select-all, a bulk action —
|
|
32576
|
+
* must act on exactly these and nothing the filter hid.
|
|
31093
32577
|
*/
|
|
31094
|
-
|
|
31095
|
-
|
|
31096
|
-
const
|
|
31097
|
-
|
|
31098
|
-
|
|
31099
|
-
|
|
31100
|
-
|
|
31101
|
-
|
|
31102
|
-
|
|
31103
|
-
|
|
31104
|
-
|
|
31105
|
-
|
|
31106
|
-
|
|
31107
|
-
|
|
31108
|
-
|
|
31109
|
-
|
|
31110
|
-
|
|
31111
|
-
|
|
31112
|
-
|
|
31113
|
-
|
|
31114
|
-
|
|
31115
|
-
|
|
31116
|
-
|
|
32578
|
+
function useDataTableFilter(rows, config) {
|
|
32579
|
+
const [state, setState] = (0, react$1.useState)(EMPTY_DATA_TABLE_FILTER);
|
|
32580
|
+
const setSearch = (0, react$1.useCallback)((search) => {
|
|
32581
|
+
setState((prev) => ({
|
|
32582
|
+
...prev,
|
|
32583
|
+
search
|
|
32584
|
+
}));
|
|
32585
|
+
}, []);
|
|
32586
|
+
const toggleFacet = (0, react$1.useCallback)((value) => {
|
|
32587
|
+
setState((prev) => toggleDataTableFacet(prev, value));
|
|
32588
|
+
}, []);
|
|
32589
|
+
const clear = (0, react$1.useCallback)(() => setState(EMPTY_DATA_TABLE_FILTER), []);
|
|
32590
|
+
const { searchText, facetValue } = config;
|
|
32591
|
+
return {
|
|
32592
|
+
state,
|
|
32593
|
+
setSearch,
|
|
32594
|
+
toggleFacet,
|
|
32595
|
+
clear,
|
|
32596
|
+
facets: (0, react$1.useMemo)(() => dataTableFacets(rows, facetValue), [rows, facetValue]),
|
|
32597
|
+
rows: (0, react$1.useMemo)(() => filterDataTableRows(rows, state, {
|
|
32598
|
+
searchText,
|
|
32599
|
+
facetValue
|
|
32600
|
+
}), [
|
|
32601
|
+
rows,
|
|
32602
|
+
state,
|
|
32603
|
+
searchText,
|
|
32604
|
+
facetValue
|
|
32605
|
+
]),
|
|
32606
|
+
active: isDataTableFilterActive(state)
|
|
32607
|
+
};
|
|
31117
32608
|
}
|
|
31118
32609
|
//#endregion
|
|
31119
32610
|
//#region src/composites/detection-colors.ts
|
|
@@ -44891,35 +46382,129 @@ function deviceRoleMetaOf(role) {
|
|
|
44891
46382
|
return DEVICE_ROLE_META[role] ?? FALLBACK_DEVICE_ROLE_META;
|
|
44892
46383
|
}
|
|
44893
46384
|
/**
|
|
44894
|
-
* Icons an operator may pick for a per-device `display.icon` override
|
|
44895
|
-
* a stable kebab-case name.
|
|
44896
|
-
*
|
|
44897
|
-
*
|
|
44898
|
-
*
|
|
46385
|
+
* Icons an operator may pick for a per-device `display.icon` override (and for
|
|
46386
|
+
* a per-role default), keyed by a stable kebab-case name. Named imports only —
|
|
46387
|
+
* no dynamic indexing into lucide, so the bundle carries exactly this set.
|
|
46388
|
+
* Unknown keys resolve to `undefined` (caller falls through to the next level).
|
|
46389
|
+
*
|
|
46390
|
+
* The vocabulary is the one the VIEWER draws from too (its own icon module,
|
|
46391
|
+
* keyed by these same strings): a key added here that the phone cannot draw
|
|
46392
|
+
* falls through to the next resolution level there, which reads as "my change
|
|
46393
|
+
* did nothing". So the KEY is the contract — a stable kebab-case string, never
|
|
46394
|
+
* a glyph identity — and `device-meta.test.ts` pins that every key resolves
|
|
46395
|
+
* and that the set never loses a key an operator may already have persisted.
|
|
46396
|
+
*
|
|
46397
|
+
* Grouped by what an operator is naming: a device, a light or appliance, a
|
|
46398
|
+
* sensor or the weather it reads, a room, or what moves through it. `gauge2`
|
|
46399
|
+
* is a legacy alias kept for rows that persisted it.
|
|
44899
46400
|
*/
|
|
44900
46401
|
var DISPLAY_ICON_REGISTRY = {
|
|
46402
|
+
camera: Camera,
|
|
46403
|
+
network: Network,
|
|
46404
|
+
"toggle-left": ToggleLeft,
|
|
46405
|
+
"toggle-right": ToggleRight,
|
|
46406
|
+
"circle-dot": CircleDot,
|
|
46407
|
+
"refresh-cw": RefreshCw,
|
|
46408
|
+
box: Box,
|
|
46409
|
+
"scroll-text": ScrollText,
|
|
46410
|
+
workflow: Workflow,
|
|
46411
|
+
lock: Lock,
|
|
46412
|
+
"lock-open": LockOpen,
|
|
46413
|
+
blinds: Blinds,
|
|
46414
|
+
"air-vent": AirVent,
|
|
46415
|
+
"monitor-play": MonitorPlay,
|
|
46416
|
+
"sliders-horizontal": SlidersHorizontal,
|
|
46417
|
+
"map-pin": MapPin,
|
|
46418
|
+
bot: Bot,
|
|
46419
|
+
tractor: Tractor,
|
|
46420
|
+
image: Image,
|
|
46421
|
+
bone: Bone,
|
|
46422
|
+
"door-closed": DoorClosed,
|
|
46423
|
+
"door-open": DoorOpen,
|
|
46424
|
+
crosshair: Crosshair,
|
|
46425
|
+
eye: Eye,
|
|
46426
|
+
"eye-off": EyeOff,
|
|
46427
|
+
smartphone: Smartphone,
|
|
46428
|
+
phone: Phone,
|
|
46429
|
+
tablet: Tablet,
|
|
46430
|
+
laptop: Laptop,
|
|
46431
|
+
monitor: Monitor,
|
|
46432
|
+
tv: Tv,
|
|
46433
|
+
speaker: Speaker,
|
|
46434
|
+
printer: Printer,
|
|
46435
|
+
projector: Projector,
|
|
46436
|
+
"gamepad-2": Gamepad2,
|
|
46437
|
+
radio: Radio,
|
|
46438
|
+
router: Router,
|
|
46439
|
+
bluetooth: Bluetooth,
|
|
46440
|
+
cpu: Cpu,
|
|
46441
|
+
"hard-drive": HardDrive,
|
|
46442
|
+
server: Server,
|
|
46443
|
+
mic: Mic,
|
|
46444
|
+
"mic-off": MicOff,
|
|
46445
|
+
video: Video,
|
|
46446
|
+
"video-off": VideoOff,
|
|
46447
|
+
music: Music,
|
|
46448
|
+
plug: Plug,
|
|
46449
|
+
"plug-zap": PlugZap,
|
|
46450
|
+
power: Power,
|
|
46451
|
+
"power-off": PowerOff,
|
|
46452
|
+
cable: Cable,
|
|
46453
|
+
battery: Battery,
|
|
46454
|
+
"battery-charging": BatteryCharging,
|
|
46455
|
+
"battery-full": BatteryFull,
|
|
46456
|
+
"battery-low": BatteryLow,
|
|
46457
|
+
key: Key,
|
|
46458
|
+
"key-round": KeyRound,
|
|
46459
|
+
mailbox: Mailbox,
|
|
46460
|
+
package: Package,
|
|
46461
|
+
shield: Shield,
|
|
46462
|
+
"shield-check": ShieldCheck,
|
|
46463
|
+
"shield-off": ShieldOff,
|
|
46464
|
+
lightbulb: Lightbulb,
|
|
46465
|
+
lamp: Lamp,
|
|
46466
|
+
"lamp-ceiling": LampCeiling,
|
|
46467
|
+
"lamp-desk": LampDesk,
|
|
46468
|
+
"lamp-floor": LampFloor,
|
|
46469
|
+
flashlight: Flashlight,
|
|
46470
|
+
heater: Heater,
|
|
46471
|
+
snowflake: Snowflake,
|
|
46472
|
+
"thermometer-sun": ThermometerSun,
|
|
46473
|
+
"thermometer-snowflake": ThermometerSnowflake,
|
|
46474
|
+
refrigerator: Refrigerator,
|
|
46475
|
+
microwave: Microwave,
|
|
46476
|
+
"washing-machine": WashingMachine,
|
|
46477
|
+
"cooking-pot": CookingPot,
|
|
46478
|
+
utensils: Utensils,
|
|
44901
46479
|
thermometer: Thermometer,
|
|
44902
46480
|
droplets: Droplets,
|
|
44903
46481
|
droplet: Droplet,
|
|
44904
46482
|
gauge: Gauge,
|
|
46483
|
+
gauge2: Gauge,
|
|
44905
46484
|
sun: Sun,
|
|
44906
46485
|
"sun-medium": SunMedium,
|
|
44907
46486
|
sunrise: Sunrise,
|
|
44908
46487
|
moon: Moon,
|
|
44909
46488
|
wind: Wind,
|
|
46489
|
+
cloud: Cloud,
|
|
44910
46490
|
"cloud-rain": CloudRain,
|
|
44911
46491
|
"cloud-sun": CloudSun,
|
|
44912
46492
|
waves: Waves,
|
|
44913
46493
|
sprout: Sprout,
|
|
46494
|
+
leaf: Leaf,
|
|
46495
|
+
flower: Flower,
|
|
44914
46496
|
zap: Zap,
|
|
44915
46497
|
flame: Flame,
|
|
44916
46498
|
bell: Bell,
|
|
46499
|
+
"bell-ring": BellRing,
|
|
46500
|
+
"bell-off": BellOff,
|
|
44917
46501
|
siren: Siren,
|
|
44918
46502
|
radar: Radar,
|
|
44919
46503
|
activity: Activity,
|
|
44920
46504
|
hash: Hash,
|
|
44921
46505
|
clock: Clock,
|
|
44922
|
-
|
|
46506
|
+
timer: Timer,
|
|
46507
|
+
calendar: Calendar,
|
|
44923
46508
|
pipette: Pipette,
|
|
44924
46509
|
fan: Fan,
|
|
44925
46510
|
bath: Bath,
|
|
@@ -44928,9 +46513,49 @@ var DISPLAY_ICON_REGISTRY = {
|
|
|
44928
46513
|
"shield-alert": ShieldAlert,
|
|
44929
46514
|
vibrate: Vibrate,
|
|
44930
46515
|
wifi: Wifi,
|
|
46516
|
+
"wifi-off": WifiOff,
|
|
44931
46517
|
"volume-2": Volume2,
|
|
44932
|
-
|
|
44933
|
-
|
|
46518
|
+
"volume-x": VolumeX,
|
|
46519
|
+
heart: Heart,
|
|
46520
|
+
"heart-pulse": HeartPulse,
|
|
46521
|
+
star: Star,
|
|
46522
|
+
sparkles: Sparkles,
|
|
46523
|
+
house: House,
|
|
46524
|
+
sofa: Sofa,
|
|
46525
|
+
"bed-double": BedDouble,
|
|
46526
|
+
bed: Bed,
|
|
46527
|
+
warehouse: Warehouse,
|
|
46528
|
+
trees: Trees,
|
|
46529
|
+
"tree-pine": TreePine,
|
|
46530
|
+
fence: Fence,
|
|
46531
|
+
store: Store,
|
|
46532
|
+
building: Building,
|
|
46533
|
+
"building-2": Building2,
|
|
46534
|
+
mountain: Mountain,
|
|
46535
|
+
umbrella: Umbrella,
|
|
46536
|
+
dumbbell: Dumbbell,
|
|
46537
|
+
wrench: Wrench,
|
|
46538
|
+
hammer: Hammer,
|
|
46539
|
+
drill: Drill,
|
|
46540
|
+
"waves-ladder": WavesLadder,
|
|
46541
|
+
"circle-parking": CircleParking,
|
|
46542
|
+
briefcase: Briefcase,
|
|
46543
|
+
route: Route,
|
|
46544
|
+
car: Car,
|
|
46545
|
+
bike: Bike,
|
|
46546
|
+
bus: Bus,
|
|
46547
|
+
truck: Truck,
|
|
46548
|
+
plane: Plane,
|
|
46549
|
+
"train-front": TrainFront,
|
|
46550
|
+
sailboat: Sailboat,
|
|
46551
|
+
dog: Dog,
|
|
46552
|
+
cat: Cat,
|
|
46553
|
+
bird: Bird,
|
|
46554
|
+
"paw-print": PawPrint,
|
|
46555
|
+
baby: Baby,
|
|
46556
|
+
user: User,
|
|
46557
|
+
users: Users,
|
|
46558
|
+
"person-standing": PersonStanding
|
|
44934
46559
|
};
|
|
44935
46560
|
/** Resolve a `display.icon` name to a lucide component, or `undefined` when the
|
|
44936
46561
|
* name is not in the curated `DISPLAY_ICON_REGISTRY`. */
|
|
@@ -45115,12 +46740,35 @@ function resolveFlags(props) {
|
|
|
45115
46740
|
showChildrenAccordion: props.showChildrenAccordion ?? d.showChildrenAccordion
|
|
45116
46741
|
};
|
|
45117
46742
|
}
|
|
45118
|
-
|
|
46743
|
+
/**
|
|
46744
|
+
* The row's lead icon — the ONE resolution order, the same one
|
|
46745
|
+
* `resolveSensorDisplay` applies to unit and precision:
|
|
46746
|
+
*
|
|
46747
|
+
* device override → operator role default → built-in role glyph → type glyph
|
|
46748
|
+
*
|
|
46749
|
+
* `roleDefaults` is what the nearest `RoleDisplayDefaultsProvider` publishes;
|
|
46750
|
+
* callers outside a provider pass nothing and the second level is skipped.
|
|
46751
|
+
* Each level FALLS THROUGH when it names a glyph the registry cannot draw —
|
|
46752
|
+
* an unknown key at any level must never leave the row without an icon, and
|
|
46753
|
+
* must never shadow a lower level that would have drawn one.
|
|
46754
|
+
*
|
|
46755
|
+
* "Set as default for all X sensors" writes the second level. It was written
|
|
46756
|
+
* for a year and read by nothing: this function went straight from the device
|
|
46757
|
+
* override to `DEVICE_ROLE_META`.
|
|
46758
|
+
*/
|
|
46759
|
+
function deviceLeadIcon(device, roleDefaults) {
|
|
45119
46760
|
if (device.display?.icon) {
|
|
45120
46761
|
const overridden = resolveDisplayIcon(device.display.icon);
|
|
45121
46762
|
if (overridden) return overridden;
|
|
45122
46763
|
}
|
|
45123
|
-
if (device.role)
|
|
46764
|
+
if (device.role) {
|
|
46765
|
+
const roleDefaultKey = roleDefaults?.[device.role]?.icon;
|
|
46766
|
+
if (roleDefaultKey !== void 0) {
|
|
46767
|
+
const fromRoleDefault = resolveDisplayIcon(roleDefaultKey);
|
|
46768
|
+
if (fromRoleDefault) return fromRoleDefault;
|
|
46769
|
+
}
|
|
46770
|
+
return deviceRoleMetaOf(device.role).icon;
|
|
46771
|
+
}
|
|
45124
46772
|
return deviceTypeMetaOf(device.type).icon;
|
|
45125
46773
|
}
|
|
45126
46774
|
/**
|
|
@@ -45277,6 +46925,7 @@ function effectiveType(device, containerChildren) {
|
|
|
45277
46925
|
*/
|
|
45278
46926
|
function effectiveIconDevice(device, containerChildren) {
|
|
45279
46927
|
if (device.type !== _camstack_types.DeviceType.Container) return device;
|
|
46928
|
+
if (device.display?.icon !== void 0 && resolveDisplayIcon(device.display.icon) !== void 0) return device;
|
|
45280
46929
|
const children = containerChildren ?? [];
|
|
45281
46930
|
if (children.length === 0) return device;
|
|
45282
46931
|
const primary = resolveContainerPrimary(children, device.linkDeviceId, device.primaryChildEntityId);
|
|
@@ -45656,7 +47305,7 @@ function RebootQuickAction({ trpc, deviceId, deviceName }) {
|
|
|
45656
47305
|
* or a standalone test) the hook returns a stable empty array — the same
|
|
45657
47306
|
* graceful default the old optional prop had.
|
|
45658
47307
|
*/
|
|
45659
|
-
var EMPTY$
|
|
47308
|
+
var EMPTY$2 = [];
|
|
45660
47309
|
var ContainerChildrenContext = (0, react$1.createContext)(null);
|
|
45661
47310
|
function ContainerChildrenProvider({ value, children }) {
|
|
45662
47311
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContainerChildrenContext.Provider, {
|
|
@@ -45671,7 +47320,7 @@ function ContainerChildrenProvider({ value, children }) {
|
|
|
45671
47320
|
* rendered outside a list, degrades exactly like the old optional prop.
|
|
45672
47321
|
*/
|
|
45673
47322
|
function useContainerChildren(deviceId) {
|
|
45674
|
-
return (0, react$1.useContext)(ContainerChildrenContext)?.get(deviceId) ?? EMPTY$
|
|
47323
|
+
return (0, react$1.useContext)(ContainerChildrenContext)?.get(deviceId) ?? EMPTY$2;
|
|
45675
47324
|
}
|
|
45676
47325
|
//#endregion
|
|
45677
47326
|
//#region src/composites/device-item/actions.tsx
|
|
@@ -45897,6 +47546,33 @@ function PtzQuickAction({ trpc, deviceId }) {
|
|
|
45897
47546
|
}), document.body)] });
|
|
45898
47547
|
}
|
|
45899
47548
|
//#endregion
|
|
47549
|
+
//#region src/composites/device-item/role-display-defaults-context.tsx
|
|
47550
|
+
var EMPTY$1 = {};
|
|
47551
|
+
var RoleDisplayDefaultsContext = (0, react$1.createContext)(EMPTY$1);
|
|
47552
|
+
function RoleDisplayDefaultsProvider({ value, children }) {
|
|
47553
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleDisplayDefaultsContext.Provider, {
|
|
47554
|
+
value,
|
|
47555
|
+
children
|
|
47556
|
+
});
|
|
47557
|
+
}
|
|
47558
|
+
/** The defaults published by the nearest provider — an empty record outside
|
|
47559
|
+
* one, never `undefined`, so a caller can index it without a guard. */
|
|
47560
|
+
function useRoleDisplayDefaults() {
|
|
47561
|
+
return (0, react$1.useContext)(RoleDisplayDefaultsContext);
|
|
47562
|
+
}
|
|
47563
|
+
/**
|
|
47564
|
+
* Reads the defaults from the hub and publishes them. Until the query lands
|
|
47565
|
+
* the children see no defaults — the built-in role glyph, which is what they
|
|
47566
|
+
* would see with no operator default at all, so nothing flashes wrong.
|
|
47567
|
+
*/
|
|
47568
|
+
function RoleDisplayDefaultsFromServer({ children }) {
|
|
47569
|
+
const { data } = useDeviceManagerGetRoleDisplayDefaults({}, { staleTime: 6e4 });
|
|
47570
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleDisplayDefaultsProvider, {
|
|
47571
|
+
value: data?.defaults ?? EMPTY$1,
|
|
47572
|
+
children
|
|
47573
|
+
});
|
|
47574
|
+
}
|
|
47575
|
+
//#endregion
|
|
45900
47576
|
//#region src/composites/device-item/children-accordion.tsx
|
|
45901
47577
|
/**
|
|
45902
47578
|
* <ChildrenAccordion> — accessory + adopted children rows shown when
|
|
@@ -45949,7 +47625,7 @@ function ChildRow$1({ trpc, child, onNavigate, parentName }) {
|
|
|
45949
47625
|
},
|
|
45950
47626
|
className: "w-full flex items-center gap-2 px-3 py-1.5 text-left hover:bg-surface-hover transition-colors cursor-pointer",
|
|
45951
47627
|
children: [
|
|
45952
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(deviceLeadIcon(child), { className: "h-3 w-3 text-foreground-subtle flex-shrink-0" }),
|
|
47628
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(deviceLeadIcon(child, useRoleDisplayDefaults()), { className: "h-3 w-3 text-foreground-subtle flex-shrink-0" }),
|
|
45953
47629
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
45954
47630
|
className: "text-[10.5px] text-foreground flex-1 truncate",
|
|
45955
47631
|
children: childListName(child, parentName)
|
|
@@ -46328,7 +48004,9 @@ var CHEVRON_SLOT_CLASS = {
|
|
|
46328
48004
|
stacked: "h-4 w-4 mt-0.5"
|
|
46329
48005
|
};
|
|
46330
48006
|
function DeviceItemHeader({ device, variant, flags, parent, integrationIcon, hasChildren, expanded, onToggleExpand, onNavigateToParent, stacked = false, nameOverflow = "truncate" }) {
|
|
46331
|
-
const
|
|
48007
|
+
const containerChildren = useContainerChildren(device.id);
|
|
48008
|
+
const roleDefaults = useRoleDisplayDefaults();
|
|
48009
|
+
const Icon = deviceLeadIcon(effectiveIconDevice(device, containerChildren), roleDefaults);
|
|
46332
48010
|
const slotClass = stacked ? CHEVRON_SLOT_CLASS.stacked : CHEVRON_SLOT_CLASS.horizontal;
|
|
46333
48011
|
const lineClass = nameOverflow === "scroll" ? NAME_TEXT_CLASS : "truncate";
|
|
46334
48012
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -47135,41 +48813,6 @@ function groupTopLevelAndAccessories(devices, options) {
|
|
|
47135
48813
|
};
|
|
47136
48814
|
}
|
|
47137
48815
|
//#endregion
|
|
47138
|
-
//#region src/composites/device-list/fuzzy-match.ts
|
|
47139
|
-
/**
|
|
47140
|
-
* fuzzy-match — case and separator-insensitive text search helper.
|
|
47141
|
-
*
|
|
47142
|
-
* Normalization rule: lowercase + strip all runs of hyphens, underscores and
|
|
47143
|
-
* whitespace entirely. This makes `Tapparella cucina` and `tapparella-cucina`
|
|
47144
|
-
* both normalize to `tapparellacucina`, so a substring match succeeds in
|
|
47145
|
-
* either direction.
|
|
47146
|
-
*/
|
|
47147
|
-
/**
|
|
47148
|
-
* Strips hyphens, underscores and whitespace runs from `s` and lowercases it.
|
|
47149
|
-
*
|
|
47150
|
-
* @example
|
|
47151
|
-
* normalizeForSearch('Tapparella cucina') // 'tapparellacucina'
|
|
47152
|
-
* normalizeForSearch('tapparella-cucina') // 'tapparellacucina'
|
|
47153
|
-
* normalizeForSearch('tapparella_cucina') // 'tapparellacucina'
|
|
47154
|
-
*/
|
|
47155
|
-
function normalizeForSearch(s) {
|
|
47156
|
-
return s.toLowerCase().replace(/[-_\s]+/g, "");
|
|
47157
|
-
}
|
|
47158
|
-
/**
|
|
47159
|
-
* Returns `true` when the normalized form of `target` contains the normalized
|
|
47160
|
-
* form of `query`. An empty query always matches.
|
|
47161
|
-
*
|
|
47162
|
-
* @example
|
|
47163
|
-
* fuzzyMatch('Tapparella cucina', 'tapparella-cucina') // true
|
|
47164
|
-
* fuzzyMatch('tapparella cucina', 'Tapparella-Cucina') // true
|
|
47165
|
-
* fuzzyMatch('xyz', 'tapparella-cucina') // false
|
|
47166
|
-
*/
|
|
47167
|
-
function fuzzyMatch(query, target) {
|
|
47168
|
-
const nq = normalizeForSearch(query);
|
|
47169
|
-
if (nq === "") return true;
|
|
47170
|
-
return normalizeForSearch(target).includes(nq);
|
|
47171
|
-
}
|
|
47172
|
-
//#endregion
|
|
47173
48816
|
//#region src/composites/device-list/multi-select.tsx
|
|
47174
48817
|
/**
|
|
47175
48818
|
* Compact multi-select dropdown — a single chip-sized trigger ("Label · N")
|
|
@@ -47612,7 +49255,7 @@ function topLevelIndentLevel(device, parentType) {
|
|
|
47612
49255
|
if (HUB_LIKE_PARENT_TYPES.has(parentType.toLowerCase())) return 1;
|
|
47613
49256
|
return 0;
|
|
47614
49257
|
}
|
|
47615
|
-
function TableLayout({ rows, accessoriesByParent,
|
|
49258
|
+
function TableLayout({ rows, accessoriesByParent, expandedParents, onToggleExpand, devices, visibleColumns, trpc, resolveIntegrationIcon, resolveParent, onNavigate, renderRowAction, onDeleteDevice, selectable, rowClickSelects, activeSelected, onToggleSelection, sort, onSort }) {
|
|
47616
49259
|
const showType = visibleColumns.includes("type");
|
|
47617
49260
|
const showFeatures = visibleColumns.includes("features");
|
|
47618
49261
|
const showManufacturer = visibleColumns.includes("manufacturer");
|
|
@@ -47671,7 +49314,6 @@ function TableLayout({ rows, accessoriesByParent, autoExpandedParents, devices,
|
|
|
47671
49314
|
}) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: rows.map((d) => {
|
|
47672
49315
|
const accessories = accessoriesByParent.get(d.id) ?? [];
|
|
47673
49316
|
const hasAccessories = accessories.length > 0;
|
|
47674
|
-
const autoExpand = autoExpandedParents.has(d.id);
|
|
47675
49317
|
const parentRowParent = d.parentDeviceId !== null ? resolveParent?.(d.parentDeviceId) ?? null : null;
|
|
47676
49318
|
const parentType = d.parentDeviceId !== null ? parentTypeById.get(d.parentDeviceId) ?? null : null;
|
|
47677
49319
|
const selection = selectable ? {
|
|
@@ -47682,7 +49324,8 @@ function TableLayout({ rows, accessoriesByParent, autoExpandedParents, devices,
|
|
|
47682
49324
|
parent: d,
|
|
47683
49325
|
accessories,
|
|
47684
49326
|
hasAccessories,
|
|
47685
|
-
|
|
49327
|
+
expanded: expandedParents.has(d.id),
|
|
49328
|
+
onToggleExpand: () => onToggleExpand(d.id),
|
|
47686
49329
|
trpc,
|
|
47687
49330
|
integrationIcon: showIcon ? resolveIntegrationIcon?.(d.addonId) ?? null : null,
|
|
47688
49331
|
showType,
|
|
@@ -47700,16 +49343,7 @@ function TableLayout({ rows, accessoriesByParent, autoExpandedParents, devices,
|
|
|
47700
49343
|
})
|
|
47701
49344
|
});
|
|
47702
49345
|
}
|
|
47703
|
-
function TableRowGroup({ parent, accessories, hasAccessories,
|
|
47704
|
-
const [expanded, setExpanded] = (0, react$1.useState)(forceExpanded);
|
|
47705
|
-
const prevForce = (0, react$1.useRef)(forceExpanded);
|
|
47706
|
-
(0, react$1.useEffect)(() => {
|
|
47707
|
-
if (prevForce.current !== forceExpanded) {
|
|
47708
|
-
setExpanded(forceExpanded);
|
|
47709
|
-
prevForce.current = forceExpanded;
|
|
47710
|
-
}
|
|
47711
|
-
}, [forceExpanded]);
|
|
47712
|
-
const onToggleExpand = () => setExpanded((v) => !v);
|
|
49346
|
+
function TableRowGroup({ parent, accessories, hasAccessories, expanded, onToggleExpand, trpc, integrationIcon, showType, showManufacturer, showFeatures, parentRowParent, indentLevel, onNavigate, renderRowAction, onDeleteDevice, selection, rowClickSelects }) {
|
|
47713
49347
|
const showAccessoryRows = hasAccessories && expanded;
|
|
47714
49348
|
const grouped = (0, react$1.useMemo)(() => groupChildrenByLayout(parent.stableId, accessories, parent.childLayout), [
|
|
47715
49349
|
parent.stableId,
|
|
@@ -47976,13 +49610,15 @@ function DeviceModeList(props) {
|
|
|
47976
49610
|
]);
|
|
47977
49611
|
const grouped = (0, react$1.useMemo)(() => groupTopLevelAndAccessories(filtered), [filtered]);
|
|
47978
49612
|
const sortedTopLevel = (0, react$1.useMemo)(() => sortRows(grouped.topLevel, sort), [grouped.topLevel, sort]);
|
|
47979
|
-
const
|
|
47980
|
-
|
|
47981
|
-
|
|
47982
|
-
|
|
47983
|
-
|
|
47984
|
-
|
|
47985
|
-
|
|
49613
|
+
const [openParents, setOpenParents] = (0, react$1.useState)(() => /* @__PURE__ */ new Set());
|
|
49614
|
+
const toggleParent = (0, react$1.useCallback)((id) => {
|
|
49615
|
+
setOpenParents((prev) => {
|
|
49616
|
+
const next = new Set(prev);
|
|
49617
|
+
if (next.has(id)) next.delete(id);
|
|
49618
|
+
else next.add(id);
|
|
49619
|
+
return next;
|
|
49620
|
+
});
|
|
49621
|
+
}, []);
|
|
47986
49622
|
const totalItems = grouped.topLevel.length;
|
|
47987
49623
|
const totalPages = Math.max(1, Math.ceil(totalItems / currentPageSize));
|
|
47988
49624
|
const safePage = Math.min(url.state.page, totalPages);
|
|
@@ -48052,7 +49688,8 @@ function DeviceModeList(props) {
|
|
|
48052
49688
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TableLayout, {
|
|
48053
49689
|
rows: pageRows,
|
|
48054
49690
|
accessoriesByParent: grouped.accessoriesByParent,
|
|
48055
|
-
|
|
49691
|
+
expandedParents: openParents,
|
|
49692
|
+
onToggleExpand: toggleParent,
|
|
48056
49693
|
visibleColumns,
|
|
48057
49694
|
sort,
|
|
48058
49695
|
onSort,
|
|
@@ -48820,6 +50457,145 @@ function DeviceGrid({ children, minCardWidth = 220, gap = 3, className }) {
|
|
|
48820
50457
|
});
|
|
48821
50458
|
}
|
|
48822
50459
|
//#endregion
|
|
50460
|
+
//#region src/composites/icon-picker.tsx
|
|
50461
|
+
/**
|
|
50462
|
+
* icon-picker — the ONE control for choosing an icon, wherever admin-ui asks.
|
|
50463
|
+
*
|
|
50464
|
+
* There were two: a device one over `DISPLAY_ICON_REGISTRY` and a room one
|
|
50465
|
+
* over `LOCATION_ICON_IDS`, identical apart from the vocabulary they walked.
|
|
50466
|
+
* That was tolerable while the device set was 56 glyphs in a flat grid. It is
|
|
50467
|
+
* not at 157: a picker that only lets the operator HUNT is a picker they stop
|
|
50468
|
+
* using, and the two copies were already drifting — only one of them had an
|
|
50469
|
+
* i18n'd label.
|
|
50470
|
+
*
|
|
50471
|
+
* So the vocabulary, the grouping and the search all come from
|
|
50472
|
+
* `@camstack/types`' `display-icon-vocabulary`, which is the same module the
|
|
50473
|
+
* viewer's sheet searches. What is left here is the web half: a popover, a
|
|
50474
|
+
* grid, and a text field.
|
|
50475
|
+
*
|
|
50476
|
+
* ## What it does NOT own
|
|
50477
|
+
*
|
|
50478
|
+
* - **The glyphs.** `resolveGlyph` is a prop, because this package's table is
|
|
50479
|
+
* `lucide-react` and the viewer's is `lucide-react-native`. A key this build
|
|
50480
|
+
* cannot draw is SKIPPED rather than rendered as a hole — an empty tile
|
|
50481
|
+
* reads as a rendering bug, not as a missing choice.
|
|
50482
|
+
* - **The words.** Every string is a prop. This package has no i18n (one file
|
|
50483
|
+
* in it imports react-i18next, and that is the exception), and a control
|
|
50484
|
+
* that reached for a translation would force it on every consumer.
|
|
50485
|
+
* - **The write.** `onPick` is called and that is all; whether it is a form
|
|
50486
|
+
* value or an immediate mutation is the caller's business, as it already was
|
|
50487
|
+
* in both pickers this replaces.
|
|
50488
|
+
*
|
|
50489
|
+
* ## The clear tile is never filtered away
|
|
50490
|
+
*
|
|
50491
|
+
* The "inherit" / "no icon" choice sits above the sections and ignores the
|
|
50492
|
+
* search. A query that hid it would strand the operator on a glyph they picked
|
|
50493
|
+
* by accident with no way back to the default — the one action a picker must
|
|
50494
|
+
* always offer.
|
|
50495
|
+
*/
|
|
50496
|
+
function IconPicker({ value, onPick, resolveGlyph, fallbackGlyph, labels, only, extraWords, clearGlyph, labelFor, disabled = false }) {
|
|
50497
|
+
const [open, setOpen] = (0, react$1.useState)(false);
|
|
50498
|
+
const [query, setQuery] = (0, react$1.useState)("");
|
|
50499
|
+
const allowed = (0, react$1.useMemo)(() => only === void 0 ? null : new Set(only), [only]);
|
|
50500
|
+
const sections = (0, react$1.useMemo)(() => {
|
|
50501
|
+
const hits = (0, _camstack_types.searchDisplayIcons)(query, extraWords);
|
|
50502
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
50503
|
+
for (const hit of hits) {
|
|
50504
|
+
const { key, group } = hit.entry;
|
|
50505
|
+
if (allowed !== null && !allowed.has(key)) continue;
|
|
50506
|
+
if (resolveGlyph(key) === void 0) continue;
|
|
50507
|
+
const bucket = byGroup.get(group);
|
|
50508
|
+
if (bucket === void 0) byGroup.set(group, [key]);
|
|
50509
|
+
else bucket.push(key);
|
|
50510
|
+
}
|
|
50511
|
+
return _camstack_types.DISPLAY_ICON_GROUPS.flatMap((group) => {
|
|
50512
|
+
const keys = byGroup.get(group);
|
|
50513
|
+
return keys === void 0 || keys.length === 0 ? [] : [{
|
|
50514
|
+
group,
|
|
50515
|
+
keys
|
|
50516
|
+
}];
|
|
50517
|
+
});
|
|
50518
|
+
}, [
|
|
50519
|
+
query,
|
|
50520
|
+
extraWords,
|
|
50521
|
+
allowed,
|
|
50522
|
+
resolveGlyph
|
|
50523
|
+
]);
|
|
50524
|
+
const Current = (value === null ? void 0 : resolveGlyph(value)) ?? fallbackGlyph;
|
|
50525
|
+
const choose = async (next) => {
|
|
50526
|
+
setOpen(false);
|
|
50527
|
+
setQuery("");
|
|
50528
|
+
await onPick(next);
|
|
50529
|
+
};
|
|
50530
|
+
const Clear = clearGlyph ?? fallbackGlyph;
|
|
50531
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50532
|
+
className: "relative flex shrink-0 items-center gap-2",
|
|
50533
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
50534
|
+
type: "button",
|
|
50535
|
+
disabled,
|
|
50536
|
+
onClick: () => setOpen((o) => !o),
|
|
50537
|
+
"aria-expanded": open,
|
|
50538
|
+
"aria-label": labels.trigger,
|
|
50539
|
+
title: labels.trigger,
|
|
50540
|
+
className: "rounded border border-border p-1 text-foreground-muted hover:bg-surface-hover disabled:opacity-50",
|
|
50541
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Current, { className: "h-4 w-4" })
|
|
50542
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
50543
|
+
type: "button",
|
|
50544
|
+
"aria-label": labels.close,
|
|
50545
|
+
onClick: () => setOpen(false),
|
|
50546
|
+
className: "fixed inset-0 z-10 cursor-default"
|
|
50547
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50548
|
+
role: "group",
|
|
50549
|
+
"aria-label": labels.panel,
|
|
50550
|
+
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",
|
|
50551
|
+
children: [
|
|
50552
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
50553
|
+
type: "search",
|
|
50554
|
+
value: query,
|
|
50555
|
+
autoFocus: true,
|
|
50556
|
+
onChange: (e) => setQuery(e.target.value),
|
|
50557
|
+
placeholder: labels.searchPlaceholder,
|
|
50558
|
+
"aria-label": labels.searchPlaceholder,
|
|
50559
|
+
className: "mb-2 w-full rounded border border-border bg-surface-raised px-2 py-1 text-xs text-foreground placeholder:text-foreground-subtle"
|
|
50560
|
+
}),
|
|
50561
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
50562
|
+
type: "button",
|
|
50563
|
+
onClick: () => void choose(null),
|
|
50564
|
+
"aria-pressed": value === null,
|
|
50565
|
+
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",
|
|
50566
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Clear, { className: "h-3.5 w-3.5" }), labels.clear]
|
|
50567
|
+
}),
|
|
50568
|
+
sections.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
50569
|
+
className: "px-1.5 py-2 text-[11px] text-foreground-subtle",
|
|
50570
|
+
children: labels.noMatches
|
|
50571
|
+
}) : sections.map(({ group, keys }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
50572
|
+
className: "mb-2 last:mb-0",
|
|
50573
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
50574
|
+
className: "px-1.5 pb-1 text-[10px] uppercase tracking-wide text-foreground-subtle",
|
|
50575
|
+
children: labels.groups[group]
|
|
50576
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
50577
|
+
className: "grid grid-cols-7 gap-1",
|
|
50578
|
+
children: keys.map((key) => {
|
|
50579
|
+
const Glyph = resolveGlyph(key);
|
|
50580
|
+
if (Glyph === void 0) return null;
|
|
50581
|
+
const selected = value === key;
|
|
50582
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
50583
|
+
type: "button",
|
|
50584
|
+
onClick: () => void choose(key),
|
|
50585
|
+
"aria-pressed": selected,
|
|
50586
|
+
"aria-label": labelFor?.(key) ?? key,
|
|
50587
|
+
title: labelFor?.(key) ?? key,
|
|
50588
|
+
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",
|
|
50589
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, { className: "h-4 w-4" })
|
|
50590
|
+
}, key);
|
|
50591
|
+
})
|
|
50592
|
+
})]
|
|
50593
|
+
}, group))
|
|
50594
|
+
]
|
|
50595
|
+
})] })]
|
|
50596
|
+
});
|
|
50597
|
+
}
|
|
50598
|
+
//#endregion
|
|
48823
50599
|
//#region src/composites/device-selector-picker.tsx
|
|
48824
50600
|
/**
|
|
48825
50601
|
* <DeviceSelectorPicker> — the operator-facing editor for a v3
|
|
@@ -53773,7 +55549,7 @@ var MotionZonesSettings = (0, react$1.lazy)(() => Promise.resolve().then(() => r
|
|
|
53773
55549
|
* inside `<Suspense>`; the admin-ui `/scenes` dialog is its only caller. */
|
|
53774
55550
|
var SceneMonitorEditor = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./SceneMonitorEditor-D0mXx_SD.cjs")).then((m) => ({ default: m.SceneMonitorEditor })));
|
|
53775
55551
|
/** Lazy-wrapped `PrivacyMaskSettings` — code-split off the main bundle. */
|
|
53776
|
-
var PrivacyMaskSettings = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-
|
|
55552
|
+
var PrivacyMaskSettings = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-BqjXsXZR.cjs")).then((m) => ({ default: m.PrivacyMaskSettings })));
|
|
53777
55553
|
//#endregion
|
|
53778
55554
|
exports.AddonGlobalSettingsForm = AddonGlobalSettingsForm;
|
|
53779
55555
|
exports.AgentStepEditor = AgentStepEditor;
|
|
@@ -53845,6 +55621,7 @@ exports.DEVICE_TYPE_CONTROL = DEVICE_TYPE_CONTROL;
|
|
|
53845
55621
|
exports.DEVICE_TYPE_META = DEVICE_TYPE_META;
|
|
53846
55622
|
exports.DISPLAY_ICON_REGISTRY = DISPLAY_ICON_REGISTRY;
|
|
53847
55623
|
exports.DataTable = DataTable;
|
|
55624
|
+
exports.DataTableFilterBar = DataTableFilterBar;
|
|
53848
55625
|
exports.DetectionCanvas = DetectionCanvas;
|
|
53849
55626
|
exports.DetectionOverlay = DetectionOverlay;
|
|
53850
55627
|
exports.DetectionResultTree = DetectionResultTree;
|
|
@@ -53877,6 +55654,7 @@ exports.DropdownItem = DropdownItem;
|
|
|
53877
55654
|
exports.DropdownTrigger = DropdownTrigger;
|
|
53878
55655
|
exports.DummyHeroCard = DummyHeroCard;
|
|
53879
55656
|
exports.DummyInline = DummyInline;
|
|
55657
|
+
exports.EMPTY_DATA_TABLE_FILTER = EMPTY_DATA_TABLE_FILTER;
|
|
53880
55658
|
exports.EVENT_KIND_ICONS = EVENT_KIND_ICONS;
|
|
53881
55659
|
exports.EmptyState = EmptyState;
|
|
53882
55660
|
exports.ErrorBox = ErrorBox;
|
|
@@ -53909,6 +55687,7 @@ exports.HumidifierInlineControl = HumidifierInlineControl;
|
|
|
53909
55687
|
exports.INPUT_COMPACT = INPUT_COMPACT;
|
|
53910
55688
|
exports.IconAction = IconAction;
|
|
53911
55689
|
exports.IconButton = IconButton;
|
|
55690
|
+
exports.IconPicker = IconPicker;
|
|
53912
55691
|
exports.ImageHeroCard = ImageHeroCard;
|
|
53913
55692
|
exports.ImageInlineControl = ImageInlineControl;
|
|
53914
55693
|
exports.ImageOff = ImageOff;
|
|
@@ -53934,6 +55713,7 @@ exports.MediaPlayerHeroCard = MediaPlayerHeroCard;
|
|
|
53934
55713
|
exports.MediaPlayerInlineControl = MediaPlayerInlineControl;
|
|
53935
55714
|
exports.MediaPlayerPanel = MediaPlayerPanel;
|
|
53936
55715
|
exports.Mic = Mic;
|
|
55716
|
+
exports.MicOff = MicOff;
|
|
53937
55717
|
exports.MobileDrawer = MobileDrawer;
|
|
53938
55718
|
exports.ModelPicker = ModelPicker;
|
|
53939
55719
|
exports.MotionZonesSettings = MotionZonesSettings;
|
|
@@ -53972,6 +55752,8 @@ exports.RadialGauge = RadialGauge;
|
|
|
53972
55752
|
exports.RecordedPlaybackProvider = RecordedPlaybackProvider;
|
|
53973
55753
|
exports.RecordingPanel = RecordingPanel;
|
|
53974
55754
|
exports.ResponseLog = ResponseLog;
|
|
55755
|
+
exports.RoleDisplayDefaultsFromServer = RoleDisplayDefaultsFromServer;
|
|
55756
|
+
exports.RoleDisplayDefaultsProvider = RoleDisplayDefaultsProvider;
|
|
53975
55757
|
exports.RotateCcw = RotateCcw;
|
|
53976
55758
|
exports.SCENE_CAPTURE_VARIANTS = SCENE_CAPTURE_VARIANTS;
|
|
53977
55759
|
exports.SCENE_CAPTURE_VARIANTS$1 = SCENE_CAPTURE_VARIANTS;
|
|
@@ -54080,6 +55862,7 @@ exports.createSharedContext = createSharedContext;
|
|
|
54080
55862
|
exports.createTheme = require_theme_index.createTheme$1;
|
|
54081
55863
|
exports.cursorFractionFor = cursorFractionFor;
|
|
54082
55864
|
exports.darkColors = require_theme_index.darkColors$1;
|
|
55865
|
+
exports.dataTableFacets = dataTableFacets;
|
|
54083
55866
|
exports.defaultTheme = require_theme_index.defaultTheme$1;
|
|
54084
55867
|
exports.deriveDeviceKind = deriveDeviceKind;
|
|
54085
55868
|
exports.deriveWebrtcDebugStats = deriveWebrtcDebugStats;
|
|
@@ -54094,6 +55877,7 @@ exports.deviceTypeMetaOf = deviceTypeMetaOf;
|
|
|
54094
55877
|
exports.devicesToOptions = devicesToOptions;
|
|
54095
55878
|
exports.ensureMfHostInit = ensureMfHostInit;
|
|
54096
55879
|
exports.eventKindLabel = eventKindLabel;
|
|
55880
|
+
exports.filterDataTableRows = filterDataTableRows;
|
|
54097
55881
|
exports.filterDeviceOptions = filterDeviceOptions;
|
|
54098
55882
|
exports.findTimezone = _camstack_types.findTimezone;
|
|
54099
55883
|
exports.formatControlDateTime = formatControlDateTime;
|
|
@@ -54116,6 +55900,7 @@ exports.humidifierTint = humidifierTint;
|
|
|
54116
55900
|
exports.initialScrubState = initialScrubState;
|
|
54117
55901
|
exports.isAbsentProvider = isAbsentProvider;
|
|
54118
55902
|
exports.isAbsentProvider$1 = isAbsentProvider;
|
|
55903
|
+
exports.isDataTableFilterActive = isDataTableFilterActive;
|
|
54119
55904
|
exports.isFieldVisible = isFieldVisible;
|
|
54120
55905
|
exports.lawnMowerActivityMeta = lawnMowerActivityMeta;
|
|
54121
55906
|
exports.lightColors = require_theme_index.lightColors$1;
|
|
@@ -54168,6 +55953,7 @@ exports.tabForSelector = tabForSelector;
|
|
|
54168
55953
|
exports.tankAlert = tankAlert;
|
|
54169
55954
|
exports.themeToCss = require_theme_index.themeToCss$1;
|
|
54170
55955
|
exports.toStationaryOverlayBox = toStationaryOverlayBox;
|
|
55956
|
+
exports.toggleDataTableFacet = toggleDataTableFacet;
|
|
54171
55957
|
exports.trpc = trpc;
|
|
54172
55958
|
exports.useAccessoriesGetStatus = useAccessoriesGetStatus;
|
|
54173
55959
|
exports.useAccessoriesSetChildHidden = useAccessoriesSetChildHidden;
|
|
@@ -54347,6 +56133,7 @@ exports.useCoverSetPosition = useCoverSetPosition;
|
|
|
54347
56133
|
exports.useCoverSetTiltPosition = useCoverSetTiltPosition;
|
|
54348
56134
|
exports.useCoverStop = useCoverStop;
|
|
54349
56135
|
exports.useCustomFieldRenderer = useCustomFieldRenderer;
|
|
56136
|
+
exports.useDataTableFilter = useDataTableFilter;
|
|
54350
56137
|
exports.useDayNightGetOptions = useDayNightGetOptions;
|
|
54351
56138
|
exports.useDayNightGetStatus = useDayNightGetStatus;
|
|
54352
56139
|
exports.useDayNightSetSettings = useDayNightSetSettings;
|
|
@@ -55054,6 +56841,7 @@ exports.useRecordingSignalGetStatus = useRecordingSignalGetStatus;
|
|
|
55054
56841
|
exports.useRecordingStartStorageMigrationMove = useRecordingStartStorageMigrationMove;
|
|
55055
56842
|
exports.useRecordingStartStorageRebalance = useRecordingStartStorageRebalance;
|
|
55056
56843
|
exports.useRemoteComponent = useRemoteComponent;
|
|
56844
|
+
exports.useRoleDisplayDefaults = useRoleDisplayDefaults;
|
|
55057
56845
|
exports.useSceneMonitorCaptureReference = useSceneMonitorCaptureReference;
|
|
55058
56846
|
exports.useSceneMonitorCreateScene = useSceneMonitorCreateScene;
|
|
55059
56847
|
exports.useSceneMonitorDeleteReference = useSceneMonitorDeleteReference;
|
|
@@ -55259,6 +57047,7 @@ exports.useValveOpen = useValveOpen;
|
|
|
55259
57047
|
exports.useValveSetPosition = useValveSetPosition;
|
|
55260
57048
|
exports.useValveStop = useValveStop;
|
|
55261
57049
|
exports.useVibrationGetStatus = useVibrationGetStatus;
|
|
57050
|
+
exports.useVideoclipsDialClipStream = useVideoclipsDialClipStream;
|
|
55262
57051
|
exports.useVideoclipsGetClipPlayback = useVideoclipsGetClipPlayback;
|
|
55263
57052
|
exports.useVideoclipsListClips = useVideoclipsListClips;
|
|
55264
57053
|
exports.useVideoclipsListSources = useVideoclipsListSources;
|