@camstack/ui-library 1.2.179 → 1.2.180

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.
@@ -32,13 +32,17 @@ export interface GridProfileOffer {
32
32
  readonly profile: 'high' | 'mid' | 'low';
33
33
  readonly offered: boolean;
34
34
  readonly missingSources: readonly number[];
35
+ /**
36
+ * The canvas this profile composes onto, `WxH`, or empty when it is not
37
+ * offered. DERIVED — the operator no longer types a resolution, so this is
38
+ * the only place the grid's actual size is ever stated.
39
+ */
40
+ readonly canvas: string;
35
41
  }
36
42
  export interface GridLayoutView {
37
43
  readonly instanceId: string;
38
44
  readonly deviceId: number;
39
45
  readonly name: string;
40
- readonly width: number;
41
- readonly height: number;
42
46
  readonly fps: number;
43
47
  readonly cells: readonly GridLayoutCell[];
44
48
  readonly profiles: readonly GridProfileOffer[];
package/dist/index.cjs CHANGED
@@ -16641,7 +16641,8 @@ function readProfileOffer(value) {
16641
16641
  return {
16642
16642
  profile,
16643
16643
  offered,
16644
- missingSources
16644
+ missingSources,
16645
+ canvas: typeof value["canvas"] === "string" ? value["canvas"] : ""
16645
16646
  };
16646
16647
  }
16647
16648
  /**
@@ -16655,8 +16656,8 @@ function readProfileOffer(value) {
16655
16656
  function parseGridView(raw) {
16656
16657
  if (raw === null) return null;
16657
16658
  if (!isRecord(raw)) return "unreadable";
16658
- const { instanceId, deviceId, name, width, height, fps, cells, profiles } = raw;
16659
- if (typeof instanceId !== "string" || typeof deviceId !== "number" || typeof name !== "string" || typeof width !== "number" || typeof height !== "number" || typeof fps !== "number" || !Array.isArray(cells) || !Array.isArray(profiles)) return "unreadable";
16659
+ const { instanceId, deviceId, name, fps, cells, profiles } = raw;
16660
+ if (typeof instanceId !== "string" || typeof deviceId !== "number" || typeof name !== "string" || typeof fps !== "number" || !Array.isArray(cells) || !Array.isArray(profiles)) return "unreadable";
16660
16661
  const parsed = [];
16661
16662
  for (const entry of cells) {
16662
16663
  const cell = readCell(entry);
@@ -16673,8 +16674,6 @@ function parseGridView(raw) {
16673
16674
  instanceId,
16674
16675
  deviceId,
16675
16676
  name,
16676
- width,
16677
- height,
16678
16677
  fps,
16679
16678
  cells: parsed,
16680
16679
  profiles: offers
@@ -16896,8 +16895,6 @@ function CameraGridLayoutEditor({ deviceId }) {
16896
16895
  try {
16897
16896
  const outcome = parseGridView(await layout.saveLayout({
16898
16897
  name: draft.name,
16899
- width: draft.width,
16900
- height: draft.height,
16901
16898
  fps: draft.fps,
16902
16899
  cells: draft.cells.map((cell) => ({ ...cell }))
16903
16900
  }));
@@ -16920,7 +16917,27 @@ function CameraGridLayoutEditor({ deviceId }) {
16920
16917
  } : cell)
16921
16918
  });
16922
16919
  }, []);
16923
- const canvasAspect = (0, react$1.useMemo)(() => draft === null ? 16 / 9 : draft.width / draft.height, [draft]);
16920
+ /**
16921
+ * The shape the grid will actually come out as.
16922
+ *
16923
+ * The canvas is no longer a field: each profile derives its own from the
16924
+ * cells and the sources' dimensions, and the server reports what it came out
16925
+ * as. Drawing at that aspect is the only way the editor shows the truth —
16926
+ * before, it drew at whatever two numbers had been typed, which is exactly
16927
+ * how a layout could look right here and wrong on screen.
16928
+ *
16929
+ * 16:9 while nothing is offered yet: a grid with no cells has no shape, and
16930
+ * an empty editor has to be drawn at something.
16931
+ */
16932
+ const canvasAspect = (0, react$1.useMemo)(() => {
16933
+ const offered = loaded === void 0 || loaded === null || loaded === "unreadable" ? [] : loaded.profiles;
16934
+ for (const row of offered) {
16935
+ if (!row.offered) continue;
16936
+ const [width, height] = row.canvas.split("x").map(Number);
16937
+ if (width !== void 0 && height !== void 0 && width > 0 && height > 0) return width / height;
16938
+ }
16939
+ return 16 / 9;
16940
+ }, [loaded]);
16924
16941
  if (loaded === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetPanel, {
16925
16942
  title: "Grid layout",
16926
16943
  icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Grid2x2, { className: "h-3.5 w-3.5" }),
@@ -17031,62 +17048,39 @@ function CameraGridLayoutEditor({ deviceId }) {
17031
17048
  }),
17032
17049
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17033
17050
  className: "grid grid-cols-4 gap-2 text-[10.5px]",
17034
- children: [
17035
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LabelledNumber, {
17036
- label: "Width",
17037
- value: draft.width,
17038
- onChange: (width) => {
17039
- setDraft({
17040
- ...draft,
17041
- width
17042
- });
17043
- }
17044
- }),
17045
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LabelledNumber, {
17046
- label: "Height",
17047
- value: draft.height,
17048
- onChange: (height) => {
17049
- setDraft({
17050
- ...draft,
17051
- height
17052
- });
17053
- }
17054
- }),
17055
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LabelledNumber, {
17056
- label: "FPS",
17057
- value: draft.fps,
17058
- onChange: (fps) => {
17059
- setDraft({
17060
- ...draft,
17061
- fps
17062
- });
17063
- }
17064
- }),
17065
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
17066
- type: "button",
17067
- disabled: draft.cells.length >= MAX_CELLS,
17068
- onClick: () => {
17069
- const first = cameras.find((camera) => camera.id !== deviceId);
17070
- if (!first) return;
17071
- setDraft({
17072
- ...draft,
17073
- cells: [...draft.cells, {
17074
- deviceId: first.id,
17075
- source: FULL_FRAME,
17076
- cell: clampNormalizedRect({
17077
- x: .1,
17078
- y: .1,
17079
- width: .4,
17080
- height: .4
17081
- })
17082
- }]
17083
- });
17084
- setSelected(draft.cells.length);
17085
- },
17086
- className: "flex items-center justify-center gap-1 self-end rounded border border-border px-2 py-1 uppercase",
17087
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, { className: "h-3 w-3" }), " Cell"]
17088
- })
17089
- ]
17051
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LabelledNumber, {
17052
+ label: "FPS",
17053
+ value: draft.fps,
17054
+ onChange: (fps) => {
17055
+ setDraft({
17056
+ ...draft,
17057
+ fps
17058
+ });
17059
+ }
17060
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
17061
+ type: "button",
17062
+ disabled: draft.cells.length >= MAX_CELLS,
17063
+ onClick: () => {
17064
+ const first = cameras.find((camera) => camera.id !== deviceId);
17065
+ if (!first) return;
17066
+ setDraft({
17067
+ ...draft,
17068
+ cells: [...draft.cells, {
17069
+ deviceId: first.id,
17070
+ source: FULL_FRAME,
17071
+ cell: clampNormalizedRect({
17072
+ x: .1,
17073
+ y: .1,
17074
+ width: .4,
17075
+ height: .4
17076
+ })
17077
+ }]
17078
+ });
17079
+ setSelected(draft.cells.length);
17080
+ },
17081
+ className: "flex items-center justify-center gap-1 self-end rounded border border-border px-2 py-1 uppercase",
17082
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, { className: "h-3 w-3" }), " Cell"]
17083
+ })]
17090
17084
  }),
