491 lines
16 KiB
JavaScript
491 lines
16 KiB
JavaScript
// ── Data & Measurement tab block definitions ───────────────────────────────
|
|
//
|
|
// Blocks that live in the Data tab (variable area) and Measurements tab:
|
|
// WCQuantity, WCUnit, WCBoolean, WCScalar, WCString, WCArray
|
|
//
|
|
// Each extends WCBlock (from wcblock.js) and provides generateOwnCode().
|
|
//
|
|
// Load order dependencies:
|
|
// wcblock.js → WCBlock base class
|
|
// helpers.js → splitValueAndUnit, rebuildSelect, refreshQuantityOptions,
|
|
// refreshUnitOptions, refreshDefaultUnitOptions, refreshVarRefOptions,
|
|
// getDefaultUnitSuffix, toCName, commentPrefix, escapeString,
|
|
// refreshAll
|
|
// dom.js → $id
|
|
|
|
|
|
// ── WCQuantity — measurement quantity definition ───────────────────────────
|
|
|
|
class WCQuantity extends WCBlock {
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
this._el.name = this.querySelector(".quantity-name");
|
|
this._el.dims = Array.from({ length: 7 }, (_, i) => this.querySelector(`.dim-${i}`));
|
|
this._el.min = this.querySelector(".quantity-min");
|
|
this._el.max = this.querySelector(".quantity-max");
|
|
this._el.defaultUnit = this.querySelector(".quantity-default-unit");
|
|
|
|
for (const inp of this.querySelectorAll("input")) {
|
|
inp.addEventListener("input", () => {
|
|
refreshQuantityOptions();
|
|
refreshDefaultUnitOptions();
|
|
refreshUnitOptions();
|
|
});
|
|
}
|
|
for (const sel of this.querySelectorAll("select")) {
|
|
sel.addEventListener("change", () => {
|
|
for (const d of $id("variable-area").querySelectorAll("wc-scalar")) {
|
|
d.updateSiDisplay();
|
|
}
|
|
});
|
|
}
|
|
setTimeout(() => { refreshQuantityOptions(); refreshDefaultUnitOptions(); }, 0);
|
|
}
|
|
|
|
saveData() {
|
|
return {
|
|
name: this._el.name.value,
|
|
dims: this._el.dims.map(el => el.value),
|
|
min: this._el.min.value,
|
|
max: this._el.max.value,
|
|
defaultUnit: this._el.defaultUnit.value
|
|
};
|
|
}
|
|
restoreData(d) {
|
|
this.querySelector(".quantity-name").value = d.name || "";
|
|
for (let i = 0; i < 7; i++) this.querySelector(`.dim-${i}`).value = d.dims ? d.dims[i] : "0";
|
|
this.querySelector(".quantity-min").value = d.min || "";
|
|
this.querySelector(".quantity-max").value = d.max || "";
|
|
this.querySelector(".quantity-default-unit").value = d.defaultUnit || "";
|
|
}
|
|
}
|
|
|
|
|
|
// ── WCUnit — unit of measurement ───────────────────────────────────────────
|
|
|
|
class WCUnit extends WCBlock {
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
this._el.fullname = this.querySelector(".unit-fullname");
|
|
this._el.type = this.querySelector(".unit-type");
|
|
this._el.suffix = this.querySelector(".unit-suffix");
|
|
this._el.si = this.querySelector(".unit-si");
|
|
this._el.resolved = this.querySelector(".unit-resolved");
|
|
|
|
for (const inp of this.querySelectorAll("input")) {
|
|
inp.addEventListener("input", () => {
|
|
refreshUnitOptions();
|
|
refreshDefaultUnitOptions();
|
|
this.updateSiDisplay();
|
|
});
|
|
}
|
|
for (const sel of this.querySelectorAll("select")) {
|
|
sel.addEventListener("change", () => {
|
|
refreshUnitOptions();
|
|
refreshDefaultUnitOptions();
|
|
});
|
|
}
|
|
setTimeout(() => {
|
|
refreshQuantityOptions();
|
|
refreshDefaultUnitOptions();
|
|
this.updateSiDisplay();
|
|
}, 0);
|
|
}
|
|
|
|
parseDefinition() {
|
|
return splitValueAndUnit(this._el.si.value, 1);
|
|
}
|
|
|
|
getSiFactor(visited) {
|
|
visited = visited || new Set();
|
|
const { numVal, unitSuffix } = this.parseDefinition();
|
|
if (!unitSuffix || visited.has(this.id)) return numVal;
|
|
for (const u of $id("data-area").querySelectorAll("wc-unit")) {
|
|
if (u.querySelector(".unit-suffix").value === unitSuffix) {
|
|
visited.add(this.id);
|
|
return numVal * u.getSiFactor(visited);
|
|
}
|
|
}
|
|
return numVal;
|
|
}
|
|
|
|
saveData() {
|
|
return {
|
|
fullname: this._el.fullname.value,
|
|
type: this._el.type.value,
|
|
suffix: this._el.suffix.value,
|
|
si: this._el.si.value
|
|
};
|
|
}
|
|
restoreData(d) {
|
|
this.querySelector(".unit-fullname").value = d.fullname || "";
|
|
this.querySelector(".unit-type").value = d.type || "";
|
|
this.querySelector(".unit-suffix").value = d.suffix || "";
|
|
this.querySelector(".unit-si").value = d.si || "1";
|
|
}
|
|
|
|
updateSiDisplay() {
|
|
const { unitSuffix } = this.parseDefinition();
|
|
this._el.resolved.textContent = unitSuffix
|
|
? `= ${this.getSiFactor()} SI`
|
|
: "";
|
|
}
|
|
}
|
|
|
|
|
|
// ── WCBoolean — boolean variable ───────────────────────────────────────────
|
|
|
|
class WCBoolean extends WCBlock {
|
|
get dataType() { return "boolean"; }
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
this._el.name = this.querySelector(".var-name");
|
|
this._el.value = this.querySelector(".var-value");
|
|
this._el.comment = this.querySelector(".var-comment");
|
|
this._el.cname = this.querySelector(".var-cname");
|
|
this._el.name.addEventListener("input", () => {
|
|
this._el.cname.textContent = toCName(this._el.name.value);
|
|
});
|
|
}
|
|
|
|
saveData() { return { name: this._el.name.value, value: this._el.value.value, comment: this._el.comment.value }; }
|
|
restoreData(d) {
|
|
this.querySelector(".var-name").value = d.name || "";
|
|
this.querySelector(".var-value").value = d.value || "false";
|
|
this.querySelector(".var-comment").value = d.comment || "";
|
|
this._el.cname.textContent = toCName(this._el.name.value);
|
|
}
|
|
|
|
generateOwnCode() {
|
|
const name = toCName(this._el.name.value) || "flag";
|
|
const raw = this._el.value.value;
|
|
const numeric = raw === "true" ? 1 : 0;
|
|
const prefix = commentPrefix(this._el.name.value, this._el.comment.value);
|
|
return `${prefix}int ${name} = ${numeric};`;
|
|
}
|
|
}
|
|
|
|
|
|
// ── WCScalar — typed scalar variable with quantity, unit, and SI conversion ──
|
|
|
|
class WCScalar extends WCBlock {
|
|
get dataType() { return "scalar"; }
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
this._el.name = this.querySelector(".var-name");
|
|
this._el.value = this.querySelector(".var-value");
|
|
this._el.unit = this.querySelector(".var-unit");
|
|
this._el.type = this.querySelector(".var-type");
|
|
this._el.si = this.querySelector(".var-si");
|
|
this._el.comment = this.querySelector(".var-comment");
|
|
this._el.cname = this.querySelector(".var-cname");
|
|
|
|
this._el.name.addEventListener("input", () => {
|
|
this._el.cname.textContent = toCName(this._el.name.value);
|
|
});
|
|
|
|
setTimeout(() => {
|
|
refreshQuantityOptions();
|
|
refreshUnitOptions();
|
|
this.updateSiDisplay();
|
|
}, 0);
|
|
|
|
this._el.value.addEventListener("input", () => {
|
|
this.syncUnitFromValue();
|
|
this.updateSiDisplay();
|
|
});
|
|
this._el.unit.addEventListener("change", () => {
|
|
this.syncValueFromUnit();
|
|
this.updateSiDisplay();
|
|
});
|
|
this._el.type.addEventListener("change", () => {
|
|
refreshUnitOptions();
|
|
this.updateSiDisplay();
|
|
});
|
|
}
|
|
|
|
parseValue() {
|
|
return splitValueAndUnit(this._el.value.value, 0);
|
|
}
|
|
|
|
siValue() {
|
|
const { numVal, unitSuffix } = this.parseValue();
|
|
if (!unitSuffix) return numVal;
|
|
const quantityName = this._el.type.value;
|
|
for (const u of $id("data-area").querySelectorAll("wc-unit")) {
|
|
if (
|
|
u.querySelector(".unit-type").value === quantityName &&
|
|
u.querySelector(".unit-suffix").value === unitSuffix
|
|
) {
|
|
return numVal * u.getSiFactor();
|
|
}
|
|
}
|
|
return numVal;
|
|
}
|
|
|
|
syncUnitFromValue() {
|
|
const { unitSuffix } = this.parseValue();
|
|
if (unitSuffix && [...this._el.unit.options].some(o => o.value === unitSuffix)) {
|
|
this._el.unit.value = unitSuffix;
|
|
}
|
|
}
|
|
|
|
syncValueFromUnit() {
|
|
const unitSuffix = this._el.unit.value;
|
|
const { numVal } = this.parseValue();
|
|
const raw = (this._el.value.value || "0").trim();
|
|
if (unitSuffix && raw.split(/\s+/).length <= 1 && !isNaN(parseFloat(raw))) {
|
|
this._el.value.value = numVal + " " + unitSuffix;
|
|
}
|
|
}
|
|
|
|
updateSiDisplay() {
|
|
const typeName = this._el.type.value;
|
|
const { unitSuffix } = this.parseValue();
|
|
if (unitSuffix && typeName) {
|
|
const si = this.siValue();
|
|
const defaultUnit = getDefaultUnitSuffix(typeName);
|
|
this._el.si.textContent = defaultUnit
|
|
? `= ${si} ${defaultUnit}`
|
|
: `= ${si} SI`;
|
|
} else {
|
|
this._el.si.textContent = "";
|
|
}
|
|
}
|
|
|
|
saveData() {
|
|
return {
|
|
name: this._el.name.value,
|
|
value: this._el.value.value,
|
|
type: this._el.type.value,
|
|
unit: this._el.unit.value,
|
|
comment: this._el.comment.value
|
|
};
|
|
}
|
|
restoreData(d) {
|
|
this.querySelector(".var-name").value = d.name || "";
|
|
this.querySelector(".var-value").value = d.value || "";
|
|
this.querySelector(".var-type").value = d.type || "";
|
|
this.querySelector(".var-unit").value = d.unit || "";
|
|
this.querySelector(".var-comment").value = d.comment || "";
|
|
this._el.cname.textContent = toCName(this._el.name.value);
|
|
}
|
|
|
|
generateOwnCode() {
|
|
const name = toCName(this._el.name.value) || "x";
|
|
const typeName = this._el.type.value;
|
|
const si = this.siValue();
|
|
const prefix = commentPrefix(this._el.name.value, this._el.comment.value);
|
|
return typeName
|
|
? `${prefix}${typeName} ${name} = ${si};`
|
|
: `${prefix}double ${name} = ${si};`;
|
|
}
|
|
}
|
|
|
|
|
|
// ── WCString — string variable ─────────────────────────────────────────────
|
|
|
|
class WCString extends WCBlock {
|
|
get dataType() { return "string"; }
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
this._el.name = this.querySelector(".var-name");
|
|
this._el.value = this.querySelector(".var-value");
|
|
this._el.comment = this.querySelector(".var-comment");
|
|
this._el.cname = this.querySelector(".var-cname");
|
|
this._el.name.addEventListener("input", () => {
|
|
this._el.cname.textContent = toCName(this._el.name.value);
|
|
});
|
|
}
|
|
|
|
saveData() { return { name: this._el.name.value, value: this._el.value.value, comment: this._el.comment.value }; }
|
|
restoreData(d) {
|
|
this.querySelector(".var-name").value = d.name || "";
|
|
this.querySelector(".var-value").value = d.value || "";
|
|
this.querySelector(".var-comment").value = d.comment || "";
|
|
this._el.cname.textContent = toCName(this._el.name.value);
|
|
}
|
|
|
|
generateOwnCode() {
|
|
const name = toCName(this._el.name.value) || "s";
|
|
const raw = this._el.value.value || "";
|
|
const escaped = escapeString(raw);
|
|
const len = raw.length;
|
|
const prefix = commentPrefix(this._el.name.value, this._el.comment.value);
|
|
return `${prefix}string ${name} = {0};\n${name}.length = ${len};\n${name}.data = malloc(${len} + 1);\nmemcpy(${name}.data, "${escaped}", ${len});\n${name}.data[${len}] = 0;\n`;
|
|
}
|
|
}
|
|
|
|
|
|
// ── WCArray — typed C array variable ───────────────────────────────────────
|
|
|
|
class WCArray extends WCBlock {
|
|
get dataType() { return "array"; }
|
|
|
|
constructor() {
|
|
super();
|
|
this._values = [];
|
|
}
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
this._el.name = this.querySelector(".var-name");
|
|
this._el.type = this.querySelector(".array-type");
|
|
this._el.elements = this.querySelector(".array-elements");
|
|
this._el.count = this.querySelector(".array-count");
|
|
this._el.comment = this.querySelector(".var-comment");
|
|
this._el.cname = this.querySelector(".var-cname");
|
|
|
|
this._el.name.addEventListener("input", () => {
|
|
this._el.cname.textContent = toCName(this._el.name.value);
|
|
refreshVarRefOptions();
|
|
});
|
|
setTimeout(() => this._renderElements(), 0);
|
|
}
|
|
|
|
_renderElements() {
|
|
const focused = this._el.elements.querySelector("input:focus");
|
|
let focusIdx = -1, focusOff = 0;
|
|
if (focused) {
|
|
const row = focused.closest(".array-element-row");
|
|
if (row && row.dataset.idx) {
|
|
focusIdx = parseInt(row.dataset.idx);
|
|
focusOff = focused.selectionStart;
|
|
}
|
|
}
|
|
|
|
this._el.elements.innerHTML = "";
|
|
|
|
for (let i = 0; i < this._values.length; i++) {
|
|
this._el.elements.appendChild(this._makeElementRow(i));
|
|
}
|
|
|
|
this._el.elements.appendChild(this._makeNewRow());
|
|
this._updateCount();
|
|
|
|
if (focusIdx >= 0 && focusIdx < this._values.length) {
|
|
const row = this._el.elements.querySelector(`[data-idx="${focusIdx}"]`);
|
|
if (row) {
|
|
const inp = row.querySelector(".array-element-value");
|
|
if (inp) { inp.focus(); inp.selectionStart = inp.selectionEnd = Math.min(focusOff, inp.value.length); }
|
|
}
|
|
}
|
|
}
|
|
|
|
_makeElementRow(i) {
|
|
const row = document.createElement("div");
|
|
row.className = "array-element-row";
|
|
row.dataset.idx = i;
|
|
|
|
const idxSpan = document.createElement("span");
|
|
idxSpan.className = "array-element-idx";
|
|
idxSpan.textContent = i;
|
|
row.appendChild(idxSpan);
|
|
|
|
const input = document.createElement("input");
|
|
input.className = "array-element-value";
|
|
input.value = this._values[i];
|
|
input.addEventListener("change", () => {
|
|
const ix = parseInt(input.closest(".array-element-row").dataset.idx);
|
|
this._values[ix] = input.value;
|
|
});
|
|
row.appendChild(input);
|
|
|
|
const delBtn = document.createElement("button");
|
|
delBtn.className = "array-element-del";
|
|
delBtn.textContent = "\u00D7";
|
|
delBtn.title = "remove";
|
|
delBtn.addEventListener("click", () => {
|
|
const ix = parseInt(delBtn.closest(".array-element-row").dataset.idx);
|
|
this._removeElement(ix);
|
|
});
|
|
row.appendChild(delBtn);
|
|
|
|
return row;
|
|
}
|
|
|
|
_makeNewRow() {
|
|
const row = document.createElement("div");
|
|
row.className = "array-element-row";
|
|
|
|
const idxSpan = document.createElement("span");
|
|
idxSpan.className = "array-element-idx";
|
|
idxSpan.textContent = this._values.length;
|
|
row.appendChild(idxSpan);
|
|
|
|
const input = document.createElement("input");
|
|
input.className = "array-element-value";
|
|
input.placeholder = "new element";
|
|
input.addEventListener("keydown", e => {
|
|
if (e.key === "Enter" && input.value.trim()) {
|
|
const val = input.value.trim();
|
|
input.value = "";
|
|
this._addElement(val);
|
|
input.focus();
|
|
}
|
|
});
|
|
input.addEventListener("blur", () => {
|
|
if (input.value.trim()) {
|
|
const val = input.value.trim();
|
|
input.value = "";
|
|
this._addElement(val);
|
|
}
|
|
});
|
|
row.appendChild(input);
|
|
|
|
return row;
|
|
}
|
|
|
|
_addElement(value) {
|
|
this._values.push(value);
|
|
this._renderElements();
|
|
}
|
|
|
|
_removeElement(idx) {
|
|
this._values.splice(idx, 1);
|
|
this._renderElements();
|
|
}
|
|
|
|
_updateCount() {
|
|
const n = this._values.length;
|
|
this._el.count.textContent = n + " element" + (n === 1 ? "" : "s");
|
|
}
|
|
|
|
saveData() {
|
|
return {
|
|
name: this._el.name.value,
|
|
type: this._el.type.value,
|
|
values: [...this._values],
|
|
comment: this._el.comment.value
|
|
};
|
|
}
|
|
restoreData(d) {
|
|
this._el.name.value = d.name || "arr";
|
|
this._el.type.value = d.type || "float";
|
|
this._el.comment.value = d.comment || "";
|
|
this._el.cname.textContent = toCName(this._el.name.value);
|
|
if (Array.isArray(d.values)) {
|
|
this._values = [...d.values];
|
|
} else if (typeof d.values === "string" && d.values) {
|
|
this._values = d.values.split(",").map(s => s.trim()).filter(s => s);
|
|
} else {
|
|
this._values = [];
|
|
}
|
|
this._renderElements();
|
|
}
|
|
|
|
generateOwnCode() {
|
|
const name = toCName(this._el.name.value) || "arr";
|
|
const type = this._el.type.value || "float";
|
|
const elements = this._values.filter(s => s).join(", ");
|
|
const prefix = commentPrefix(this._el.name.value, this._el.comment.value);
|
|
if (elements) {
|
|
return `${prefix}${type} ${name}[] = {${elements}};\n`;
|
|
}
|
|
return `${prefix}${type} ${name}[1];\n`;
|
|
}
|
|
}
|