@camstack/ui-library 1.2.265 → 1.2.267
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composites/index.d.ts +8 -1
- package/dist/composites/item-form-dialog.d.ts +24 -0
- package/dist/composites/media-grid.d.ts +27 -0
- package/dist/composites/node-color-map.d.ts +11 -0
- package/dist/composites/node-color.d.ts +1 -0
- package/dist/composites/node-picker.d.ts +3 -1
- package/dist/composites/page-header.d.ts +4 -0
- package/dist/composites/page-shell.d.ts +20 -0
- package/dist/composites/section-toolbar.d.ts +14 -0
- package/dist/composites/use-item-form-url.d.ts +21 -0
- package/dist/index.cjs +288 -56
- package/dist/index.js +280 -58
- package/dist/lib/responsive.d.ts +2 -0
- package/dist/primitives/dialog.d.ts +1 -1
- package/package.json +1 -1
|
@@ -16,8 +16,10 @@ export type { BatteryBadgeProps, BatteryStatusLike } from "./battery-badge";
|
|
|
16
16
|
export { BatteryBadge } from "./battery-badge";
|
|
17
17
|
export type { NetworkLinkBadgeProps, NetworkLinkStatusLike } from "./network-link-badge";
|
|
18
18
|
export { NetworkLinkBadge } from "./network-link-badge";
|
|
19
|
-
export { NODE_COLOR_IDS, assignNodeColorId, nodeColorClass, nodeTextColorClass } from "./node-color";
|
|
19
|
+
export { NODE_COLOR_IDS, assignNodeColorId, nodeColorClass, nodeSoftBgClass, nodeTextColorClass } from "./node-color";
|
|
20
20
|
export type { NodeColorId } from "./node-color";
|
|
21
|
+
export type { AgentSettingsColorRow } from "./node-color-map";
|
|
22
|
+
export { buildColorByNodeId, colorIdForNode } from "./node-color-map";
|
|
21
23
|
export type { PipelineRoleId, RoleNodePillProps } from "./role-node-pill";
|
|
22
24
|
export { RoleNodePill } from "./role-node-pill";
|
|
23
25
|
export * from "./breadcrumb";
|
|
@@ -157,6 +159,11 @@ export { mountAddonPage } from "./mount-addon-page";
|
|
|
157
159
|
export type { NodePickerProps } from "./node-picker";
|
|
158
160
|
export { NodePicker } from "./node-picker";
|
|
159
161
|
export * from "./page-header";
|
|
162
|
+
export * from "./page-shell";
|
|
163
|
+
export * from "./section-toolbar";
|
|
164
|
+
export * from "./item-form-dialog";
|
|
165
|
+
export * from "./use-item-form-url";
|
|
166
|
+
export * from "./media-grid";
|
|
160
167
|
export type { PhaseIconProps } from "./phase-icon";
|
|
161
168
|
export { PhaseIcon } from "./phase-icon";
|
|
162
169
|
export type { PipelineBuilderProps } from "./pipeline-builder";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ItemFormDialog — shared shell for create/edit of a named list item.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors Notification Center Rules: `Dialog` + `DialogContent` width lg,
|
|
5
|
+
* sheet-on-mobile, scroll body; title + Cancel in the chrome; body and footer
|
|
6
|
+
* slots for the form. Open state is owned by the caller (typically
|
|
7
|
+
* `useItemFormUrl`).
|
|
8
|
+
*/
|
|
9
|
+
import type { LucideIcon } from "lucide-react";
|
|
10
|
+
import type { ReactNode, JSX } from "react";
|
|
11
|
+
import { type DialogContentProps } from "../primitives";
|
|
12
|
+
export interface ItemFormDialogProps {
|
|
13
|
+
readonly open: boolean;
|
|
14
|
+
readonly onClose: () => void;
|
|
15
|
+
readonly title: string;
|
|
16
|
+
readonly icon?: LucideIcon;
|
|
17
|
+
readonly error?: string | null;
|
|
18
|
+
readonly width?: DialogContentProps["width"];
|
|
19
|
+
readonly children: ReactNode;
|
|
20
|
+
/** Footer actions (Save, …). Cancel is always in the header. */
|
|
21
|
+
readonly footer?: ReactNode;
|
|
22
|
+
readonly testId?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function ItemFormDialog({ open, onClose, title, icon: Icon, error, width, children, footer, testId }: ItemFormDialogProps): JSX.Element | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MediaGrid — responsive grid of media tiles (events, frames, crops).
|
|
3
|
+
* Empty and pending are distinct; never fold unanswered into empty (D315).
|
|
4
|
+
*/
|
|
5
|
+
import type { ReactNode, JSX } from "react";
|
|
6
|
+
export interface MediaCardProps {
|
|
7
|
+
readonly children: ReactNode;
|
|
8
|
+
readonly selected?: boolean;
|
|
9
|
+
readonly onSelect?: () => void;
|
|
10
|
+
readonly label?: string;
|
|
11
|
+
readonly className?: string;
|
|
12
|
+
readonly testId?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function MediaCard({ children, selected, onSelect, label, className, testId }: MediaCardProps): JSX.Element;
|
|
15
|
+
export type MediaGridStatus = "pending" | "ready" | "empty";
|
|
16
|
+
export interface MediaGridProps {
|
|
17
|
+
readonly status: MediaGridStatus;
|
|
18
|
+
/** Required when status is `ready`. */
|
|
19
|
+
readonly children?: ReactNode;
|
|
20
|
+
/** Shown when status is pending. */
|
|
21
|
+
readonly pendingMessage?: string;
|
|
22
|
+
/** Shown when status is empty. */
|
|
23
|
+
readonly emptyMessage?: string;
|
|
24
|
+
readonly className?: string;
|
|
25
|
+
readonly testId?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function MediaGrid({ status, children, pendingMessage, emptyMessage, className, testId }: MediaGridProps): JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nodeId → palette slot for assignment pills, NodePicker and other cluster surfaces.
|
|
3
|
+
* Reads persisted `color` from listAgentSettings; falls back to assignNodeColorId.
|
|
4
|
+
*/
|
|
5
|
+
import { type NodeColorId } from "@camstack/types";
|
|
6
|
+
export interface AgentSettingsColorRow {
|
|
7
|
+
readonly nodeId: string;
|
|
8
|
+
readonly settings: object;
|
|
9
|
+
}
|
|
10
|
+
export declare function buildColorByNodeId(rows: readonly AgentSettingsColorRow[] | undefined): ReadonlyMap<string, NodeColorId>;
|
|
11
|
+
export declare function colorIdForNode(colorByNodeId: ReadonlyMap<string, NodeColorId>, nodeId: string): NodeColorId;
|
|
@@ -2,3 +2,4 @@ import { NODE_COLOR_IDS, assignNodeColorId, type NodeColorId } from "@camstack/t
|
|
|
2
2
|
export { NODE_COLOR_IDS, assignNodeColorId, type NodeColorId };
|
|
3
3
|
export declare function nodeColorClass(id: NodeColorId): string;
|
|
4
4
|
export declare function nodeTextColorClass(id: NodeColorId): string;
|
|
5
|
+
export declare function nodeSoftBgClass(id: NodeColorId): string;
|
|
@@ -12,5 +12,7 @@ export interface NodePickerProps {
|
|
|
12
12
|
* default hides the picker since there's nothing to choose.
|
|
13
13
|
*/
|
|
14
14
|
readonly alwaysShow?: boolean;
|
|
15
|
+
/** When true, offline nodes stay enabled and selectable. Default false. */
|
|
16
|
+
readonly allowOffline?: boolean;
|
|
15
17
|
}
|
|
16
|
-
export declare function NodePicker({ selectedNodeId, onSelect, label, hideIcon, alwaysShow }: NodePickerProps): JSX.Element | null;
|
|
18
|
+
export declare function NodePicker({ selectedNodeId, onSelect, label, hideIcon, alwaysShow, allowOffline }: NodePickerProps): JSX.Element | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PageShell — the one page chrome for admin-ui and addon pages.
|
|
3
|
+
*
|
|
4
|
+
* Gutter `p-2 sm:p-4`, `overflow-x-clip` (never `hidden` — that kills sticky
|
|
5
|
+
* descendants). Optional title row (icon + title + subtitle | actions) and a
|
|
6
|
+
* `nav` slot under it for hub pills / selectors.
|
|
7
|
+
*/
|
|
8
|
+
import type { LucideIcon } from "lucide-react";
|
|
9
|
+
import type { ReactNode, JSX } from "react";
|
|
10
|
+
export interface PageShellProps {
|
|
11
|
+
readonly icon?: LucideIcon;
|
|
12
|
+
readonly title?: string;
|
|
13
|
+
readonly subtitle?: ReactNode;
|
|
14
|
+
readonly actions?: ReactNode;
|
|
15
|
+
/** Selector row inside the header, under the title (hub pills, …). */
|
|
16
|
+
readonly nav?: ReactNode;
|
|
17
|
+
readonly className?: string;
|
|
18
|
+
readonly children: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
export declare function PageShell({ icon: Icon, title, subtitle, actions, nav, className, children }: PageShellProps): JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SectionToolbar — filters / node on the left, Add (or other actions) on the
|
|
3
|
+
* right, inside a SectionCard body. Owns no data.
|
|
4
|
+
*/
|
|
5
|
+
import type { ReactNode, JSX } from "react";
|
|
6
|
+
export interface SectionToolbarProps {
|
|
7
|
+
/** Filters, NodePicker, search, … */
|
|
8
|
+
readonly start?: ReactNode;
|
|
9
|
+
/** Add button or other primary actions. */
|
|
10
|
+
readonly end?: ReactNode;
|
|
11
|
+
readonly className?: string;
|
|
12
|
+
readonly testId?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function SectionToolbar({ start, end, className, testId }: SectionToolbarProps): JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type ItemFormUrlState = {
|
|
2
|
+
readonly mode: "closed";
|
|
3
|
+
} | {
|
|
4
|
+
readonly mode: "new";
|
|
5
|
+
} | {
|
|
6
|
+
readonly mode: "edit";
|
|
7
|
+
readonly id: string;
|
|
8
|
+
};
|
|
9
|
+
export interface UseItemFormUrlOptions {
|
|
10
|
+
/** Query key for create (default `new`). Value is the literal `1` when open. */
|
|
11
|
+
readonly newKey?: string;
|
|
12
|
+
/** Query key for edit id (default `edit`). */
|
|
13
|
+
readonly editKey?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface UseItemFormUrlResult {
|
|
16
|
+
readonly state: ItemFormUrlState;
|
|
17
|
+
readonly openNew: () => void;
|
|
18
|
+
readonly openEdit: (id: string) => void;
|
|
19
|
+
readonly close: () => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function useItemFormUrl(options?: UseItemFormUrlOptions): UseItemFormUrlResult;
|
package/dist/index.cjs
CHANGED
|
@@ -19723,18 +19723,21 @@ function WidgetPanel({ title, icon, headerRight, collapsible = false, defaultOpe
|
|
|
19723
19723
|
}
|
|
19724
19724
|
function WidgetMetricCard({ label, icon, children, footer }) {
|
|
19725
19725
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19726
|
-
className: "rounded-
|
|
19726
|
+
className: "min-w-0 rounded-lg border border-border bg-surface p-2 sm:p-2.5",
|
|
19727
19727
|
children: [
|
|
19728
19728
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19729
|
-
className: "mb-
|
|
19730
|
-
children: [icon,
|
|
19729
|
+
className: "mb-0.5 flex min-w-0 items-center gap-1.5 text-[10px] uppercase tracking-wider text-foreground-subtle",
|
|
19730
|
+
children: [icon, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
19731
|
+
className: "min-w-0 truncate",
|
|
19732
|
+
children: label
|
|
19733
|
+
})]
|
|
19731
19734
|
}),
|
|
19732
19735
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19733
|
-
className: "text-
|
|
19736
|
+
className: "truncate text-lg font-semibold leading-tight text-foreground",
|
|
19734
19737
|
children
|
|
19735
19738
|
}),
|
|
19736
|
-
footer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19737
|
-
className: "mt-0.5 text-[10px] text-foreground-subtle",
|
|
19739
|
+
footer != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19740
|
+
className: "mt-0.5 truncate text-[10px] text-foreground-subtle",
|
|
19738
19741
|
children: footer
|
|
19739
19742
|
}) : null
|
|
19740
19743
|
]
|
|
@@ -25753,7 +25756,9 @@ var contentVariants = cva("relative fixed left-1/2 top-1/2 -translate-x-1/2 -tra
|
|
|
25753
25756
|
width: {
|
|
25754
25757
|
sm: "max-w-sm",
|
|
25755
25758
|
md: "max-w-md",
|
|
25756
|
-
lg: "max-w-lg"
|
|
25759
|
+
lg: "max-w-lg",
|
|
25760
|
+
/** Wide editors (scripts, long forms). */
|
|
25761
|
+
xl: "max-w-4xl"
|
|
25757
25762
|
},
|
|
25758
25763
|
/**
|
|
25759
25764
|
* WHICH box scrolls when the content is taller than the height cap.
|
|
@@ -25800,6 +25805,11 @@ var contentVariants = cva("relative fixed left-1/2 top-1/2 -translate-x-1/2 -tra
|
|
|
25800
25805
|
width: "lg",
|
|
25801
25806
|
class: "md:w-[calc(100vw-2rem)] md:max-w-lg"
|
|
25802
25807
|
},
|
|
25808
|
+
{
|
|
25809
|
+
placement: "sheet-on-mobile",
|
|
25810
|
+
width: "xl",
|
|
25811
|
+
class: "md:w-[calc(100vw-2rem)] md:max-w-4xl"
|
|
25812
|
+
},
|
|
25803
25813
|
{
|
|
25804
25814
|
scroll: "body",
|
|
25805
25815
|
placement: "sheet-on-mobile",
|
|
@@ -30918,6 +30928,8 @@ var GRID_GAP = "gap-2 sm:gap-3";
|
|
|
30918
30928
|
var LIST_ROW = "px-3 sm:px-4 py-1.5 sm:py-2";
|
|
30919
30929
|
/** 1-up at L1, 2-up at L2, 4-up at L3 — quick-stat row. */
|
|
30920
30930
|
var GRID_QUICK_STATS = "grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-4";
|
|
30931
|
+
/** Auto-fill media tiles (events, frames, crops) — ~4 across at lg. */
|
|
30932
|
+
var MEDIA_GRID = "grid grid-cols-[repeat(auto-fill,minmax(11rem,1fr))]";
|
|
30921
30933
|
/** 1-up at L1, 2-up at L2+ — paired panels (motion + detection rules). */
|
|
30922
30934
|
var GRID_PAIRED = "grid grid-cols-1 lg:grid-cols-2";
|
|
30923
30935
|
/** Side-by-side layout that stacks below md (canvas + side panel). */
|
|
@@ -32829,12 +32841,46 @@ var NODE_TEXT_CLASS = {
|
|
|
32829
32841
|
"node-g": "text-node-g",
|
|
32830
32842
|
"node-h": "text-node-h"
|
|
32831
32843
|
};
|
|
32844
|
+
/** Soft fill for selected pills / chrome (Tailwind v4 needs full class strings). */
|
|
32845
|
+
var NODE_BG_SOFT_CLASS$1 = {
|
|
32846
|
+
"node-a": "bg-node-a/15",
|
|
32847
|
+
"node-b": "bg-node-b/15",
|
|
32848
|
+
"node-c": "bg-node-c/15",
|
|
32849
|
+
"node-d": "bg-node-d/15",
|
|
32850
|
+
"node-e": "bg-node-e/15",
|
|
32851
|
+
"node-f": "bg-node-f/15",
|
|
32852
|
+
"node-g": "bg-node-g/15",
|
|
32853
|
+
"node-h": "bg-node-h/15"
|
|
32854
|
+
};
|
|
32832
32855
|
function nodeColorClass(id) {
|
|
32833
32856
|
return NODE_BG_CLASS[id];
|
|
32834
32857
|
}
|
|
32835
32858
|
function nodeTextColorClass(id) {
|
|
32836
32859
|
return NODE_TEXT_CLASS[id];
|
|
32837
32860
|
}
|
|
32861
|
+
function nodeSoftBgClass(id) {
|
|
32862
|
+
return NODE_BG_SOFT_CLASS$1[id];
|
|
32863
|
+
}
|
|
32864
|
+
//#endregion
|
|
32865
|
+
//#region src/composites/node-color-map.ts
|
|
32866
|
+
/**
|
|
32867
|
+
* nodeId → palette slot for assignment pills, NodePicker and other cluster surfaces.
|
|
32868
|
+
* Reads persisted `color` from listAgentSettings; falls back to assignNodeColorId.
|
|
32869
|
+
*/
|
|
32870
|
+
function readSettingsColor(settings) {
|
|
32871
|
+
for (const [key, value] of Object.entries(settings)) if (key === "color") return value;
|
|
32872
|
+
}
|
|
32873
|
+
function buildColorByNodeId(rows) {
|
|
32874
|
+
const map = /* @__PURE__ */ new Map();
|
|
32875
|
+
for (const row of rows ?? []) {
|
|
32876
|
+
const parsed = _camstack_types.NodeColorIdSchema.safeParse(readSettingsColor(row.settings));
|
|
32877
|
+
map.set(row.nodeId, parsed.success ? parsed.data : (0, _camstack_types.assignNodeColorId)(row.nodeId));
|
|
32878
|
+
}
|
|
32879
|
+
return map;
|
|
32880
|
+
}
|
|
32881
|
+
function colorIdForNode(colorByNodeId, nodeId) {
|
|
32882
|
+
return colorByNodeId.get(nodeId) ?? (0, _camstack_types.assignNodeColorId)(nodeId);
|
|
32883
|
+
}
|
|
32838
32884
|
//#endregion
|
|
32839
32885
|
//#region src/composites/role-node-pill.tsx
|
|
32840
32886
|
var ROLE_SOFT_CLASS = {
|
|
@@ -58072,12 +58118,15 @@ function mountAddonPage(PageComponent, options = {}) {
|
|
|
58072
58118
|
*
|
|
58073
58119
|
* Uses `useClusterNodes` hook so every consumer hits the same React-Query
|
|
58074
58120
|
* cache. Renders a `Server` icon + label + segmented pill bar with one
|
|
58075
|
-
* button per
|
|
58121
|
+
* button per node (hub first), tinted from agent-settings palette colours.
|
|
58122
|
+
* Offline nodes stay listed; they are disabled unless `allowOffline`.
|
|
58076
58123
|
*
|
|
58077
58124
|
* Used by admin-ui Addons page, benchmark addon page, pipeline page, etc.
|
|
58078
58125
|
*/
|
|
58079
|
-
function NodePicker({ selectedNodeId, onSelect, label = "Node", hideIcon = false, alwaysShow = false }) {
|
|
58126
|
+
function NodePicker({ selectedNodeId, onSelect, label = "Node", hideIcon = false, alwaysShow = false, allowOffline = false }) {
|
|
58080
58127
|
const { nodes } = useClusterNodes();
|
|
58128
|
+
const settingsQuery = usePipelineOrchestratorListAgentSettings(void 0);
|
|
58129
|
+
const colorByNodeId = (0, react$1.useMemo)(() => buildColorByNodeId(settingsQuery.data), [settingsQuery.data]);
|
|
58081
58130
|
if (!alwaysShow && nodes.length <= 1) return null;
|
|
58082
58131
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58083
58132
|
className: "flex items-center gap-1.5",
|
|
@@ -58091,12 +58140,14 @@ function NodePicker({ selectedNodeId, onSelect, label = "Node", hideIcon = false
|
|
|
58091
58140
|
className: "flex rounded-md border border-border overflow-hidden text-[11px] font-medium",
|
|
58092
58141
|
children: nodes.map((node, idx) => {
|
|
58093
58142
|
const isSelected = selectedNodeId === node.id;
|
|
58143
|
+
const colorId = colorIdForNode(colorByNodeId, node.id);
|
|
58144
|
+
const disabled = !allowOffline && !node.isOnline;
|
|
58094
58145
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
58095
58146
|
type: "button",
|
|
58096
58147
|
onClick: () => onSelect(node.id),
|
|
58097
|
-
disabled
|
|
58148
|
+
disabled,
|
|
58098
58149
|
title: node.isOnline ? node.name : `${node.name} (offline)`,
|
|
58099
|
-
className: cn("px-2.5 py-1 transition-colors", isSelected ?
|
|
58150
|
+
className: cn("px-2.5 py-1 transition-colors", isSelected ? cn(nodeSoftBgClass(colorId), nodeTextColorClass(colorId)) : cn("bg-transparent hover:bg-surface-hover", nodeTextColorClass(colorId), !disabled && "opacity-70 hover:opacity-100"), idx > 0 && "border-l border-border", disabled && "opacity-50 cursor-not-allowed"),
|
|
58100
58151
|
children: node.shortName
|
|
58101
58152
|
}, node.id);
|
|
58102
58153
|
})
|
|
@@ -58108,20 +58159,227 @@ function NodePicker({ selectedNodeId, onSelect, label = "Node", hideIcon = false
|
|
|
58108
58159
|
//#region src/composites/page-header.tsx
|
|
58109
58160
|
function PageHeader({ title, subtitle, actions, className }) {
|
|
58110
58161
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58111
|
-
className: cn("flex flex-col gap-2
|
|
58112
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58113
|
-
className: "
|
|
58114
|
-
children:
|
|
58115
|
-
|
|
58116
|
-
|
|
58117
|
-
|
|
58118
|
-
|
|
58119
|
-
|
|
58162
|
+
className: cn("mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between", className),
|
|
58163
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58164
|
+
className: "min-w-0",
|
|
58165
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
58166
|
+
className: "text-lg font-semibold text-foreground",
|
|
58167
|
+
children: title
|
|
58168
|
+
}), subtitle !== void 0 && subtitle !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
58169
|
+
className: "mt-0.5 text-xs text-foreground-subtle",
|
|
58170
|
+
children: subtitle
|
|
58171
|
+
})]
|
|
58172
|
+
}), actions !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
58173
|
+
className: "flex flex-wrap items-center gap-2",
|
|
58120
58174
|
children: actions
|
|
58121
58175
|
})]
|
|
58122
58176
|
});
|
|
58123
58177
|
}
|
|
58124
58178
|
//#endregion
|
|
58179
|
+
//#region src/composites/page-shell.tsx
|
|
58180
|
+
function PageShell({ icon: Icon, title, subtitle, actions, nav, className, children }) {
|
|
58181
|
+
const hasTitleRow = Boolean(title) || subtitle != null || actions != null;
|
|
58182
|
+
const hasHeader = hasTitleRow || nav != null;
|
|
58183
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58184
|
+
className: cn("space-y-4 overflow-x-clip p-2 sm:p-4", className),
|
|
58185
|
+
children: [hasHeader && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58186
|
+
className: "space-y-3",
|
|
58187
|
+
children: [hasTitleRow && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58188
|
+
className: "flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-4",
|
|
58189
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58190
|
+
className: "min-w-0",
|
|
58191
|
+
children: [title !== void 0 && title !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h1", {
|
|
58192
|
+
className: "flex items-center gap-2 text-lg font-semibold text-foreground",
|
|
58193
|
+
children: [Icon !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
58194
|
+
className: "h-4 w-4 shrink-0 text-foreground-subtle",
|
|
58195
|
+
"aria-hidden": true
|
|
58196
|
+
}), title]
|
|
58197
|
+
}), subtitle != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
58198
|
+
className: "mt-0.5 text-xs text-foreground-subtle",
|
|
58199
|
+
children: subtitle
|
|
58200
|
+
})]
|
|
58201
|
+
}), actions != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
58202
|
+
className: "flex shrink-0 flex-wrap items-center gap-2",
|
|
58203
|
+
children: actions
|
|
58204
|
+
})]
|
|
58205
|
+
}), nav]
|
|
58206
|
+
}), children]
|
|
58207
|
+
});
|
|
58208
|
+
}
|
|
58209
|
+
//#endregion
|
|
58210
|
+
//#region src/composites/section-toolbar.tsx
|
|
58211
|
+
function SectionToolbar({ start, end, className, testId }) {
|
|
58212
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58213
|
+
className: cn("flex flex-col gap-2 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between", className),
|
|
58214
|
+
"data-testid": testId,
|
|
58215
|
+
children: [start != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
58216
|
+
className: "flex min-w-0 flex-1 flex-wrap items-center gap-2",
|
|
58217
|
+
children: start
|
|
58218
|
+
}), end != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
58219
|
+
className: "flex shrink-0 flex-wrap items-center gap-2 sm:justify-end",
|
|
58220
|
+
children: end
|
|
58221
|
+
})]
|
|
58222
|
+
});
|
|
58223
|
+
}
|
|
58224
|
+
//#endregion
|
|
58225
|
+
//#region src/composites/item-form-dialog.tsx
|
|
58226
|
+
function ItemFormDialog({ open, onClose, title, icon: Icon, error, width = "lg", children, footer, testId }) {
|
|
58227
|
+
if (!open) return null;
|
|
58228
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
|
|
58229
|
+
open: true,
|
|
58230
|
+
onOpenChange: (next) => !next && onClose(),
|
|
58231
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogContent, {
|
|
58232
|
+
width,
|
|
58233
|
+
placement: "sheet-on-mobile",
|
|
58234
|
+
scroll: "body",
|
|
58235
|
+
"data-testid": testId,
|
|
58236
|
+
children: [
|
|
58237
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58238
|
+
className: "mb-3 flex shrink-0 items-center justify-between gap-2",
|
|
58239
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
|
|
58240
|
+
className: "flex min-w-0 items-center gap-2 text-sm font-semibold text-foreground",
|
|
58241
|
+
children: [Icon !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
58242
|
+
className: "h-4 w-4 shrink-0 text-foreground-subtle",
|
|
58243
|
+
"aria-hidden": true
|
|
58244
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
58245
|
+
className: "truncate",
|
|
58246
|
+
children: title
|
|
58247
|
+
})]
|
|
58248
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
58249
|
+
size: "sm",
|
|
58250
|
+
variant: "ghost",
|
|
58251
|
+
type: "button",
|
|
58252
|
+
onClick: onClose,
|
|
58253
|
+
children: "Cancel"
|
|
58254
|
+
})]
|
|
58255
|
+
}),
|
|
58256
|
+
error != null && error !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
58257
|
+
className: "mb-3 shrink-0 rounded-md border border-danger/40 bg-danger/10 px-3 py-2 text-xs text-danger",
|
|
58258
|
+
children: error
|
|
58259
|
+
}),
|
|
58260
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogBody, {
|
|
58261
|
+
className: "pr-1",
|
|
58262
|
+
children
|
|
58263
|
+
}),
|
|
58264
|
+
footer != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogFooter, {
|
|
58265
|
+
className: "mt-3 shrink-0",
|
|
58266
|
+
children: footer
|
|
58267
|
+
})
|
|
58268
|
+
]
|
|
58269
|
+
})
|
|
58270
|
+
});
|
|
58271
|
+
}
|
|
58272
|
+
//#endregion
|
|
58273
|
+
//#region src/hooks/use-url-query.ts
|
|
58274
|
+
/**
|
|
58275
|
+
* Tiny URL query sync for surfaces that cannot assume react-router
|
|
58276
|
+
* (federated addon pages) or that only need a handful of params.
|
|
58277
|
+
*
|
|
58278
|
+
* Reads on mount + `popstate`; writes with `history.replaceState`. Empty /
|
|
58279
|
+
* null values are deleted so the URL stays short.
|
|
58280
|
+
*/
|
|
58281
|
+
function readParam(key) {
|
|
58282
|
+
return new URLSearchParams(window.location.search).get(key);
|
|
58283
|
+
}
|
|
58284
|
+
function writeParam(key, value) {
|
|
58285
|
+
const params = new URLSearchParams(window.location.search);
|
|
58286
|
+
if (value === null || value === "") params.delete(key);
|
|
58287
|
+
else params.set(key, value);
|
|
58288
|
+
const qs = params.toString();
|
|
58289
|
+
const url = qs ? `${window.location.pathname}?${qs}` : window.location.pathname;
|
|
58290
|
+
window.history.replaceState(null, "", url);
|
|
58291
|
+
}
|
|
58292
|
+
/**
|
|
58293
|
+
* One query param as React state. `null` means absent.
|
|
58294
|
+
* Changing it replaces the URL without a navigation.
|
|
58295
|
+
*/
|
|
58296
|
+
function useUrlQueryParam(key) {
|
|
58297
|
+
const [value, setValue] = (0, react$1.useState)(() => readParam(key));
|
|
58298
|
+
(0, react$1.useEffect)(() => {
|
|
58299
|
+
const onPop = () => setValue(readParam(key));
|
|
58300
|
+
window.addEventListener("popstate", onPop);
|
|
58301
|
+
return () => window.removeEventListener("popstate", onPop);
|
|
58302
|
+
}, [key]);
|
|
58303
|
+
return [value, (0, react$1.useCallback)((next) => {
|
|
58304
|
+
writeParam(key, next);
|
|
58305
|
+
setValue(next);
|
|
58306
|
+
}, [key])];
|
|
58307
|
+
}
|
|
58308
|
+
//#endregion
|
|
58309
|
+
//#region src/composites/use-item-form-url.ts
|
|
58310
|
+
/**
|
|
58311
|
+
* URL ownership for create/edit modals: `?new=1` or `?edit=<id>` (replace).
|
|
58312
|
+
* Mutually exclusive — opening one clears the other.
|
|
58313
|
+
*/
|
|
58314
|
+
function useItemFormUrl(options = {}) {
|
|
58315
|
+
const newKey = options.newKey ?? "new";
|
|
58316
|
+
const editKey = options.editKey ?? "edit";
|
|
58317
|
+
const [newParam, setNewParam] = useUrlQueryParam(newKey);
|
|
58318
|
+
const [editParam, setEditParam] = useUrlQueryParam(editKey);
|
|
58319
|
+
return {
|
|
58320
|
+
state: (0, react$1.useMemo)(() => {
|
|
58321
|
+
if (editParam !== null && editParam !== "") return {
|
|
58322
|
+
mode: "edit",
|
|
58323
|
+
id: editParam
|
|
58324
|
+
};
|
|
58325
|
+
if (newParam === "1") return { mode: "new" };
|
|
58326
|
+
return { mode: "closed" };
|
|
58327
|
+
}, [editParam, newParam]),
|
|
58328
|
+
openNew: (0, react$1.useCallback)(() => {
|
|
58329
|
+
setEditParam(null);
|
|
58330
|
+
setNewParam("1");
|
|
58331
|
+
}, [setEditParam, setNewParam]),
|
|
58332
|
+
openEdit: (0, react$1.useCallback)((id) => {
|
|
58333
|
+
setNewParam(null);
|
|
58334
|
+
setEditParam(id);
|
|
58335
|
+
}, [setEditParam, setNewParam]),
|
|
58336
|
+
close: (0, react$1.useCallback)(() => {
|
|
58337
|
+
setNewParam(null);
|
|
58338
|
+
setEditParam(null);
|
|
58339
|
+
}, [setEditParam, setNewParam])
|
|
58340
|
+
};
|
|
58341
|
+
}
|
|
58342
|
+
//#endregion
|
|
58343
|
+
//#region src/composites/media-grid.tsx
|
|
58344
|
+
function MediaCard({ children, selected = false, onSelect, label, className, testId }) {
|
|
58345
|
+
const interactive = onSelect !== void 0;
|
|
58346
|
+
const onKeyDown = (event) => {
|
|
58347
|
+
if (!interactive) return;
|
|
58348
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
58349
|
+
event.preventDefault();
|
|
58350
|
+
onSelect();
|
|
58351
|
+
}
|
|
58352
|
+
};
|
|
58353
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
58354
|
+
role: interactive ? "button" : void 0,
|
|
58355
|
+
tabIndex: interactive ? 0 : void 0,
|
|
58356
|
+
"aria-pressed": interactive ? selected : void 0,
|
|
58357
|
+
"aria-label": label,
|
|
58358
|
+
"data-testid": testId,
|
|
58359
|
+
onClick: onSelect,
|
|
58360
|
+
onKeyDown,
|
|
58361
|
+
className: cn("relative aspect-square min-w-0 overflow-hidden rounded-md border bg-surface", selected ? "border-primary ring-1 ring-primary" : "border-border", interactive && "cursor-pointer hover:border-foreground-subtle", className),
|
|
58362
|
+
children
|
|
58363
|
+
});
|
|
58364
|
+
}
|
|
58365
|
+
function MediaGrid({ status, children, pendingMessage = "Loading…", emptyMessage = "Nothing to show", className, testId }) {
|
|
58366
|
+
if (status === "pending") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
58367
|
+
className: "animate-pulse py-8 text-center text-xs text-foreground-subtle",
|
|
58368
|
+
"data-testid": testId,
|
|
58369
|
+
children: pendingMessage
|
|
58370
|
+
});
|
|
58371
|
+
if (status === "empty") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
58372
|
+
className: "py-8 text-center text-xs text-foreground-subtle",
|
|
58373
|
+
"data-testid": testId,
|
|
58374
|
+
children: emptyMessage
|
|
58375
|
+
});
|
|
58376
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
58377
|
+
className: cn(MEDIA_GRID, GRID_GAP, className),
|
|
58378
|
+
"data-testid": testId,
|
|
58379
|
+
children
|
|
58380
|
+
});
|
|
58381
|
+
}
|
|
58382
|
+
//#endregion
|
|
58125
58383
|
//#region src/composites/phase-icon.tsx
|
|
58126
58384
|
/**
|
|
58127
58385
|
* PhaseIcon — renders the correct lucide icon for a CameraPhase.
|
|
@@ -62065,42 +62323,6 @@ function useZoneEditing() {
|
|
|
62065
62323
|
return (0, react$1.useContext)(ZoneEditingContext);
|
|
62066
62324
|
}
|
|
62067
62325
|
//#endregion
|
|
62068
|
-
//#region src/hooks/use-url-query.ts
|
|
62069
|
-
/**
|
|
62070
|
-
* Tiny URL query sync for surfaces that cannot assume react-router
|
|
62071
|
-
* (federated addon pages) or that only need a handful of params.
|
|
62072
|
-
*
|
|
62073
|
-
* Reads on mount + `popstate`; writes with `history.replaceState`. Empty /
|
|
62074
|
-
* null values are deleted so the URL stays short.
|
|
62075
|
-
*/
|
|
62076
|
-
function readParam(key) {
|
|
62077
|
-
return new URLSearchParams(window.location.search).get(key);
|
|
62078
|
-
}
|
|
62079
|
-
function writeParam(key, value) {
|
|
62080
|
-
const params = new URLSearchParams(window.location.search);
|
|
62081
|
-
if (value === null || value === "") params.delete(key);
|
|
62082
|
-
else params.set(key, value);
|
|
62083
|
-
const qs = params.toString();
|
|
62084
|
-
const url = qs ? `${window.location.pathname}?${qs}` : window.location.pathname;
|
|
62085
|
-
window.history.replaceState(null, "", url);
|
|
62086
|
-
}
|
|
62087
|
-
/**
|
|
62088
|
-
* One query param as React state. `null` means absent.
|
|
62089
|
-
* Changing it replaces the URL without a navigation.
|
|
62090
|
-
*/
|
|
62091
|
-
function useUrlQueryParam(key) {
|
|
62092
|
-
const [value, setValue] = (0, react$1.useState)(() => readParam(key));
|
|
62093
|
-
(0, react$1.useEffect)(() => {
|
|
62094
|
-
const onPop = () => setValue(readParam(key));
|
|
62095
|
-
window.addEventListener("popstate", onPop);
|
|
62096
|
-
return () => window.removeEventListener("popstate", onPop);
|
|
62097
|
-
}, [key]);
|
|
62098
|
-
return [value, (0, react$1.useCallback)((next) => {
|
|
62099
|
-
writeParam(key, next);
|
|
62100
|
-
setValue(next);
|
|
62101
|
-
}, [key])];
|
|
62102
|
-
}
|
|
62103
|
-
//#endregion
|
|
62104
62326
|
//#region src/hooks/use-device-cap-slice.ts
|
|
62105
62327
|
/**
|
|
62106
62328
|
* useDeviceCapSlice — generic runtime-state slice reader for an arbitrary
|
|
@@ -62802,6 +63024,7 @@ exports.ImageOff = ImageOff;
|
|
|
62802
63024
|
exports.ImageSelector = ImageSelector;
|
|
62803
63025
|
exports.InferenceConfigSelector = InferenceConfigSelector;
|
|
62804
63026
|
exports.Input = Input;
|
|
63027
|
+
exports.ItemFormDialog = ItemFormDialog;
|
|
62805
63028
|
exports.KebabMenu = KebabMenu;
|
|
62806
63029
|
exports.KeyValueList = KeyValueList;
|
|
62807
63030
|
exports.LIST_ROW = LIST_ROW;
|
|
@@ -62816,8 +63039,11 @@ exports.LockInlineControl = LockInlineControl;
|
|
|
62816
63039
|
exports.LockPanel = LockPanel;
|
|
62817
63040
|
exports.LogStream = LogStream;
|
|
62818
63041
|
exports.LoginForm = LoginForm;
|
|
63042
|
+
exports.MEDIA_GRID = MEDIA_GRID;
|
|
62819
63043
|
exports.MODE_COLOR = MODE_COLOR;
|
|
62820
63044
|
exports.MaskShapeCanvas = MaskShapeCanvas;
|
|
63045
|
+
exports.MediaCard = MediaCard;
|
|
63046
|
+
exports.MediaGrid = MediaGrid;
|
|
62821
63047
|
exports.MediaPlayerHeroCard = MediaPlayerHeroCard;
|
|
62822
63048
|
exports.MediaPlayerInlineControl = MediaPlayerInlineControl;
|
|
62823
63049
|
exports.MediaPlayerPanel = MediaPlayerPanel;
|
|
@@ -62854,6 +63080,7 @@ exports.PICKER_WINDOW = PICKER_WINDOW;
|
|
|
62854
63080
|
exports.PRIORITY = PRIORITY;
|
|
62855
63081
|
exports.PTZOverlay = PTZOverlay;
|
|
62856
63082
|
exports.PageHeader = PageHeader;
|
|
63083
|
+
exports.PageShell = PageShell;
|
|
62857
63084
|
exports.PageSizeSelect = PageSizeSelect;
|
|
62858
63085
|
exports.Pagination = Pagination;
|
|
62859
63086
|
exports.PasswordInput = PasswordInput;
|
|
@@ -62916,6 +63143,7 @@ exports.ScrollArea = ScrollArea;
|
|
|
62916
63143
|
exports.SecretFieldControl = SecretFieldControl;
|
|
62917
63144
|
exports.SecretRevealProvider = SecretRevealProvider;
|
|
62918
63145
|
exports.SectionCard = SectionCard;
|
|
63146
|
+
exports.SectionToolbar = SectionToolbar;
|
|
62919
63147
|
exports.SegmentedChoice = SegmentedChoice;
|
|
62920
63148
|
exports.Select = Select;
|
|
62921
63149
|
exports.SelectedChip = SelectedChip;
|
|
@@ -62999,6 +63227,7 @@ exports.assignNodeColorId = _camstack_types.assignNodeColorId;
|
|
|
62999
63227
|
exports.badgeFor = badgeFor;
|
|
63000
63228
|
exports.badgeFor$1 = badgeFor;
|
|
63001
63229
|
exports.bindsToModel = bindsToModel;
|
|
63230
|
+
exports.buildColorByNodeId = buildColorByNodeId;
|
|
63002
63231
|
exports.buildPeerConnectionConfig = buildPeerConnectionConfig;
|
|
63003
63232
|
exports.buildStepTreeFromSchema = buildStepTreeFromSchema;
|
|
63004
63233
|
exports.catalogHasDraft = catalogHasDraft;
|
|
@@ -63011,6 +63240,7 @@ exports.classifySessionVerdict = classifySessionVerdict;
|
|
|
63011
63240
|
exports.clearPickerGroup = clearPickerGroup;
|
|
63012
63241
|
exports.clusterRootModelIds = clusterRootModelIds;
|
|
63013
63242
|
exports.cn = cn;
|
|
63243
|
+
exports.colorIdForNode = colorIdForNode;
|
|
63014
63244
|
exports.columnsForContext = columnsForContext;
|
|
63015
63245
|
exports.compareSrflxMappings = compareSrflxMappings;
|
|
63016
63246
|
exports.computePageButtons = computePageButtons;
|
|
@@ -63103,6 +63333,7 @@ exports.nextSort = nextSort;
|
|
|
63103
63333
|
exports.nextSortDirection = nextSortDirection;
|
|
63104
63334
|
exports.nextTableSort = nextTableSort;
|
|
63105
63335
|
exports.nodeColorClass = nodeColorClass;
|
|
63336
|
+
exports.nodeSoftBgClass = nodeSoftBgClass;
|
|
63106
63337
|
exports.nodeTextColorClass = nodeTextColorClass;
|
|
63107
63338
|
exports.normalizeForSearch = normalizeForSearch;
|
|
63108
63339
|
exports.offListSelections = offListSelections;
|
|
@@ -63625,6 +63856,7 @@ exports.useIntercomStartTalkSession = useIntercomStartTalkSession;
|
|
|
63625
63856
|
exports.useIntercomStopSession = useIntercomStopSession;
|
|
63626
63857
|
exports.useIsMidWidth = useIsMidWidth;
|
|
63627
63858
|
exports.useIsMobile = useIsMobile;
|
|
63859
|
+
exports.useItemFormUrl = useItemFormUrl;
|
|
63628
63860
|
exports.useKeychainAdminAcknowledgeReset = useKeychainAdminAcknowledgeReset;
|
|
63629
63861
|
exports.useKeychainAdminDeleteOrphans = useKeychainAdminDeleteOrphans;
|
|
63630
63862
|
exports.useKeychainAdminDownloadRecoveryKey = useKeychainAdminDownloadRecoveryKey;
|