@camstack/addon-osd-manager 0.1.157 → 0.1.161

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.
Files changed (17) hide show
  1. package/dist/{MotionZonesSettings-vYt4dibj.mjs → MotionZonesSettings-CSheC3_V.mjs} +2 -2
  2. package/dist/{PrivacyMaskSettings-BiQ78hst.mjs → PrivacyMaskSettings-I6eE4GFM.mjs} +4 -4
  3. package/dist/{SceneMonitorEditor-CJtJIyMf.mjs → SceneMonitorEditor-mNJ89n0T.mjs} +3 -3
  4. package/dist/_stub.js +3059 -2713
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-DdAtpC7z.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-BYQUZdGL.mjs} +2 -2
  6. package/dist/{_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BtgV-Ym5.mjs → _virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B2mEdhqd.mjs} +1 -1
  7. package/dist/{hostInit-0Mc7Fm1R.mjs → hostInit-DFyjx8Xk.mjs} +1 -1
  8. package/dist/index.js +350 -57
  9. package/dist/index.mjs +322 -58
  10. package/dist/{player-overlays-BPyna-eI.mjs → player-overlays-DFXmMB9Y.mjs} +1 -1
  11. package/dist/remoteEntry.js +1 -1
  12. package/dist/{responsive-B0yDP9vf.mjs → responsive-DLFs-5i_.mjs} +1 -1
  13. package/dist/{scene-monitor-copy-BzX20O6q.mjs → scene-monitor-copy-DVNcikwa.mjs} +1 -1
  14. package/dist/{square-BOMOn_6j.mjs → square-Co3Eglp3.mjs} +1 -1
  15. package/dist/{trash-2-BolGRl2P.mjs → trash-2-DnKldIWK.mjs} +1 -1
  16. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-UY9P_4V2.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-C3yC0JaQ.mjs} +1 -1
  17. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -39331,22 +39331,35 @@ setOverlay: method(object({
39331
39331
  }
39332
39332
  }] }
39333
39333
  };
39334
+ _enum([
39335
+ "value",
39336
+ "unit",
39337
+ "full"
39338
+ ]);
39339
+ _enum([
39340
+ "value+unit",
39341
+ "value",
39342
+ "unit"
39343
+ ]);
39334
39344
  /**
39335
- * `osd-manager` — the ORCHESTRATOR over the device-scope `osd` cap.
39336
- *
39337
- * The `osd` cap is the firmware contract: it probes a camera's overlay
39338
- * SLOTS and writes literal text into one. It has no idea WHERE that text
39339
- * comes from, and it must not — a driver that grew a "show the temperature
39340
- * here" feature would grow it once per vendor.
39341
- *
39342
- * This cap owns the other half: a per-(camera, slot) BINDING that says
39343
- * which value feeds the slot, how it is formatted, and under which
39344
- * conditions it is shown at all. Named TEMPLATES combine many device
39345
- * fields into one overlay string (Scrypted OSD Manager parity).
39345
+ * How the operator reached the leaf device. `deviceId` is ALWAYS the
39346
+ * OSD-supported leaf that is read; `containerId` is set when the pick
39347
+ * went through a container (gateway) first.
39346
39348
  */
