// ── Data & Measurement tab block definitions ─────────────────────────────── // // Blocks that live in the Data tab (variable area) and Measurements tab: // WCQuantity, WCUnit, WCBoolean, WCScalar, WCVector, 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 ─────────────────────────── /** Measurement quantity definition: name, 7 SI base dimensions, min/max range, default unit. */ class WCQuantity extends WCBlock { connectedCallback() { super.connectedCallback(); this._el.name = this._require(".quantity-name"); this._el.dims = Array.from({ length: 7 }, (_, i) => this._require(`.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", () => { const va = $id("variable-area"); if (va) for (const d of va.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._el.name.value = d.name || ""; for (let i = 0; i < 7; i++) { if (this._el.dims[i]) this._el.dims[i].value = d.dims ? d.dims[i] : "0"; } if (this._el.min) this._el.min.value = d.min || ""; if (this._el.max) this._el.max.value = d.max || ""; if (this._el.defaultUnit) this._el.defaultUnit.value = d.defaultUnit || ""; } } // ── WCUnit — unit of measurement ─────────────────────────────────────────── /** Unit of measurement: name, associated quantity, suffix, SI factor, recursive SI resolution. */ class WCUnit extends WCBlock { connectedCallback() { super.connectedCallback(); this._el.fullname = this._require(".unit-fullname"); this._el.type = this._require(".unit-type"); this._el.suffix = this._require(".unit-suffix"); this._el.si = this._require(".unit-si"); this._el.resolved = this._require(".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._el.fullname.value = d.fullname || ""; this._el.type.value = d.type || ""; this._el.suffix.value = d.suffix || ""; this._el.si.value = d.si || "1"; } updateSiDisplay() { const { unitSuffix } = this.parseDefinition(); this._el.resolved.textContent = unitSuffix ? `= ${this.getSiFactor()} SI` : ""; } } // ── WCBoolean — boolean variable ─────────────────────────────────────────── /** Boolean variable: name, true/false value, optional comment. Generates C `int`. */ class WCBoolean extends WCBlock { connectedCallback() { super.connectedCallback(); this._el.name = this._require(".var-name"); this._el.value = this._require(".var-value"); this._el.comment = this._require(".var-comment"); this._el.cname = this._require(".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._el.name.value = d.name || ""; this._el.value.value = d.value || "false"; this._el.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 ── /** Typed scalar variable with quantity, unit, SI conversion, and auto-sync between value and unit. */ class WCScalar extends WCBlock { get dataType() { return "scalar"; } connectedCallback() { super.connectedCallback(); this._el.name = this._require(".var-name"); this._el.value = this._require(".var-value"); this._el.unit = this._require(".var-unit"); this._el.type = this._require(".var-type"); this._el.si = this._require(".var-si"); this._el.comment = this._require(".var-comment"); this._el.cname = this._require(".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._el.name.value = d.name || ""; this._el.value.value = d.value || ""; this._el.type.value = d.type || ""; this._el.unit.value = d.unit || ""; this._el.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};`; } } // ── WCVector — N-dimensional vector with quantity, unit, and SI conversion ── /** N-dimensional vector variable with quantity, unit, SI conversion; generates C array. */ class WCVector extends WCBlock { get dataType() { return "vector"; } connectedCallback() { super.connectedCallback(); this._el.name = this._require(".var-name"); this._el.dim = this._require(".var-dim"); this._el.value = this._require(".var-value"); this._el.unit = this._require(".var-unit"); this._el.type = this._require(".var-type"); this._el.si = this._require(".var-si"); this._el.comment = this._require(".var-comment"); this._el.cname = this._require(".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(); }); } getDim() { return parseInt(this._el.dim.value) || 3; } parseValue() { const { numVal, unitSuffix } = splitValueAndUnit(this._el.value.value, 0); return { numVal, unitSuffix }; } 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 vals = this._el.value.value.trim().split(/\s+/); if (unitSuffix && vals.length > 0 && !isNaN(parseFloat(vals[0]))) { const first = parseFloat(vals[0]); if (vals.length <= 2) { this._el.value.value = first + " " + 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, dim: parseInt(this._el.dim.value) || 3, value: this._el.value.value, type: this._el.type.value, unit: this._el.unit.value, comment: this._el.comment.value }; } restoreData(d) { this._el.name.value = d.name || ""; this._el.dim.value = d.dim || 3; this._el.value.value = d.value || ""; this._el.type.value = d.type || ""; this._el.unit.value = d.unit || ""; this._el.comment.value = d.comment || ""; this._el.cname.textContent = toCName(this._el.name.value); } generateOwnCode() { const name = toCName(this._el.name.value) || "v"; const typeName = this._el.type.value; const dim = this.getDim(); const prefix = commentPrefix(this._el.name.value, this._el.comment.value); const baseType = typeName || "double"; const raw = this._el.value.value.trim(); if (raw) { const parts = raw.split(/\s+/).filter(s => s); const siFactor = this.parseValue().unitSuffix ? this.siValue() / this.parseValue().numVal : 1; const siParts = parts.map(p => { const n = parseFloat(p); return isNaN(n) ? 0 : n * siFactor; }); return `${prefix}${baseType} ${name}[${dim}] = {${siParts.join(", ")}};\n`; } return `${prefix}${baseType} ${name}[${dim}];\n`; } } // ── WCString — string variable ───────────────────────────────────────────── /** String variable: name, value. Generates C `string` struct with malloc/memcpy. */ class WCString extends WCBlock { get dataType() { return "string"; } connectedCallback() { super.connectedCallback(); this._el.name = this._require(".var-name"); this._el.value = this._require(".var-value"); this._el.comment = this._require(".var-comment"); this._el.cname = this._require(".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._el.name.value = d.name || ""; this._el.value.value = d.value || ""; this._el.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 ─────────────────────────────────────── /** Typed C array variable with dynamic element list and optional vector dimension. */ class WCArray extends WCBlock { get dataType() { return "array"; } connectedCallback() { super.connectedCallback(); this._el.name = this._require(".var-name"); this._el.type = this._require(".array-type"); this._el.elements = this._require(".array-elements"); this._el.count = this._require(".array-count"); this._el.comment = this._require(".var-comment"); this._el.cname = this._require(".var-cname"); this._el.vecDim = this.querySelector(".array-vec-dim"); this._el.name.addEventListener("input", () => { this._el.cname.textContent = toCName(this._el.name.value); refreshVarRefOptions(); }); if (this._el.vecDim) { this._el.vecDim.addEventListener("input", () => this._updateCount()); } 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); const row = this._makeElementRow(this._values.length - 1); this._el.elements.insertBefore(row, this._el.elements.lastChild); const newRow = this._el.elements.lastChild; newRow.querySelector(".array-element-idx").textContent = this._values.length; this._updateCount(); } _removeElement(idx) { this._values.splice(idx, 1); const row = this._el.elements.querySelector(`[data-idx="${idx}"]`); if (row) row.remove(); const rows = this._el.elements.children; for (let i = 0; i < rows.length - 1; i++) { rows[i].dataset.idx = i; rows[i].querySelector(".array-element-idx").textContent = i; } this._updateCount(); } _updateCount() { const n = this._values.length; const dim = this.getVecDim(); if (dim > 1) { this._el.count.textContent = n + " element" + (n === 1 ? "" : "s") + " (\u00D7" + dim + " = " + (n * dim) + ")"; } else { this._el.count.textContent = n + " element" + (n === 1 ? "" : "s"); } } getVecDim() { return this._el.vecDim ? (parseInt(this._el.vecDim.value) || 1) : 1; } saveData() { return { name: this._el.name.value, type: this._el.type.value, values: [...this._values], comment: this._el.comment.value, vecDim: this.getVecDim() }; } 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 (this._el.vecDim) this._el.vecDim.value = d.vecDim || 1; 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 dim = this.getVecDim(); const count = this._values.length || 1; const totalSize = dim > 1 ? dim + "*" + count : count; const elements = this._values.filter(s => s).join(", "); const prefix = commentPrefix(this._el.name.value, this._el.comment.value); if (elements && dim <= 1) { return `${prefix}${type} ${name}[${totalSize}] = {${elements}};\n`; } return `${prefix}${type} ${name}[${totalSize}];\n`; } }