@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.
- package/dist/{MotionZonesSettings-vYt4dibj.mjs → MotionZonesSettings-CSheC3_V.mjs} +2 -2
- package/dist/{PrivacyMaskSettings-BiQ78hst.mjs → PrivacyMaskSettings-I6eE4GFM.mjs} +4 -4
- package/dist/{SceneMonitorEditor-CJtJIyMf.mjs → SceneMonitorEditor-mNJ89n0T.mjs} +3 -3
- package/dist/_stub.js +3059 -2713
- 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
- 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
- package/dist/{hostInit-0Mc7Fm1R.mjs → hostInit-DFyjx8Xk.mjs} +1 -1
- package/dist/index.js +350 -57
- package/dist/index.mjs +322 -58
- package/dist/{player-overlays-BPyna-eI.mjs → player-overlays-DFXmMB9Y.mjs} +1 -1
- package/dist/remoteEntry.js +1 -1
- package/dist/{responsive-B0yDP9vf.mjs → responsive-DLFs-5i_.mjs} +1 -1
- package/dist/{scene-monitor-copy-BzX20O6q.mjs → scene-monitor-copy-DVNcikwa.mjs} +1 -1
- package/dist/{square-BOMOn_6j.mjs → square-Co3Eglp3.mjs} +1 -1
- package/dist/{trash-2-BolGRl2P.mjs → trash-2-DnKldIWK.mjs} +1 -1
- 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
- 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
|
-
*
|
|
39336
|
-
*
|
|
39337
|
-
*
|
|
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
|
-
|
|
39348
|
-
|
|
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:
|
|
39358
|
-
* `{
|
|
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
|
-
|
|
39364
|
-
/** Parser with
|
|
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
|
|
58372
|
-
function
|
|
58373
|
-
return `{${
|
|
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.
|
|
58438
|
-
for (const
|
|
58439
|
-
const
|
|
58440
|
-
|
|
58441
|
-
|
|
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
|
|
58556
|
-
*
|
|
58557
|
-
*
|
|
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
|
|
58565
|
-
* first — that is what an operator reaches for, and a picker that
|
|
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)
|
|
58577
|
-
|
|
58578
|
-
|
|
58579
|
-
|
|
58580
|
-
|
|
58581
|
-
|
|
58582
|
-
|
|
58583
|
-
|
|
58584
|
-
|
|
58585
|
-
|
|
58586
|
-
|
|
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
|
-
|
|
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
|
|
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;
|