17091
17085
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GridProfileReport, {
17092
17086
  offers: loaded.profiles,
@@ -17134,7 +17128,11 @@ function GridProfileReport({ offers, cameras }) {
17134
17128
  children: offer.profile
17135
17129
  }), offer.offered ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
17136
17130
  className: "text-foreground-subtle",
17137
- children: ["composed from every source’s ", offer.profile]
17131
+ children: [
17132
+ offer.canvas,
17133
+ " — composed from every source’s ",
17134
+ offer.profile
17135
+ ]
17138
17136
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
17139
17137
  className: "text-danger",
17140
17138
  children: offer.missingSources.length === 0 ? "not offered — this grid has no cells yet" : `not offered — no ${offer.profile} on ${offer.missingSources.map(nameOf).join(", ")}`
package/dist/index.js CHANGED
@@ -16617,7 +16617,8 @@ function readProfileOffer(value) {
16617
16617
  return {
16618
16618
  profile,
16619
16619
  offered,
16620
- missingSources
16620
+ missingSources,
16621
+ canvas: typeof value["canvas"] === "string" ? value["canvas"] : ""
16621
16622
  };
16622
16623
  }
16623
16624
  /**
@@ -16631,8 +16632,8 @@ function readProfileOffer(value) {
16631
16632
  function parseGridView(raw) {
16632
16633
  if (raw === null) return null;
16633
16634
  if (!isRecord(raw)) return "unreadable";
16634
- const { instanceId, deviceId, name, width, height, fps, cells, profiles } = raw;
16635
- if (typeof instanceId !== "string" || typeof deviceId !== "number" || typeof name !== "string" || typeof width !== "number" || typeof height !== "number" || typeof fps !== "number" || !Array.isArray(cells) || !Array.isArray(profiles)) return "unreadable";
16635
+ const { instanceId, deviceId, name, fps, cells, profiles } = raw;
16636
+ if (typeof instanceId !== "string" || typeof deviceId !== "number" || typeof name !== "string" || typeof fps !== "number" || !Array.isArray(cells) || !Array.isArray(profiles)) return "unreadable";
16636
16637
  const parsed = [];
16637
16638
  for (const entry of cells) {
16638
16639
  const cell = readCell(entry);
@@ -16649,8 +16650,6 @@ function parseGridView(raw) {
16649
16650
  instanceId,
16650
16651
  deviceId,
16651
16652
  name,
16652
- width,
16653
- height,
16654
16653
  fps,
16655
16654
  cells: parsed,
16656
16655
  profiles: offers
@@ -16872,8 +16871,6 @@ function CameraGridLayoutEditor({ deviceId }) {
16872
16871
  try {
16873
16872
  const outcome = parseGridView(await layout.saveLayout({
16874
16873
  name: draft.name,
16875
- width: draft.width,
16876
- height: draft.height,
16877
16874
  fps: draft.fps,
16878
16875
  cells: draft.cells.map((cell) => ({ ...cell }))
16879
16876
  }));
@@ -16896,7 +16893,27 @@ function CameraGridLayoutEditor({ deviceId }) {
16896
16893
  } : cell)
16897
16894
  });
16898
16895
  }, []);
16899
- const canvasAspect = useMemo(() => draft === null ? 16 / 9 : draft.width / draft.height, [draft]);
16896
+ /**
16897
+ * The shape the grid will actually come out as.
16898
+ *
16899
+ * The canvas is no longer a field: each profile derives its own from the
16900
+ * cells and the sources' dimensions, and the server reports what it came out
16901
+ * as. Drawing at that aspect is the only way the editor shows the truth —
16902
+ * before, it drew at whatever two numbers had been typed, which is exactly
16903
+ * how a layout could look right here and wrong on screen.
16904
+ *
16905
+ * 16:9 while nothing is offered yet: a grid with no cells has no shape, and
16906
+ * an empty editor has to be drawn at something.
16907
+ */
16908
+ const canvasAspect = useMemo(() => {
16909
+ const offered = loaded === void 0 || loaded === null || loaded === "unreadable" ? [] : loaded.profiles;
16910
+ for (const row of offered) {
16911
+ if (!row.offered) continue;
16912
+ const [width, height] = row.canvas.split("x").map(Number);
16913
+ if (width !== void 0 && height !== void 0 && width > 0 && height > 0) return width / height;
16914
+ }
16915
+ return 16 / 9;
16916
+ }, [loaded]);
16900
16917
  if (loaded === void 0) return /* @__PURE__ */ jsx(WidgetPanel, {
16901
16918
  title: "Grid layout",
16902
16919
  icon: /* @__PURE__ */ jsx(Grid2x2, { className: "h-3.5 w-3.5" }),
@@ -17007,62 +17024,39 @@ function CameraGridLayoutEditor({ deviceId }) {
17007
17024
  }),
17008
17025
  /* @__PURE__ */ jsxs("div", {
17009
17026
  className: "grid grid-cols-4 gap-2 text-[10.5px]",
17010
- children: [
17011
- /* @__PURE__ */ jsx(LabelledNumber, {
17012
- label: "Width",
17013
- value: draft.width,
17014
- onChange: (width) => {
17015
- setDraft({
17016
- ...draft,
17017
- width
17018
- });
17019
- }
17020
- }),
17021
- /* @__PURE__ */ jsx(LabelledNumber, {
17022
- label: "Height",
17023
- value: draft.height,
17024
- onChange: (height) => {
17025
- setDraft({
17026
- ...draft,
17027
- height
17028
- });
17029
- }
17030
- }),
17031
- /* @__PURE__ */ jsx(LabelledNumber, {
17032
- label: "FPS",
17033
- value: draft.fps,
17034
- onChange: (fps) => {
17035
- setDraft({
17036
- ...draft,
17037
- fps
17038
- });
17039
- }
17040
- }),
17041
- /* @__PURE__ */ jsxs("button", {
17042
- type: "button",
17043
- disabled: draft.cells.length >= MAX_CELLS,
17044
- onClick: () => {
17045
- const first = cameras.find((camera) => camera.id !== deviceId);
17046
- if (!first) return;
17047
- setDraft({
17048
- ...draft,
17049
- cells: [...draft.cells, {
17050
- deviceId: first.id,
17051
- source: FULL_FRAME,
17052
- cell: clampNormalizedRect({
17053
- x: .1,
17054
- y: .1,
17055
- width: .4,
17056
- height: .4
17057
- })
17058
- }]
17059
- });
17060
- setSelected(draft.cells.length);
17061
- },
17062
- className: "flex items-center justify-center gap-1 self-end rounded border border-border px-2 py-1 uppercase",
17063
- children: [/* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }), " Cell"]
17064
- })
17065
- ]
17027
+ children: [/* @__PURE__ */ jsx(LabelledNumber, {
17028
+ label: "FPS",
17029
+ value: draft.fps,
17030
+ onChange: (fps) => {
17031
+ setDraft({
17032
+ ...draft,
17033
+ fps
17034
+ });
17035
+ }
17036
+ }), /* @__PURE__ */ jsxs("button", {
17037
+ type: "button",
17038
+ disabled: draft.cells.length >= MAX_CELLS,
17039
+ onClick: () => {
17040
+ const first = cameras.find((camera) => camera.id !== deviceId);
17041
+ if (!first) return;
17042
+ setDraft({
17043
+ ...draft,
17044
+ cells: [...draft.cells, {
17045
+ deviceId: first.id,
17046
+ source: FULL_FRAME,
17047
+ cell: clampNormalizedRect({
17048
+ x: .1,
17049
+ y: .1,
17050
+ width: .4,
17051
+ height: .4
17052
+ })
17053
+ }]
17054
+ });
17055
+ setSelected(draft.cells.length);
17056
+ },
17057
+ className: "flex items-center justify-center gap-1 self-end rounded border border-border px-2 py-1 uppercase",
17058
+ children: [/* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }), " Cell"]
17059
+ })]
17066
17060
  }),
17067
17061
  /* @__PURE__ */ jsx(GridProfileReport, {
17068
17062
  offers: loaded.profiles,
@@ -17110,7 +17104,11 @@ function GridProfileReport({ offers, cameras }) {
17110
17104
  children: offer.profile
17111
17105
  }), offer.offered ? /* @__PURE__ */ jsxs("span", {
17112
17106
  className: "text-foreground-subtle",
17113
- children: ["composed from every source’s ", offer.profile]
17107
+ children: [
17108
+ offer.canvas,
17109
+ " — composed from every source’s ",
17110
+ offer.profile
17111
+ ]
17114
17112
  }) : /* @__PURE__ */ jsx("span", {
17115
17113
  className: "text-danger",
17116
17114
  children: offer.missingSources.length === 0 ? "not offered — this grid has no cells yet" : `not offered — no ${offer.profile} on ${offer.missingSources.map(nameOf).join(", ")}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/ui-library",
3
- "version": "1.2.179",
3
+ "version": "1.2.180",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",