39347
- /** One field reference inside a named multi-device template. */
39348
- var OsdTemplateFieldSchema = object({
39349
- deviceId: number().int(),
39349
+ var OsdTemplatePickSchema = object({
39350
+ containerId: number().int().optional(),
39351
+ deviceId: number().int()
39352
+ });
39353
+ /**
39354
+ * One named binding inside a multi-device template: device path + unit
39355
+ * selection + precision. The parser expands `{alias}`, `{alias.value}`,
39356
+ * `{alias.unit}`, or `{alias.full}` from the same binding — there is no
39357
+ * per-binding format lock.
39358
+ */
39359
+ var OsdTemplateBindingSchema = object({
39360
+ /** Token stem in the parser, e.g. `t` → `{t}` / `{t.value}`. Unique within the template. */
39361
+ alias: string().min(1).max(32).regex(/^[A-Za-z][A-Za-z0-9_-]*$/, "alias must start with a letter"),
39362
+ pick: OsdTemplatePickSchema,
39350
39363
  capName: string().min(1).max(64),
39351
39364
  valuePath: string().min(1).max(64),
39352
39365
  /** Convert TO this unit at render (`tryConvertUnit`). Absent = source unit. */
@@ -39354,14 +39367,14 @@ var OsdTemplateFieldSchema = object({
39354
39367
  maxDecimals: number().int().min(0).max(4).default(1)
39355
39368
  });
39356
39369
  /**
39357
- * A named template: several device fields → one parser string with
39358
- * `{deviceId.capName.valuePath}` tokens (Scrypted `{deviceId.sensorId}`).
39370
+ * A named template: bindings (alias → leaf field) + a parser that
39371
+ * references `{alias}` / `{alias.value}` / `{alias.unit}` / `{alias.full}`.
39359
39372
  */
39360
39373
  var OsdNamedTemplateSchema = object({
39361
39374
  id: string().min(1).max(64),
39362
39375
  name: string().min(1).max(64),
39363
- fields: array(OsdTemplateFieldSchema).max(32).default([]),
39364
- /** Parser with multi-token placeholders, e.g. `T {12.thermometer.value}°`. */
39376
+ bindings: array(OsdTemplateBindingSchema).max(32).default([]),
39377
+ /** Parser with alias placeholders, e.g. `{t.value} {t.unit} / {h}`. */
39365
39378
  parser: string().max(256).default("")
39366
39379
  });
39367
39380
  /** Where a slot's value comes from. */
@@ -45567,6 +45580,10 @@ var UNIT_TABLE = {
45567
45580
  function specFor(unit) {
45568
45581
  return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
45569
45582
  }
45583
+ /** The dimension a unit belongs to, or `undefined` for an unknown unit. */
45584
+ function unitDimension(unit) {
45585
+ return specFor(unit)?.dimension;
45586
+ }
45570
45587
  /** Value in `spec`'s unit → its dimension's canonical unit. */
45571
45588
  function toCanonical(value, spec) {
45572
45589
  return value * spec.factor + spec.offset;
@@ -58368,14 +58385,91 @@ function readStatePath(slice, path) {
58368
58385
  }
58369
58386
  //#endregion
58370
58387
  //#region src/template-tokens.ts
58371
- /** Build the placeholder token for one template field. */
58372
- function fieldToken(field) {
58373
- return `{${field.deviceId}.${field.capName}.${field.valuePath}}`;
58388
+ /** Build a placeholder token for one binding alias + expand kind. */
58389
+ function aliasToken(alias, kind = "full") {
58390
+ if (kind === "full") return `{${alias}}`;
58391
+ return `{${alias}.${kind}}`;
58392
+ }
58393
+ /** Explicit `{alias.full}` form (chips prefer this over bare `{alias}`). */
58394
+ function aliasFullToken(alias) {
58395
+ return `{${alias}.full}`;
58396
+ }
58397
+ /** Chip / parser insert options for one binding (value, unit, value+unit). */
58398
+ function bindingInsertChips(binding) {
58399
+ const alias = binding.alias;
58400
+ return [
58401
+ {
58402
+ id: `${alias}.value`,
58403
+ label: `${alias} · value`,
58404
+ token: aliasToken(alias, "value"),
58405
+ kind: "value"
58406
+ },
58407
+ {
58408
+ id: `${alias}.unit`,
58409
+ label: `${alias} · unit`,
58410
+ token: aliasToken(alias, "unit"),
58411
+ kind: "unit"
58412
+ },
58413
+ {
58414
+ id: `${alias}.full`,
58415
+ label: `${alias} · value+unit`,
58416
+ token: aliasFullToken(alias),
58417
+ kind: "full"
58418
+ }
58419
+ ];
58420
+ }
58421
+ /** @deprecated Prefer `bindingInsertChips` — bare `{alias}` only. */
58422
+ function bindingToken(binding) {
58423
+ return aliasToken(binding.alias, "full");
58424
+ }
58425
+ /**
58426
+ * Parse `{alias}`, `{alias.value}`, `{alias.unit}`, `{alias.full}` from a
58427
+ * parser string. Bare `{alias}` and `{alias.full}` both mean `full`.
58428
+ */
58429
+ function parseAliasTokens(parser) {
58430
+ const out = [];
58431
+ const seen = /* @__PURE__ */ new Set();
58432
+ for (const match of parser.matchAll(/\{([A-Za-z][A-Za-z0-9_-]*)(?:\.(value|unit|full))?\}/g)) {
58433
+ const alias = match[1];
58434
+ if (alias === void 0 || alias === "") continue;
58435
+ const suffix = match[2];
58436
+ const kind = suffix === "value" || suffix === "unit" ? suffix : "full";
58437
+ const token = match[0] ?? "";
58438
+ const key = `${alias}:${kind}:${token}`;
58439
+ if (seen.has(key)) continue;
58440
+ seen.add(key);
58441
+ out.push({
58442
+ alias,
58443
+ kind,
58444
+ token
58445
+ });
58446
+ }
58447
+ return out;
58448
+ }
58449
+ /** Unique alias stems referenced by a parser (any token kind). */
58450
+ function parseAliasStems(parser) {
58451
+ const stems = [];
58452
+ for (const entry of parseAliasTokens(parser)) if (!stems.includes(entry.alias)) stems.push(entry.alias);
58453
+ return stems;
58374
58454
  }
58375
58455
  /** Replace every occurrence of `token` in `parser` with `value`. */
58376
58456
  function replaceToken(parser, token, value) {
58377
58457
  return parser.replaceAll(token, value);
58378
58458
  }
58459
+ /**
58460
+ * Suggest a short unique alias from a device/field name.
58461
+ * Falls back to `b1`, `b2`, … when the slug collides.
58462
+ */
58463
+ function suggestAlias(seed, taken, fallbackPrefix = "b") {
58464
+ const slug = seed.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/^(\d)/, "a$1").slice(0, 24);
58465
+ const base = slug.length > 0 ? slug : fallbackPrefix;
58466
+ if (!taken.has(base)) return base;
58467
+ for (let i = 2; i < 1e3; i += 1) {
58468
+ const candidate = `${base}${i}`.slice(0, 32);
58469
+ if (!taken.has(candidate)) return candidate;
58470
+ }
58471
+ return `${fallbackPrefix}${Date.now().toString(36)}`;
58472
+ }
58379
58473
  //#endregion
58380
58474
  //#region src/unit-display.ts
58381
58475
  /**
@@ -58383,6 +58477,16 @@ function replaceToken(parser, token, value) {
58383
58477
  * UNIT_TABLE / tryConvertUnit (same set as D699).
58384
58478
  */
58385
58479
  /**
58480
+ * Dimension for a UnitPicker from a raw source unit (may be bare `C`).
58481
+ * Normalize before `unitDimension` — the table only knows canonical spellings.
58482
+ */
58483
+ function dimensionFromSourceUnit(sourceUnit) {
58484
+ if (sourceUnit === void 0) return void 0;
58485
+ const normalized = normalizeUnit(sourceUnit);
58486
+ if (normalized === void 0) return void 0;
58487
+ return unitDimension(normalized);
58488
+ }
58489
+ /**
58386
58490
  * Convert `raw` from `sourceUnit` to `displayUnit` when both are known and
58387
58491
  * same-dimension. On refusal, keep the raw value and the source unit so the
58388
58492
  * label never lies about the number.
@@ -58431,24 +58535,64 @@ function inferSourceUnit(slice, valuePath) {
58431
58535
  }
58432
58536
  //#endregion
58433
58537
  //#region src/named-template-render.ts
58538
+ /** Combine value/unit for a token kind. */
58539
+ function applyTemplateTokenKind(value, unit, kind) {
58540
+ if (kind === "value") return value;
58541
+ if (kind === "unit") return unit;
58542
+ const v = value.trim();
58543
+ const u = unit.trim();
58544
+ if (v === "") return u;
58545
+ if (u === "") return v;
58546
+ return `${v} ${u}`;
58547
+ }
58548
+ /**
58549
+ * @deprecated Prefer `applyTemplateTokenKind`. Maps legacy format enum to
58550
+ * the same join rules (`value+unit` → `full`).
58551
+ */
58552
+ function applyTemplateFormat(value, unit, format) {
58553
+ return applyTemplateTokenKind(value, unit, format === "value" ? "value" : format === "unit" ? "unit" : "full");
58554
+ }
58555
+ /** Resolve one binding to live value/unit/formatted parts (or a reason). */
58556
+ function resolveBindingLive(binding, facts) {
58557
+ const deviceId = binding.pick.deviceId;
58558
+ const deviceState = facts.stateByDevice.get(deviceId);
58559
+ if (deviceState === void 0) return { reason: `no state mirrored for device ${deviceId}` };
58560
+ const slice = deviceState[binding.capName];
58561
+ if (slice === void 0) return { reason: `device ${deviceId} publishes no "${binding.capName}" state` };
58562
+ const raw = readStatePath(slice, binding.valuePath);
58563
+ if (raw === void 0) return { reason: `"${binding.capName}.${binding.valuePath}" is absent on device ${deviceId}` };
58564
+ let value;
58565
+ let unit = "";
58566
+ if (typeof raw === "number") {
58567
+ const converted = convertForDisplay(raw, inferSourceUnit(slice, binding.valuePath), binding.displayUnit);
58568
+ value = truncateDecimals(converted.value, binding.maxDecimals);
58569
+ unit = converted.unit;
58570
+ } else if (typeof raw === "boolean") value = raw ? "ON" : "OFF";
58571
+ else if (raw === null) value = "";
58572
+ else value = String(raw);
58573
+ return {
58574
+ value,
58575
+ unit,
58576
+ formatted: applyTemplateTokenKind(value, unit, "full")
58577
+ };
58578
+ }
58579
+ /** Substitute every token form for one alias from live parts. Longest first. */
58580
+ function expandBindingTokens(parser, alias, live) {
58581
+ let text = parser;
58582
+ text = replaceToken(text, aliasFullToken(alias), live.formatted);
58583
+ text = replaceToken(text, aliasToken(alias, "value"), live.value);
58584
+ text = replaceToken(text, aliasToken(alias, "unit"), live.unit);
58585
+ text = replaceToken(text, aliasToken(alias, "full"), live.formatted);
58586
+ return text;
58587
+ }
58434
58588
  /** Render one named template's parser from live states. */
58435
58589
  function renderNamedTemplate(template, facts) {
58436
58590
  let text = template.parser;
58437
- if (template.fields.length === 0) return text.trim() === "" ? { reason: `template "${template.id}" has no fields` } : { text };
58438
- for (const field of template.fields) {
58439
- const token = fieldToken(field);
58440
- const deviceState = facts.stateByDevice.get(field.deviceId);
58441
- if (deviceState === void 0) return { reason: `no state mirrored for device ${field.deviceId}` };
58442
- const slice = deviceState[field.capName];
58443
- if (slice === void 0) return { reason: `device ${field.deviceId} publishes no "${field.capName}" state` };
58444
- const raw = readStatePath(slice, field.valuePath);
58445
- if (raw === void 0) return { reason: `"${field.capName}.${field.valuePath}" is absent on device ${field.deviceId}` };
58446
- let rendered;
58447
- if (typeof raw === "number") rendered = truncateDecimals(convertForDisplay(raw, inferSourceUnit(slice, field.valuePath), field.displayUnit).value, field.maxDecimals);
58448
- else if (typeof raw === "boolean") rendered = raw ? "ON" : "OFF";
58449
- else if (raw === null) rendered = "";
58450
- else rendered = String(raw);
58451
- text = replaceToken(text, token, rendered);
58591
+ if (template.bindings.length === 0) return text.trim() === "" ? { reason: `template "${template.id}" has no bindings` } : { text };
58592
+ for (const binding of template.bindings) {
58593
+ const live = resolveBindingLive(binding, facts);
58594
+ if ("reason" in live) return live;
58595
+ text = expandBindingTokens(text, binding.alias, live);
58452
58596
  }
58453
58597
  return { text };
58454
58598
  }
@@ -58550,20 +58694,98 @@ function settle(slotId, text, previousText, outcome, reason) {
58550
58694
  };
58551
58695
  }
58552
58696
  //#endregion
58697
+ //#region src/osd-source-fields.ts
58698
+ /** Caps the OSD source picker may bind. */
58699
+ var OSD_SOURCE_CAP_NAMES = new Set([
58700
+ "temperature-sensor",
58701
+ "humidity-sensor",
58702
+ "climate-control",
58703
+ "numeric-sensor",
58704
+ "weather",
58705
+ "air-quality-sensor",
58706
+ "lock-control",
58707
+ "contact",
58708
+ "binary",
58709
+ "battery"
58710
+ ]);
58711
+ /**
58712
+ * Per-cap leaf allowlist. Absent entry ⇒ every leaf from `stateLeavesOf`
58713
+ * that survives bookkeeping (single-metric sensors). Present ⇒ only these
58714
+ * paths (climate / weather / battery).
58715
+ */
58716
+ var OSD_SOURCE_PATHS = new Map([
58717
+ ["climate-control", new Set([
58718
+ "currentTemp",
58719
+ "currentHumidity",
58720
+ "target",
58721
+ "targetHumidity",
58722
+ "mode"
58723
+ ])],
58724
+ ["weather", new Set([
58725
+ "condition",
58726
+ "temperature",
58727
+ "humidity",
58728
+ "pressure",
58729
+ "windSpeed",
58730
+ "windBearing"
58731
+ ])],
58732
+ ["battery", new Set(["percentage"])],
58733
+ ["temperature-sensor", new Set(["celsius"])],
58734
+ ["humidity-sensor", new Set(["percent"])],
58735
+ ["numeric-sensor", new Set(["value"])],
58736
+ ["air-quality-sensor", new Set([
58737
+ "co2Ppm",
58738
+ "vocPpb",
58739
+ "pm25",
58740
+ "pm10",
58741
+ "aqi"
58742
+ ])],
58743
+ ["binary", new Set(["on"])],
58744
+ ["contact", new Set(["entryOpen"])],
58745
+ ["lock-control", new Set(["state"])]
58746
+ ]);
58747
+ function isOsdSourceCap(capName) {
58748
+ return OSD_SOURCE_CAP_NAMES.has(capName);
58749
+ }
58750
+ /** Bindable OSD leaves for one cap — empty when the cap is not OSD-relevant. */
58751
+ function osdStateLeavesOf(cap) {
58752
+ if (!isOsdSourceCap(cap.name)) return [];
58753
+ const leaves = stateLeavesOf(cap);
58754
+ const allowed = OSD_SOURCE_PATHS.get(cap.name);
58755
+ if (allowed === void 0) return leaves;
58756
+ return leaves.filter((leaf) => allowed.has(leaf.path));
58757
+ }
58758
+ /** Device types that never contribute their OWN catalog leaves. */
58759
+ var OSD_NON_LEAF_TYPES = new Set(["container"]);
58760
+ /** True when this device type may expose OSD leaves (not a container gateway). */
58761
+ function isOsdLeafDeviceType(type) {
58762
+ if (type === null || type === void 0) return true;
58763
+ return !OSD_NON_LEAF_TYPES.has(type);
58764
+ }
58765
+ /** @deprecated Prefer {@link isOsdLeafDeviceType}. */
58766
+ var OSD_PICKER_EXCLUDED_TYPES = OSD_NON_LEAF_TYPES;
58767
+ /** @deprecated Prefer {@link isOsdLeafDeviceType}. */
58768
+ function isOsdPickerDeviceType(type) {
58769
+ return isOsdLeafDeviceType(type);
58770
+ }
58771
+ function isContainerDeviceType(type) {
58772
+ return type === "container";
58773
+ }
58774
+ //#endregion
58553
58775
  //#region src/source-catalog.ts
58554
58776
  /**
58555
- * Build the editor's source picker from the CAP DEFINITIONS — never a
58556
- * hand-written list. A cap that ships a new runtime-state field becomes
58557
- * bindable the moment it deploys, with no change here.
58777
+ * Build the editor's source picker from OSD-relevant cap definitions.
58778
+ * Gateway / infrastructure caps (broker, mqtt, deviceExport, …) never
58779
+ * appear — see `osd-source-fields.ts`.
58558
58780
  */
58559
58781
  /** Turn `air-quality-sensor` + `pm25` into `Air Quality Sensor · pm25`. */
58560
58782
  function humanizeCapName(capName) {
58561
58783
  return capName.split("-").map((part) => part.length === 0 ? part : part[0].toUpperCase() + part.slice(1)).join(" ");
58562
58784
  }
58563
58785
  /**
58564
- * Every bindable value across the cluster, with the camera's own state
58565
- * first — that is what an operator reaches for, and a picker that buries
58566
- * it under 200 other devices is a picker nobody uses.
58786
+ * Every OSD-bindable value across the cluster, with the camera's own
58787
+ * state first — that is what an operator reaches for, and a picker that
58788
+ * buries it under 200 other devices is a picker nobody uses.
58567
58789
  */
58568
58790
  function sourceOptionsFor(cameraDeviceId, devices, capsByDevice) {
58569
58791
  const capsByName = new Map(ALL_CAPABILITY_DEFINITIONS.map((cap) => [cap.name, cap]));
@@ -58573,18 +58795,22 @@ function sourceOptionsFor(cameraDeviceId, devices, capsByDevice) {
58573
58795
  return a.id - b.id;
58574
58796
  });
58575
58797
  const options = [];
58576
- for (const device of ordered) for (const capName of capsByDevice.get(device.id) ?? []) {
58577
- const cap = capsByName.get(capName);
58578
- if (cap === void 0) continue;
58579
- for (const leaf of stateLeavesOf(cap)) options.push({
58580
- deviceId: device.id,
58581
- deviceName: device.name,
58582
- capName,
58583
- valuePath: leaf.path,
58584
- label: `${humanizeCapName(capName)} · ${leaf.path}`,
58585
- valueType: leaf.valueType,
58586
- ...leaf.enumValues !== void 0 ? { enumValues: leaf.enumValues } : {}
58587
- });
58798
+ for (const device of ordered) {
58799
+ if (!isOsdLeafDeviceType(device.type ?? null)) continue;
58800
+ for (const capName of capsByDevice.get(device.id) ?? []) {
58801
+ if (!isOsdSourceCap(capName)) continue;
58802
+ const cap = capsByName.get(capName);
58803
+ if (cap === void 0) continue;
58804
+ for (const leaf of osdStateLeavesOf(cap)) options.push({
58805
+ deviceId: device.id,
58806
+ deviceName: device.name,
58807
+ capName,
58808
+ valuePath: leaf.path,
58809
+ label: `${humanizeCapName(capName)} · ${leaf.path}`,
58810
+ valueType: leaf.valueType,
58811
+ ...leaf.enumValues !== void 0 ? { enumValues: leaf.enumValues } : {}
58812
+ });
58813
+ }
58588
58814
  }
58589
58815
  return options;
58590
58816
  }
@@ -58980,7 +59206,7 @@ var OsdManager = class {
58980
59206
  if (stored === void 0) throw new Error(`osd-manager: template "${template.id}" missing after upsert`);
58981
59207
  this.logger.info("osd-manager: named template upserted", { meta: {
58982
59208
  templateId: stored.id,
58983
- fieldCount: stored.fields.length
59209
+ bindingCount: stored.bindings.length
58984
59210
  } });
58985
59211
  return { template: stored };
58986
59212
  };
@@ -59068,7 +59294,7 @@ var OsdManager = class {
59068
59294
  const templatesById = /* @__PURE__ */ new Map();
59069
59295
  for (const named of allTemplates) {
59070
59296
  templatesById.set(named.id, named);
59071
- if (templateIds.has(named.id)) for (const field of named.fields) wanted.add(field.deviceId);
59297
+ if (templateIds.has(named.id)) for (const binding of named.bindings) wanted.add(binding.pick.deviceId);
59072
59298
  }
59073
59299
  const stateByDevice = /* @__PURE__ */ new Map();
59074
59300
  const gateState = /* @__PURE__ */ new Map();
@@ -59270,7 +59496,8 @@ var OsdManagerAddon = class extends BaseAddon {
59270
59496
  listDevices: async () => {
59271
59497
  return (await ctx.api.deviceManager.listAll.query({ projection: "slim" })).map((d) => ({
59272
59498
  id: d.id,
59273
- name: d.name
59499
+ name: d.name,
59500
+ type: d.type ?? null
59274
59501
  }));
59275
59502
  },
59276
59503
  listCameras: async () => {
@@ -59368,28 +59595,94 @@ var OsdManagerAddon = class extends BaseAddon {
59368
59595
  }
59369
59596
  };
59370
59597
  //#endregion
59598
+ //#region src/osd-picker-devices.ts
59599
+ /**
59600
+ * Devices offered to the OSD DevicePicker — containers INCLUDED so the
59601
+ * operator can pick a gateway, then an OSD-supported child.
59602
+ */
59603
+ function toOsdPickerDevices(devices) {
59604
+ return devices.map((d) => ({
59605
+ id: d.id,
59606
+ name: d.name,
59607
+ type: d.type ?? null,
59608
+ location: d.location ?? null,
59609
+ parentDeviceId: d.parentDeviceId ?? null
59610
+ }));
59611
+ }
59612
+ /** Children of a container that have at least one OSD catalog field. */
59613
+ function osdSupportedChildrenOf(containerId, devices, deviceIdsWithOsdFields) {
59614
+ return devices.filter((d) => d.parentDeviceId === containerId && isOsdLeafDeviceType(d.type) && deviceIdsWithOsdFields.has(d.id)).toSorted((a, b) => a.name.localeCompare(b.name));
59615
+ }
59616
+ function deviceById(devices, id) {
59617
+ return devices.find((d) => d.id === id);
59618
+ }
59619
+ //#endregion
59620
+ //#region src/template-id.ts
59621
+ /**
59622
+ * Named-template ids are system-generated — never an operator text field.
59623
+ * Create uses a uuid; the editable surface is the template name.
59624
+ */
59625
+ function generateTemplateId() {
59626
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
59627
+ return `tpl-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
59628
+ }
59629
+ /** Optional slug helper when a stable short id from the name is preferred. */
59630
+ function templateIdFromName(name) {
59631
+ const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
59632
+ return `${slug.length > 0 ? slug : "template"}-${Date.now().toString(36)}`;
59633
+ }
59634
+ //#endregion
59371
59635
  exports.DEFAULT_TICK_MS = DEFAULT_TICK_MS;
59372
59636
  exports.EMPTY_BINDINGS = EMPTY_BINDINGS;
59373
59637
  exports.OSD_BINDINGS_KEY = OSD_BINDINGS_KEY;
59638
+ exports.OSD_NON_LEAF_TYPES = OSD_NON_LEAF_TYPES;
59639
+ exports.OSD_PICKER_EXCLUDED_TYPES = OSD_PICKER_EXCLUDED_TYPES;
59640
+ exports.OSD_SOURCE_CAP_NAMES = OSD_SOURCE_CAP_NAMES;
59374
59641
  exports.OSD_SUPPORTED_CONDITIONS = OSD_SUPPORTED_CONDITIONS;
59375
59642
  exports.OsdBindingsStore = OsdBindingsStore;
59376
59643
  exports.OsdDeviceBindingsSchema = OsdDeviceBindingsSchema;
59377
59644
  exports.OsdManager = OsdManager;
59378
59645
  exports.OsdManagerAddon = OsdManagerAddon;
59646
+ exports.aliasFullToken = aliasFullToken;
59647
+ exports.aliasToken = aliasToken;
59379
59648
  exports.applyTemplate = applyTemplate;
59649
+ exports.applyTemplateFormat = applyTemplateFormat;
59650
+ exports.applyTemplateTokenKind = applyTemplateTokenKind;
59380
59651
  exports.assertSupportedConditions = assertSupportedConditions;
59652
+ exports.bindingInsertChips = bindingInsertChips;
59653
+ exports.bindingToken = bindingToken;
59654
+ exports.convertForDisplay = convertForDisplay;
59381
59655
  exports.default = OsdManagerAddon;
59656
+ exports.deviceById = deviceById;
59657
+ exports.dimensionFromSourceUnit = dimensionFromSourceUnit;
59382
59658
  exports.displayValue = displayValue;
59383
59659
  exports.evaluateGate = evaluateGate;
59660
+ exports.expandBindingTokens = expandBindingTokens;
59384
59661
  exports.formatClock = formatClock;
59385
59662
  exports.formatSlotText = formatSlotText;
59663
+ exports.generateTemplateId = generateTemplateId;
59386
59664
  exports.humanizeCapName = humanizeCapName;
59665
+ exports.inferSourceUnit = inferSourceUnit;
59666
+ exports.isContainerDeviceType = isContainerDeviceType;
59667
+ exports.isOsdLeafDeviceType = isOsdLeafDeviceType;
59668
+ exports.isOsdPickerDeviceType = isOsdPickerDeviceType;
59669
+ exports.isOsdSourceCap = isOsdSourceCap;
59387
59670
  exports.limitText = limitText;
59671
+ exports.osdStateLeavesOf = osdStateLeavesOf;
59672
+ exports.osdSupportedChildrenOf = osdSupportedChildrenOf;
59673
+ exports.parseAliasStems = parseAliasStems;
59674
+ exports.parseAliasTokens = parseAliasTokens;
59388
59675
  exports.readStatePath = readStatePath;
59676
+ exports.renderNamedTemplate = renderNamedTemplate;
59389
59677
  exports.renderSlot = renderSlot;
59678
+ exports.replaceToken = replaceToken;
59679
+ exports.resolveBindingLive = resolveBindingLive;
59390
59680
  exports.resolveSource = resolveSource;
59391
59681
  exports.sourceOptionsFor = sourceOptionsFor;
59392
59682
  exports.stateLeavesOf = stateLeavesOf;
59683
+ exports.suggestAlias = suggestAlias;
59684
+ exports.templateIdFromName = templateIdFromName;
59685
+ exports.toOsdPickerDevices = toOsdPickerDevices;
59393
59686
  exports.truncateDecimals = truncateDecimals;
59394
59687
  exports.unsupportedConditionKeys = unsupportedConditionKeys;
59395
59688
  exports.withBinding = withBinding;