898 lines
27 KiB
JavaScript
898 lines
27 KiB
JavaScript
/*
|
|
Copyright (c) 2023-forever Douglas Malnati. All rights reserved.
|
|
|
|
See the /faq/tos page for details.
|
|
|
|
(If this generated header is stamped on a file which is a 3rd party file or under a different license or copyright, then ignore this copyright statement and use that file's terms.)
|
|
*/
|
|
|
|
import * as utl from '/js/Utl.js';
|
|
|
|
import { DialogBox } from './DomWidgets.js';
|
|
import { StrAccumulator } from '/js/Utl.js';
|
|
import { WsprCodecMaker } from '../../../../pro/codec/WsprCodec.js';
|
|
|
|
|
|
|
|
|
|
|
|
export class MsgDefinitionInputUiController
|
|
{
|
|
constructor()
|
|
{
|
|
this.codecMaker = new WsprCodecMaker();
|
|
|
|
this.onApplyCbFn = () => {};
|
|
this.onErrCbFn = () => {};
|
|
|
|
this.ok = true;
|
|
this.cachedLastMsgDefApplied = "";
|
|
|
|
this.namePrefix = "Message Definition Analysis";
|
|
this.name = "";
|
|
|
|
this.fileNamePart = "";
|
|
|
|
this.ui = this.#MakeUI();
|
|
|
|
this.#SetUpEvents();
|
|
this.#ShowExampleValue();
|
|
}
|
|
|
|
SetDisplayName(name)
|
|
{
|
|
this.name = name;
|
|
|
|
this.dialogBox.SetTitleBar(this.namePrefix + (this.name == "" ? "" : ` - ${this.name}`));
|
|
}
|
|
|
|
SetDownloadFileNamePart(fileNamePart)
|
|
{
|
|
this.fileNamePart = fileNamePart;
|
|
}
|
|
|
|
GetUI()
|
|
{
|
|
return this.ui;
|
|
}
|
|
|
|
GetUIInput()
|
|
{
|
|
return this.msgDefInput;
|
|
}
|
|
|
|
GetUIAnalysis()
|
|
{
|
|
return this.codecAnalysis;
|
|
}
|
|
|
|
GetUIButtonApply()
|
|
{
|
|
return this.applyButton;
|
|
}
|
|
|
|
GetUIButtonRestore()
|
|
{
|
|
return this.restoreButton;
|
|
}
|
|
|
|
GetUIButtonShowExample()
|
|
{
|
|
return this.showExampleButton;
|
|
}
|
|
|
|
GetUIButtonFromFile()
|
|
{
|
|
return this.uploadButton;
|
|
}
|
|
|
|
GetUIButtonPrettify()
|
|
{
|
|
return this.prettifyButton;
|
|
}
|
|
|
|
GetUIButtonToFile()
|
|
{
|
|
return this.downloadButton;
|
|
}
|
|
|
|
PrettifyMsgDefinition()
|
|
{
|
|
let prettyText = this.#BuildPrettifiedMsgDefinitionText();
|
|
if (!prettyText)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
let wasApplied = this.GetMsgDefinitionRaw() == this.cachedLastMsgDefApplied;
|
|
this.msgDefInput.value = prettyText;
|
|
this.#OnMsgDefInputChange();
|
|
|
|
if (this.ok && wasApplied)
|
|
{
|
|
this.cachedLastMsgDefApplied = prettyText;
|
|
this.#MarkMsgDefApplied();
|
|
this.#SetStateApplied();
|
|
this.onApplyCbFn();
|
|
}
|
|
|
|
this.onErrCbFn(this.ok);
|
|
return this.ok;
|
|
}
|
|
|
|
SetModeNoPopup()
|
|
{
|
|
// remove show/hide button
|
|
this.ui.removeChild(this.analysisButton);
|
|
|
|
// remove dialog box
|
|
this.ui.removeChild(this.dialogBox.GetUI());
|
|
|
|
// insert analysis
|
|
this.codecAnalysis.style.marginTop = "3px";
|
|
this.ui.append(this.codecAnalysis);
|
|
|
|
return this.ui;
|
|
}
|
|
|
|
SetModeIndividual()
|
|
{
|
|
// git rid of styling which doesn't apply
|
|
this.msgDefInput.style.marginBottom = "0px";
|
|
|
|
// remove show/hide button
|
|
this.ui.removeChild(this.analysisButton);
|
|
|
|
// remove dialog box
|
|
this.ui.removeChild(this.dialogBox.GetUI());
|
|
}
|
|
|
|
SetOnApplyCallback(cb)
|
|
{
|
|
this.onApplyCbFn = cb;
|
|
}
|
|
|
|
GetOnApplyCallback()
|
|
{
|
|
return this.onApplyCbFn;
|
|
}
|
|
|
|
SetOnErrStateChangeCallback(cb)
|
|
{
|
|
this.onErrCbFn = cb;
|
|
}
|
|
|
|
IsOk()
|
|
{
|
|
return this.ok;
|
|
}
|
|
|
|
GetMsgDefinition()
|
|
{
|
|
return this.cachedLastMsgDefApplied;
|
|
}
|
|
|
|
GetMsgDefinitionRaw()
|
|
{
|
|
return this.msgDefInput.value;
|
|
}
|
|
|
|
GetFieldList()
|
|
{
|
|
let c = this.codecMaker.GetCodecInstance();
|
|
const fieldList = c.GetFieldList();
|
|
|
|
return fieldList;
|
|
}
|
|
|
|
GetFieldNameList()
|
|
{
|
|
const fieldList = this.GetFieldList();
|
|
|
|
let fieldNameList = [];
|
|
for (let field of fieldList)
|
|
{
|
|
fieldNameList.push(`${field.name}${field.unit}`);
|
|
}
|
|
|
|
return fieldNameList;
|
|
}
|
|
|
|
SetMsgDefinition(value, markApplied)
|
|
{
|
|
markApplied = markApplied ?? true;
|
|
|
|
this.msgDefInput.value = value;
|
|
|
|
this.#OnMsgDefInputChange();
|
|
|
|
if (this.ok)
|
|
{
|
|
if (markApplied)
|
|
{
|
|
this.cachedLastMsgDefApplied = value;
|
|
this.#MarkMsgDefApplied();
|
|
this.#SetStateApplied();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// it's bad, so indicate that whatever the prior applied value
|
|
// was is still in effect
|
|
this.#DisableApplyButton();
|
|
}
|
|
|
|
this.onErrCbFn(this.ok);
|
|
|
|
return this.ok;
|
|
}
|
|
|
|
#SetUpEvents()
|
|
{
|
|
this.msgDefInput.addEventListener('input', () => {
|
|
this.#OnMsgDefInputChange();
|
|
})
|
|
|
|
this.applyButton.addEventListener('click', () => {
|
|
if (this.ok)
|
|
{
|
|
this.cachedLastMsgDefApplied = this.GetMsgDefinitionRaw();
|
|
|
|
this.#MarkMsgDefApplied();
|
|
this.#SetStateApplied();
|
|
|
|
this.onApplyCbFn();
|
|
}
|
|
});
|
|
|
|
this.restoreButton.addEventListener('click', () => {
|
|
this.SetMsgDefinition(this.cachedLastMsgDefApplied, false);
|
|
});
|
|
|
|
this.showExampleButton.addEventListener('click', () => {
|
|
this.#ShowExampleValue();
|
|
this.#OnMsgDefInputChange();
|
|
});
|
|
|
|
this.uploadButton.addEventListener('click', () => {
|
|
utl.LoadFromFile(".json").then((str) => {
|
|
this.SetMsgDefinition(str, false);
|
|
});
|
|
});
|
|
|
|
this.prettifyButton.addEventListener('click', () => {
|
|
this.PrettifyMsgDefinition();
|
|
});
|
|
|
|
this.downloadButton.addEventListener('click', () => {
|
|
let fileName = `MsgDef`;
|
|
if (this.fileNamePart != "")
|
|
{
|
|
fileName += `_`;
|
|
fileName += this.fileNamePart;
|
|
}
|
|
fileName += `.json`;
|
|
|
|
utl.SaveToFile(this.GetMsgDefinitionRaw(), fileName);
|
|
});
|
|
|
|
this.analysisButton.addEventListener('click', () => {
|
|
this.dialogBox.ToggleShowHide();
|
|
});
|
|
|
|
utl.GiveHotkeysVSCode(this.msgDefInput, () => {
|
|
this.applyButton.click();
|
|
});
|
|
}
|
|
|
|
GetExampleValue()
|
|
{
|
|
let msgDefRowList = [
|
|
`// Example Message Definition -- modify then save!\n`,
|
|
`{ "name": "Altitude", "unit": "Meters", "lowValue": 0, "highValue": 21340, "stepSize": 20 },`,
|
|
`{ "name": "SatsUSA", "unit": "Count", "lowValue": 0, "highValue": 32, "stepSize": 4 },`,
|
|
`{ "name": "ADC1", "unit": "Volts", "lowValue": 2.5, "highValue": 5.5, "stepSize": 0.2 },`,
|
|
`{ "name": "SomeInteger", "unit": "Value", "lowValue": -10, "highValue": 110, "stepSize": 5 },`,
|
|
`{ "name": "SomeFloat", "unit": "Value", "lowValue": -10.5, "highValue": 9.5, "stepSize": 20 },`,
|
|
`{ "name": "ClockDrift", "unit": "Millis", "valueSegmentList": [[-25, 5, -5], [-5, 1, 5], [5, 5, 25]] },`,
|
|
];
|
|
|
|
let str = ``;
|
|
let sep = "";
|
|
for (let msgDefRow of msgDefRowList)
|
|
{
|
|
str += sep;
|
|
str += msgDefRow;
|
|
|
|
sep = "\n";
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
#ShowExampleValue()
|
|
{
|
|
this.SetMsgDefinition(this.GetExampleValue(), false);
|
|
}
|
|
|
|
#OnMsgDefInputChange()
|
|
{
|
|
this.ok = this.#ApplyMsgDefinition();
|
|
|
|
// handle setting the validity state
|
|
if (this.ok)
|
|
{
|
|
this.#MarkMsgDefValid();
|
|
|
|
// handle setting the applied state
|
|
// (this can override the msg def coloring)
|
|
if (this.GetMsgDefinitionRaw() == this.cachedLastMsgDefApplied)
|
|
{
|
|
this.#SetStateApplied();
|
|
}
|
|
else
|
|
{
|
|
this.#SetStateNotApplied();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.#MarkMsgDefInvalid();
|
|
this.#DisableApplyButton();
|
|
}
|
|
|
|
this.onErrCbFn(this.ok);
|
|
|
|
return this.ok;
|
|
}
|
|
|
|
#MarkMsgDefValid()
|
|
{
|
|
this.msgDefInput.style.backgroundColor = "rgb(235, 255, 235)";
|
|
this.restoreButton.disabled = false;
|
|
}
|
|
|
|
#MarkMsgDefInvalid()
|
|
{
|
|
this.msgDefInput.style.backgroundColor = "lightpink";
|
|
this.restoreButton.disabled = false;
|
|
}
|
|
|
|
#MarkMsgDefApplied()
|
|
{
|
|
this.msgDefInput.style.backgroundColor = "white";
|
|
this.restoreButton.disabled = true;
|
|
}
|
|
|
|
#DisableApplyButton()
|
|
{
|
|
this.applyButton.disabled = true;
|
|
}
|
|
|
|
#SetStateApplied()
|
|
{
|
|
this.#DisableApplyButton();
|
|
this.restoreButton.disabled = false;
|
|
|
|
this.#MarkMsgDefApplied();
|
|
}
|
|
|
|
#SetStateNotApplied()
|
|
{
|
|
this.applyButton.disabled = false;
|
|
}
|
|
|
|
#CheckMsgDefOk()
|
|
{
|
|
let ok = this.codecMaker.SetCodecDefFragment("MyMessageType", this.msgDefInput.value);
|
|
|
|
return ok;
|
|
}
|
|
|
|
#ApplyMsgDefinition()
|
|
{
|
|
let ok = this.#CheckMsgDefOk();
|
|
|
|
ok &= this.#DoMsgDefinitionAnalysis(ok);
|
|
|
|
return ok;
|
|
}
|
|
|
|
#DoMsgDefinitionAnalysis(codecOk)
|
|
{
|
|
let retVal = true;
|
|
|
|
if (codecOk)
|
|
{
|
|
// get msg data
|
|
const fieldList = this.codecMaker.GetCodecInstance().GetFieldList();
|
|
|
|
// calc max field length for formatting
|
|
let maxFieldName = 5;
|
|
for (let field of fieldList)
|
|
{
|
|
let fieldName = field.name + field.unit;
|
|
|
|
if (fieldName.length > maxFieldName)
|
|
{
|
|
maxFieldName = fieldName.length;
|
|
}
|
|
}
|
|
|
|
// analyze utilization
|
|
let sumBits = 0;
|
|
for (let field of fieldList)
|
|
{
|
|
sumBits += field.Bits;
|
|
}
|
|
|
|
// output
|
|
const ENCODABLE_BITS = this.codecMaker.GetFieldBitsAvailable();
|
|
let pctUsed = (sumBits * 100 / ENCODABLE_BITS);
|
|
|
|
let pctUsedErr = "";
|
|
|
|
if (sumBits > ENCODABLE_BITS)
|
|
{
|
|
retVal = false;
|
|
|
|
pctUsedErr = "<---- OVERFLOW ERR";
|
|
}
|
|
|
|
let bitsRemaining = ENCODABLE_BITS - sumBits;
|
|
|
|
if (bitsRemaining < 0) { bitsRemaining = 0; }
|
|
let pctRemaining = (bitsRemaining * 100 / ENCODABLE_BITS);
|
|
|
|
// determine the number of values that could be encoded in the remaining bits, if any
|
|
let values = Math.pow(2, bitsRemaining);
|
|
if (bitsRemaining < 1)
|
|
{
|
|
values = 0;
|
|
}
|
|
let valuesFloor = Math.floor(values);
|
|
|
|
// setTimeout(() => {
|
|
// console.log(`------`)
|
|
// for (let field of fieldList)
|
|
// {
|
|
// console.log(`${field.name}${field.unit}: ${field.Bits} bits`);
|
|
// }
|
|
|
|
// console.log(`Encodable bits: ${ENCODABLE_BITS}`);
|
|
// console.log(`Sum bits: ${sumBits}`);
|
|
// console.log(`Bits remaining: ${bitsRemaining}`);
|
|
// console.log(`Values that could be encoded in remaining bits: ${values}`);
|
|
// console.log(`Values (floor) that could be encoded in remaining bits: ${valuesFloor}`);
|
|
// }, 0);
|
|
|
|
let valuesStr = ` (${utl.Commas(0).padStart(11)} values)`;
|
|
if (bitsRemaining >= 1)
|
|
{
|
|
valuesStr = ` (${utl.Commas(valuesFloor).padStart(11)} values)`;
|
|
}
|
|
|
|
// put out to 3 decimal places because available bits is 29.180... and so
|
|
// no need to worry about rounding after the 29.180 portion, so just display
|
|
// it and move on.
|
|
|
|
let a = new StrAccumulator();
|
|
let valuesAvailable = utl.Commas(Math.floor(Math.pow(2, ENCODABLE_BITS)));
|
|
a.A(`Encodable Bits Available: ${ENCODABLE_BITS.toFixed(3).padStart(6)} (${valuesAvailable.padStart(6)} values)`);
|
|
a.A(`Encodable Bits Used : ${sumBits.toFixed(3).padStart(6)} (${pctUsed.toFixed(2).padStart(6)} %) ${pctUsedErr}`);
|
|
a.A(`Encodable Bits Remaining: ${(bitsRemaining).toFixed(3).padStart(6)} (${pctRemaining.toFixed(2).padStart(6)} %)${valuesStr}`);
|
|
|
|
let PAD_VALUES = 9;
|
|
let PAD_BITS = 6;
|
|
let PAD_AVAIL = 8;
|
|
|
|
let FnOutput = (name, numValues, numBits, pct) => {
|
|
a.A(`${name.padEnd(maxFieldName)} ${numValues.padStart(PAD_VALUES)} ${numBits.padStart(PAD_BITS)} ${pct.padStart(PAD_AVAIL)}`);
|
|
}
|
|
|
|
a.A(``);
|
|
FnOutput("Field", "# Values", "# Bits", "% Used");
|
|
a.A(`-`.repeat(maxFieldName) + `-`.repeat(PAD_VALUES) + `-`.repeat(PAD_BITS) + `-`.repeat(PAD_AVAIL) + `-`.repeat(9));
|
|
let fieldRowList = [];
|
|
for (let field of fieldList)
|
|
{
|
|
let fieldName = field.name + field.unit;
|
|
let pct = (field.Bits * 100 / ENCODABLE_BITS).toFixed(2);
|
|
|
|
fieldRowList.push({
|
|
field,
|
|
fieldJsonText: this.#GetRawFieldJsonText(field),
|
|
fieldName,
|
|
numValues: field.NumValues.toString(),
|
|
bits: field.Bits.toFixed(3).toString(),
|
|
pct,
|
|
});
|
|
}
|
|
|
|
this.#SetCodecAnalysisWithFieldRows(a.Get(), fieldRowList, maxFieldName, PAD_VALUES, PAD_BITS, PAD_AVAIL);
|
|
}
|
|
else
|
|
{
|
|
retVal = false;
|
|
|
|
let a = new StrAccumulator();
|
|
a.A(`Codec definition invalid. (Make sure all rows have a trailing comma)`);
|
|
a.A(``);
|
|
for (let err of this.codecMaker.GetErrList())
|
|
{
|
|
a.A(err);
|
|
}
|
|
this.#SetCodecAnalysisPlain(a.Get());
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
#SetCodecAnalysisPlain(text)
|
|
{
|
|
this.codecAnalysis.replaceChildren(document.createTextNode(text));
|
|
}
|
|
|
|
#SetCodecAnalysisWithFieldRows(prefixText, fieldRowList, maxFieldName, PAD_VALUES, PAD_BITS, PAD_AVAIL)
|
|
{
|
|
this.codecAnalysis.replaceChildren();
|
|
this.codecAnalysis.appendChild(document.createTextNode(prefixText));
|
|
|
|
for (let row of fieldRowList)
|
|
{
|
|
let line = document.createElement("div");
|
|
line.style.whiteSpace = "pre";
|
|
line.style.fontFamily = "inherit";
|
|
line.style.fontSize = "inherit";
|
|
line.style.lineHeight = "inherit";
|
|
|
|
let fieldNamePadding = " ".repeat(Math.max(0, maxFieldName - row.fieldName.length));
|
|
let suffix = ` ${row.numValues.padStart(PAD_VALUES)} ${row.bits.padStart(PAD_BITS)} ${row.pct.padStart(PAD_AVAIL)}`;
|
|
|
|
let link = document.createElement("a");
|
|
link.href = this.#GetSegmentedFieldCalculatorUrl(row.field, row.fieldJsonText);
|
|
link.target = "_blank";
|
|
link.rel = "noopener noreferrer";
|
|
link.textContent = row.fieldName;
|
|
link.style.fontFamily = "inherit";
|
|
link.style.fontSize = "inherit";
|
|
link.style.lineHeight = "inherit";
|
|
link.style.display = "inline";
|
|
line.appendChild(link);
|
|
line.appendChild(document.createTextNode(fieldNamePadding));
|
|
|
|
line.appendChild(document.createTextNode(suffix));
|
|
this.codecAnalysis.appendChild(line);
|
|
}
|
|
}
|
|
|
|
#NormalizeFieldJsonForCompare(field)
|
|
{
|
|
if (!field || typeof field !== "object")
|
|
{
|
|
return null;
|
|
}
|
|
|
|
let normalized = {
|
|
name: String(field.name ?? "").trim(),
|
|
unit: String(field.unit ?? "").trim(),
|
|
};
|
|
|
|
if (Array.isArray(field.valueSegmentList))
|
|
{
|
|
normalized.valueSegmentList = field.valueSegmentList.map((segment) => Array.isArray(segment) ? segment.map((value) => Number(value)) : segment);
|
|
}
|
|
else
|
|
{
|
|
normalized.lowValue = Number(field.lowValue);
|
|
normalized.highValue = Number(field.highValue);
|
|
normalized.stepSize = Number(field.stepSize);
|
|
}
|
|
|
|
return JSON.stringify(normalized);
|
|
}
|
|
|
|
#GetRawFieldJsonText(field)
|
|
{
|
|
const target = this.#NormalizeFieldJsonForCompare(field);
|
|
if (!target)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
const lineList = this.GetMsgDefinitionRaw().split("\n");
|
|
for (const rawLine of lineList)
|
|
{
|
|
const trimmed = rawLine.trim();
|
|
if (!trimmed || trimmed.startsWith("//"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
const parsed = JSON.parse(trimmed.replace(/,\s*$/, ""));
|
|
if (this.#NormalizeFieldJsonForCompare(parsed) === target)
|
|
{
|
|
return trimmed.replace(/,\s*$/, "");
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore non-JSON lines.
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
#GetSegmentedFieldCalculatorUrl(field, fieldJsonText = "")
|
|
{
|
|
let fieldJson = Array.isArray(field?.valueSegmentList)
|
|
? {
|
|
name: field.name,
|
|
unit: field.unit,
|
|
valueSegmentList: field.valueSegmentList,
|
|
}
|
|
: {
|
|
name: field.name,
|
|
unit: field.unit,
|
|
lowValue: field.lowValue,
|
|
highValue: field.highValue,
|
|
stepSize: field.stepSize,
|
|
};
|
|
|
|
const exactFieldJsonText = fieldJsonText || this.#GetRawFieldJsonText(field);
|
|
return `/pro/codec/fieldcalc/?fieldJson=${encodeURIComponent(exactFieldJsonText || JSON.stringify(fieldJson))}`;
|
|
}
|
|
|
|
#ParsePrettifyFieldRowList()
|
|
{
|
|
let fieldRowList = [];
|
|
for (let rawLine of this.GetMsgDefinitionRaw().split("\n"))
|
|
{
|
|
let trimmed = rawLine.trim();
|
|
if (!trimmed || trimmed.startsWith("//"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
let parsed = JSON.parse(trimmed.replace(/,\s*$/, ""));
|
|
if (!parsed || typeof parsed != "object" || Array.isArray(parsed))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (typeof parsed.name != "string" || typeof parsed.unit != "string")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (Array.isArray(parsed.valueSegmentList))
|
|
{
|
|
fieldRowList.push({
|
|
type: "segmented",
|
|
name: parsed.name,
|
|
unit: parsed.unit,
|
|
valueSegmentList: parsed.valueSegmentList,
|
|
});
|
|
}
|
|
else if (typeof parsed.lowValue == "number" && typeof parsed.highValue == "number" && typeof parsed.stepSize == "number")
|
|
{
|
|
fieldRowList.push({
|
|
type: "uniform",
|
|
name: parsed.name,
|
|
unit: parsed.unit,
|
|
lowValue: parsed.lowValue,
|
|
highValue: parsed.highValue,
|
|
stepSize: parsed.stepSize,
|
|
});
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
return [];
|
|
}
|
|
}
|
|
|
|
return fieldRowList;
|
|
}
|
|
|
|
#FormatSegmentListOneLine(valueSegmentList)
|
|
{
|
|
return `[${valueSegmentList
|
|
.map((segment) => `[${segment.map((value) => Number(value).toString()).join(", ")}]`)
|
|
.join(", ")}]`;
|
|
}
|
|
|
|
#BuildAlignedFieldPart(key, valueText, keyWidth, valueWidth = 0, align = "left", withComma = true, padBeforeComma = true)
|
|
{
|
|
let keyText = `"${key}":`;
|
|
let rawValueText = String(valueText);
|
|
let finalValueText = rawValueText;
|
|
if (valueWidth > 0)
|
|
{
|
|
if (align == "right" || padBeforeComma)
|
|
{
|
|
finalValueText = align == "right"
|
|
? rawValueText.padStart(valueWidth)
|
|
: rawValueText.padEnd(valueWidth);
|
|
}
|
|
}
|
|
|
|
if (align == "left" && valueWidth > 0 && padBeforeComma == false)
|
|
{
|
|
let textWithComma = `${rawValueText}${withComma ? "," : ""}`;
|
|
return `${keyText} ${textWithComma.padEnd(valueWidth + (withComma ? 1 : 0))}`;
|
|
}
|
|
|
|
return `${keyText} ${finalValueText}${withComma ? "," : ""}`;
|
|
}
|
|
|
|
#BuildPrettifiedMsgDefinitionText()
|
|
{
|
|
if (this.#CheckMsgDefOk() == false)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
let fieldRowList = this.#ParsePrettifyFieldRowList();
|
|
if (!fieldRowList.length)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
let nameValueList = fieldRowList.map((field) => JSON.stringify(field.name));
|
|
let unitValueList = fieldRowList.map((field) => JSON.stringify(field.unit));
|
|
let lowValueList = fieldRowList.filter((field) => field.type == "uniform").map((field) => Number(field.lowValue).toString());
|
|
let highValueList = fieldRowList.filter((field) => field.type == "uniform").map((field) => Number(field.highValue).toString());
|
|
let stepValueList = fieldRowList.filter((field) => field.type == "uniform").map((field) => Number(field.stepSize).toString());
|
|
let maxNameValueWidth = Math.max(...nameValueList.map((part) => part.length));
|
|
let maxUnitValueWidth = Math.max(...unitValueList.map((part) => part.length));
|
|
let maxLowValueWidth = lowValueList.length ? Math.max(...lowValueList.map((part) => part.length)) : 0;
|
|
let maxHighValueWidth = highValueList.length ? Math.max(...highValueList.map((part) => part.length)) : 0;
|
|
let maxStepValueWidth = stepValueList.length ? Math.max(...stepValueList.map((part) => part.length)) : 0;
|
|
let maxFirstKeyWidth = Math.max(`"name":`.length, `"unit":`.length);
|
|
let namePartWidth = Math.max(...nameValueList.map((value) => this.#BuildAlignedFieldPart("name", value, maxFirstKeyWidth, maxNameValueWidth, "left", true, false).length));
|
|
let unitPartWidth = Math.max(...unitValueList.map((value) => this.#BuildAlignedFieldPart("unit", value, maxFirstKeyWidth, maxUnitValueWidth, "left", true, false).length));
|
|
let lineBodyList = fieldRowList.map((field, index) => {
|
|
let namePart = this.#BuildAlignedFieldPart("name", nameValueList[index], maxFirstKeyWidth, maxNameValueWidth, "left", true, false).padEnd(namePartWidth);
|
|
let unitPart = this.#BuildAlignedFieldPart("unit", unitValueList[index], maxFirstKeyWidth, maxUnitValueWidth, "left", true, false).padEnd(unitPartWidth);
|
|
|
|
if (field.type == "segmented")
|
|
{
|
|
let segmentListText = this.#FormatSegmentListOneLine(field.valueSegmentList);
|
|
let thirdPart = this.#BuildAlignedFieldPart("valueSegmentList", segmentListText, 0, 0, "left", false);
|
|
return `{ ${namePart} ${unitPart} ${thirdPart}`;
|
|
}
|
|
|
|
let thirdPart = this.#BuildAlignedFieldPart("lowValue", Number(field.lowValue).toString(), 0, maxLowValueWidth, "right", true);
|
|
let fourthPart = this.#BuildAlignedFieldPart("highValue", Number(field.highValue).toString(), 0, maxHighValueWidth, "right", true);
|
|
let fifthPart = this.#BuildAlignedFieldPart("stepSize", Number(field.stepSize).toString(), 0, maxStepValueWidth, "right", false);
|
|
return `{ ${namePart} ${unitPart} ${thirdPart} ${fourthPart} ${fifthPart}`;
|
|
});
|
|
|
|
let maxBodyWidth = Math.max(...lineBodyList.map((line) => line.length));
|
|
let finalLineList = lineBodyList.map((line) => `${line.padEnd(maxBodyWidth)} },`);
|
|
return finalLineList.join("\n");
|
|
}
|
|
|
|
#MakeUI()
|
|
{
|
|
// main ui
|
|
let ui = document.createElement('div');
|
|
ui.style.boxSizing = "border-box";
|
|
// ui.style.border = "3px solid red";
|
|
|
|
// input for msg definitions
|
|
this.msgDefInput = this.#MakeMsgDefInput();
|
|
this.msgDefInput.style.marginBottom = "3px";
|
|
ui.appendChild(this.msgDefInput);
|
|
|
|
// make apply button
|
|
this.applyButton = document.createElement('button');
|
|
this.applyButton.innerHTML = "Apply";
|
|
ui.appendChild(this.applyButton);
|
|
|
|
ui.appendChild(document.createTextNode(' '));
|
|
|
|
// make restore last button
|
|
this.restoreButton = document.createElement('button');
|
|
this.restoreButton.innerHTML = "Restore Last Applied";
|
|
ui.appendChild(this.restoreButton);
|
|
|
|
ui.appendChild(document.createTextNode(' '));
|
|
|
|
// make show example button
|
|
this.showExampleButton = document.createElement('button');
|
|
this.showExampleButton.innerHTML = "Show Example";
|
|
ui.appendChild(this.showExampleButton);
|
|
|
|
ui.appendChild(document.createTextNode(' '));
|
|
|
|
// button to prettify the msg def
|
|
this.prettifyButton = document.createElement('button');
|
|
this.prettifyButton.innerHTML = "Prettify";
|
|
ui.appendChild(this.prettifyButton);
|
|
|
|
ui.appendChild(document.createTextNode(' '));
|
|
|
|
// button to upload a msg def json file
|
|
this.uploadButton = document.createElement('button');
|
|
this.uploadButton.innerHTML = "From File";
|
|
ui.appendChild(this.uploadButton);
|
|
|
|
ui.appendChild(document.createTextNode(' '));
|
|
|
|
// button to download the msg def into a json file
|
|
this.downloadButton = document.createElement('button');
|
|
this.downloadButton.innerHTML = "To File";
|
|
ui.appendChild(this.downloadButton);
|
|
|
|
ui.appendChild(document.createTextNode(' '));
|
|
|
|
// button to show/hide msg def analysis
|
|
this.analysisButton = document.createElement('button');
|
|
this.analysisButton.innerHTML = "Show/Hide Analysis";
|
|
ui.appendChild(this.analysisButton);
|
|
|
|
// msg def analysis
|
|
this.codecAnalysis = this.#MakeCodecAnalysis();
|
|
|
|
// dialog for showing msg def analysis
|
|
this.dialogBox = new DialogBox();
|
|
ui.appendChild(this.dialogBox.GetUI());
|
|
this.dialogBox.SetTitleBar(this.namePrefix + (this.name == "" ? "" : ` - ${this.name}`));
|
|
this.dialogBox.GetContentContainer().appendChild(this.codecAnalysis);
|
|
|
|
return ui;
|
|
}
|
|
|
|
#MakeMsgDefInput()
|
|
{
|
|
let dom = document.createElement('textarea');
|
|
dom.style.boxSizing = "border-box";
|
|
dom.spellcheck = false;
|
|
dom.style.backgroundColor = "white";
|
|
dom.placeholder = "// Message Definition goes here";
|
|
|
|
// I want it to take up a row by itself
|
|
dom.style.display = "block";
|
|
|
|
dom.style.minWidth = "800px";
|
|
dom.style.minHeight = "150px";
|
|
|
|
return dom;
|
|
}
|
|
|
|
#MakeCodecAnalysis()
|
|
{
|
|
let dom = document.createElement('div');
|
|
dom.style.boxSizing = "border-box";
|
|
dom.style.backgroundColor = "rgb(234, 234, 234)";
|
|
dom.style.fontFamily = "monospace";
|
|
dom.style.whiteSpace = "pre-wrap";
|
|
dom.style.overflow = "auto";
|
|
dom.style.padding = "2px";
|
|
dom.style.border = "1px solid rgb(118, 118, 118)";
|
|
dom.style.resize = "both";
|
|
dom.style.width = "500px";
|
|
dom.style.height = "190px";
|
|
dom.style.cursor = "default";
|
|
|
|
// make it so flex column container sees this as a whole row
|
|
dom.style.display = "block";
|
|
|
|
dom.style.minWidth = "500px";
|
|
dom.style.minHeight = "190px";
|
|
|
|
return dom;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|