Commit pirate JS files
This commit is contained in:
897
js/MsgDefinitionInputUiController.js
Normal file
897
js/MsgDefinitionInputUiController.js
Normal file
@@ -0,0 +1,897 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user