v0.16.3
This commit is contained in:
1 parent
f376f0a7b6
commit
8059e16d12
36 files changed
+1140
-193
No files matched your search
+75
-6
@@ -23,11 +23,12 @@ const FLOW_NODE_META = Object.freeze({
|
||||
zone_state: { titleKey: 'flow.node.zoneState', category: 'sensor' },
|
||||
group_state: { titleKey: 'flow.node.groupState', category: 'sensor' },
|
||||
night_mode: { titleKey: 'flow.node.nightMode', category: 'time' },
|
||||
constant: { titleKey: 'flow.node.constant', category: 'sensor' },
|
||||
constant: { titleKey: 'flow.node.constant', category: 'sensor', descriptionKey: 'flow.constantDescription' },
|
||||
shared_input: { titleKey: 'flow.node.sharedInput', category: 'sensor' },
|
||||
logic_and: { titleKey: 'flow.node.and', category: 'logic' },
|
||||
logic_or: { titleKey: 'flow.node.or', category: 'logic' },
|
||||
logic_not: { titleKey: 'flow.node.not', category: 'logic' },
|
||||
zone_heat_cycle: { titleKey: 'flow.node.simpleHeat', category: 'action', descriptionKey: 'flow.simpleHeatDescription' },
|
||||
zone_thermostat: { titleKey: 'flow.node.thermostat', category: 'action' },
|
||||
device_action: { titleKey: 'flow.node.greeDevice', category: 'action' },
|
||||
device_feature_action: { titleKey: 'flow.node.greeFeature', category: 'action' },
|
||||
@@ -98,6 +99,7 @@ function flowDefaultConfig(kind) {
|
||||
if (kind === 'night_mode') return {};
|
||||
if (kind === 'constant') return { value: true };
|
||||
if (kind === 'shared_input') return sharedFlowReferenceDefaultConfig(app.flowSharedInputs?.[0]);
|
||||
if (kind === 'zone_heat_cycle') return { zone_id: app.zones[0]?.id || '', day_target: 20.0, night_target: 20.6, day_min: 19.7, boost_after_minutes: 10, day_off_after_minutes: 30, cooldown_seconds: 60 };
|
||||
if (kind === 'zone_thermostat') return { zone_id: app.zones[0]?.id || '', preset: 'comfort', setpoint: 21, mode: 'auto', power: null, heat_anticipation_c: null, standby_offset_c: null, swing_vertical: null, swing_horizontal: null, cooldown_seconds: 60 };
|
||||
if (kind === 'device_action') return { device_id: app.devices[0]?.id || '', power: true, mode: '', target_temperature: null, fan_speed: null, swing_vertical: null, swing_horizontal: null, quiet: null, turbo: null, light: null, air: null, xfan: null, health: null, sleep: null, cooldown_seconds: 60 };
|
||||
if (kind === 'device_feature_action') return { device_id: app.devices[0]?.id || '', feature: 'light', value: true, cooldown_seconds: 60 };
|
||||
@@ -424,6 +426,10 @@ function flowNodeSummary(node) {
|
||||
if (node.kind === 'logic_and') return tr('flow.allConditions');
|
||||
if (node.kind === 'logic_or') return tr('flow.anyCondition');
|
||||
if (node.kind === 'logic_not') return tr('flow.invertCondition');
|
||||
if (node.kind === 'zone_heat_cycle') {
|
||||
const zone = app.zones.find(z => z.id === c.zone_id);
|
||||
return `${zone?.name || tr('common.noZone')} · ${tr('flow.simpleHeatDay')} ${fmtTemp(c.day_target)} · ${tr('flow.simpleHeatNight')} ${fmtTemp(c.night_target)}`;
|
||||
}
|
||||
if (node.kind === 'zone_thermostat') {
|
||||
const zone = app.zones.find(z => z.id === c.zone_id);
|
||||
const power = c.power == null ? null : (c.power ? tr('common.on') : tr('common.off'));
|
||||
@@ -627,10 +633,18 @@ function renderFlowBlockLibrary(filter = '') {
|
||||
const groups = $$('.flow-palette .flow-palette-group');
|
||||
host.innerHTML = groups.map(group => {
|
||||
const title = group.querySelector(':scope > span')?.textContent?.trim() || '';
|
||||
const buttons = $$('[data-flow-add]', group).filter(button => !query || `${title} ${button.textContent}`.toLocaleLowerCase(locale()).includes(query));
|
||||
const buttons = $$('[data-flow-add]', group).filter(button => {
|
||||
const meta = FLOW_NODE_META[button.dataset.flowAdd];
|
||||
const description = meta?.descriptionKey ? tr(meta.descriptionKey) : '';
|
||||
return !query || `${title} ${button.textContent} ${description}`.toLocaleLowerCase(locale()).includes(query);
|
||||
});
|
||||
if (!buttons.length) return '';
|
||||
const categoryClass = [...group.classList].find(name => name.startsWith('block-category-')) || '';
|
||||
return `<section class="flow-block-library-group ${esc(categoryClass)}"><h3>${esc(title)}</h3><div>${buttons.map(button => `<button type="button" class="secondary" data-flow-add="${esc(button.dataset.flowAdd)}">${esc(button.textContent.trim())}</button>`).join('')}</div></section>`;
|
||||
return `<section class="flow-block-library-group ${esc(categoryClass)}"><h3>${esc(title)}</h3><div>${buttons.map(button => {
|
||||
const meta = FLOW_NODE_META[button.dataset.flowAdd];
|
||||
const description = meta?.descriptionKey ? tr(meta.descriptionKey) : '';
|
||||
return `<button type="button" class="secondary" data-flow-add="${esc(button.dataset.flowAdd)}" ${description ? `title="${esc(description)}"` : ''}><span>${esc(button.textContent.trim())}</span>${description ? `<small class="flow-block-library-description">${esc(description)}</small>` : ''}</button>`;
|
||||
}).join('')}</div></section>`;
|
||||
}).join('') || `<div class="empty compact"><strong>${esc(tr('flow.noBlocksFound'))}</strong><span>${esc(tr('flow.noBlocksFoundHint'))}</span></div>`;
|
||||
}
|
||||
|
||||
@@ -647,7 +661,7 @@ function renderFlowEditor() {
|
||||
const nodesHost = $('#flowNodes');
|
||||
nodesHost.innerHTML = draft.nodes.map(node => {
|
||||
const meta = FLOW_NODE_META[node.kind] || { title: node.kind, category: 'logic' };
|
||||
return `<article class="diagram-node flow-node-${esc(meta.category)} ${(app.flowSelectedNodeIds || []).includes(node.id) ? 'selected' : ''}" data-flow-node="${esc(node.id)}" style="left:${Number(node.x || 0)}px;top:${Number(node.y || 0)}px">
|
||||
return `<article class="diagram-node flow-node-${esc(meta.category)} ${(app.flowSelectedNodeIds || []).includes(node.id) ? 'selected' : ''}" data-flow-node="${esc(node.id)}" ${meta.descriptionKey ? `title="${esc(tr(meta.descriptionKey))}"` : ''} style="left:${Number(node.x || 0)}px;top:${Number(node.y || 0)}px">
|
||||
<button class="diagram-port flow-port-in" type="button" data-flow-input="${esc(node.id)}" title="${esc(tr('flow.connectHere'))}"></button>
|
||||
<div class="flow-node-head"><span>${esc(flowNodeTitle(meta))}</span><button type="button" data-flow-remove="${esc(node.id)}" aria-label="${esc(tr('actions.delete'))}">${uiIcon('close')}</button></div>
|
||||
<div class="flow-node-body">${esc(flowNodeSummary(node))}${node.kind === 'shared_input' && node.config?.input_id ? `<div class="flow-node-current" data-flow-shared-current="${esc(node.config.input_id)}"><span>${esc(tr('flow.sharedInputTestCurrent'))}</span><strong>—</strong></div>` : ''}</div>
|
||||
@@ -773,11 +787,50 @@ function flowIncomingHeatingCurve(nodeId) {
|
||||
return edge ? flowNodeById(edge.from) : null;
|
||||
}
|
||||
|
||||
// The inspector derives all examples from this block's room targets; GREE
|
||||
// accepts whole-degree setpoints but the room targets may have tenths.
|
||||
function flowSimpleHeatHelp(config, nodeId = '', expanded = false) {
|
||||
const day = Number.isFinite(Number(config.day_target)) ? Number(config.day_target) : 20;
|
||||
const night = Number.isFinite(Number(config.night_target)) ? Number(config.night_target) : 20.6;
|
||||
const minimum = Number.isFinite(Number(config.day_min)) ? Number(config.day_min) : 19.7;
|
||||
const boostMinutes = Number.isFinite(Number(config.boost_after_minutes)) ? Number(config.boost_after_minutes) : 10;
|
||||
const offMinutes = Number.isFinite(Number(config.day_off_after_minutes)) ? Number(config.day_off_after_minutes) : 30;
|
||||
const nightGentle = Math.min(30, Math.max(16, Math.floor(night) + 1));
|
||||
const nightBoost = Math.min(30, Math.max(16, Math.ceil(night + 2)));
|
||||
const nightBrake = Math.min(30, Math.max(16, Math.floor(night - 0.5)));
|
||||
const nightStandby = Math.min(30, Math.max(16, Math.floor(night - 1)));
|
||||
const dayGentle = Math.min(30, Math.max(16, Math.floor(day) + 1));
|
||||
const dayBoost = Math.min(30, Math.max(16, Math.ceil(day + 2)));
|
||||
const line = (key, params) => `<li>${esc(tr(key, params))}</li>`;
|
||||
return `<details class="flow-block-help" data-flow-help-node="${esc(nodeId)}" ${expanded ? 'open' : ''}>
|
||||
<summary>${esc(tr('flow.simpleHeatHowItWorks'))}</summary>
|
||||
<p>${esc(tr('flow.simpleHeatHelpIntro'))}</p>
|
||||
<strong>${esc(tr('flow.simpleHeatHelpNightTitle', { target: fmtTemp(night) }))}</strong>
|
||||
<ul>
|
||||
${line('flow.simpleHeatHelpNightSleep', { room: fmtTemp(night), gree: fmtTemp(nightStandby) })}
|
||||
${line('flow.simpleHeatHelpNightBrake', { room: fmtTemp(night - 0.1), gree: fmtTemp(nightBrake) })}
|
||||
${line('flow.simpleHeatHelpNightGentle', { room: fmtTemp(night - 0.1), gree: fmtTemp(nightGentle) })}
|
||||
${line('flow.simpleHeatHelpNightBoost', { room: fmtTemp(night - 0.8), minutes: boostMinutes, gree: fmtTemp(nightBoost) })}
|
||||
</ul>
|
||||
<strong>${esc(tr('flow.simpleHeatHelpDayTitle', { target: fmtTemp(day), minimum: fmtTemp(minimum) }))}</strong>
|
||||
<ul>
|
||||
${line('flow.simpleHeatHelpDayOff', { minimum: fmtTemp(minimum) })}
|
||||
${line('flow.simpleHeatHelpDayHeat', { gentle: fmtTemp(dayGentle), boost: fmtTemp(dayBoost), room: fmtTemp(day - 0.8), minutes: boostMinutes })}
|
||||
${line('flow.simpleHeatHelpDayStop', { target: fmtTemp(day), minutes: offMinutes })}
|
||||
</ul>
|
||||
<p>${esc(tr('flow.simpleHeatHelpSensors'))}</p>
|
||||
<p>${esc(tr('flow.simpleHeatHelpSchedule'))}</p>
|
||||
<p>${esc(tr('flow.simpleHeatHelpControl'))}</p>
|
||||
</details>`;
|
||||
}
|
||||
|
||||
function renderFlowInspector() {
|
||||
const host = $('#flowInspector'), node = flowNodeById(app.flowSelectedNodeId); if (!host) return;
|
||||
host.classList.toggle('has-selection', Boolean(node));
|
||||
if (!node) { host.classList.remove('is-expanded'); host.innerHTML = `<div class="empty compact"><strong>${esc(tr('flow.selectBlock'))}</strong><span>${esc(tr('flow.selectBlockHint'))}</span></div>`; return; }
|
||||
const c = node.config || {}, meta = FLOW_NODE_META[node.kind] || { title: node.kind };
|
||||
const currentHelp = host.querySelector('.flow-block-help');
|
||||
const helpOpen = Boolean(currentHelp?.open && currentHelp.dataset.flowHelpNode === node.id);
|
||||
let fields = '';
|
||||
if (node.kind === 'weekday') fields = `<div class="flow-day-grid">${[1, 2, 3, 4, 5, 6, 7].map(day => `<label><input type="checkbox" data-flow-config="days" value="${day}" ${(c.days || []).includes(day) ? 'checked' : ''}><span>${esc(tr(`day.${day}`))}</span></label>`).join('')}</div>`;
|
||||
else if (node.kind === 'time_range') fields = `<div class="two"><label><span>${esc(tr('common.from'))}</span><input type="time" data-flow-config="start" value="${esc(c.start || '06:00')}"></label><label><span>${esc(tr('common.to'))}</span><input type="time" data-flow-config="end" value="${esc(c.end || '08:00')}"></label></div>`;
|
||||
@@ -803,7 +856,7 @@ function renderFlowInspector() {
|
||||
else if (node.kind === 'zone_state') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-flow-config="field">${['enabled', 'mode', 'active_preset', 'demand', 'control_owner', 'device_manual_override', 'local_thermostat_power'].map(v => `<option value="${v}" ${c.field === v ? 'selected' : ''}>${esc(v)}</option>`).join('')}</select></label>${flowTextComparisonFields(c)}`;
|
||||
else if (node.kind === 'group_state') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-flow-config="group_id">${flowSelectOptions(app.groups, c.group_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-flow-config="field"><option value="power_enabled" selected>power_enabled</option></select></label>${flowTextComparisonFields(c)}`;
|
||||
else if (node.kind === 'night_mode') fields = `<p class="field-note">${esc(tr('flow.nightModeHint'))}</p>`;
|
||||
else if (node.kind === 'constant') fields = `<label><span>${esc(tr('flow.value'))}</span><select data-flow-config="value"><option value="true" ${c.value !== false ? 'selected' : ''}>${esc(tr('common.yes'))}</option><option value="false" ${c.value === false ? 'selected' : ''}>${esc(tr('common.no'))}</option></select></label>`;
|
||||
else if (node.kind === 'constant') fields = `<label><span>${esc(tr('flow.value'))}</span><select data-flow-config="value"><option value="true" ${c.value !== false ? 'selected' : ''}>${esc(tr('common.yes'))}</option><option value="false" ${c.value === false ? 'selected' : ''}>${esc(tr('common.no'))}</option></select></label><p class="field-note">${esc(tr('flow.constantHelp'))}</p>`;
|
||||
else if (node.kind === 'shared_input') {
|
||||
const item = (app.flowSharedInputs || []).find(value => value.id === c.input_id) || app.flowSharedInputs?.[0];
|
||||
fields = (app.flowSharedInputs || []).length
|
||||
@@ -813,6 +866,19 @@ function renderFlowInspector() {
|
||||
else if (node.kind === 'logic_and') fields = `<p class="field-note">${esc(tr('flow.andHint'))}</p>`;
|
||||
else if (node.kind === 'logic_or') fields = `<p class="field-note">${esc(tr('flow.orHint'))}</p>`;
|
||||
else if (node.kind === 'logic_not') fields = `<p class="field-note">${esc(tr('flow.notHint'))}</p>`;
|
||||
else if (node.kind === 'zone_heat_cycle') fields = `
|
||||
<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label>
|
||||
<div class="two">
|
||||
<label title="${esc(tr('flow.simpleHeatDayTip'))}"><span>${esc(tr('flow.simpleHeatDay'))} °C</span><input type="number" min="17" max="28" step="0.1" data-flow-config="day_target" value="${Number(c.day_target ?? 20)}"></label>
|
||||
<label title="${esc(tr('flow.simpleHeatNightTip'))}"><span>${esc(tr('flow.simpleHeatNight'))} °C</span><input type="number" min="17" max="28" step="0.1" data-flow-config="night_target" value="${Number(c.night_target ?? 20.6)}"></label>
|
||||
</div>
|
||||
<label title="${esc(tr('flow.simpleHeatMinimumTip'))}"><span>${esc(tr('flow.simpleHeatMinimum'))} °C</span><input type="number" min="17" max="28" step="0.1" data-flow-config="day_min" value="${Number(c.day_min ?? 19.7)}"></label>
|
||||
<div class="two">
|
||||
<label title="${esc(tr('flow.simpleHeatBoostTip'))}"><span>${esc(tr('flow.simpleHeatBoostMinutes'))}</span><input type="number" min="3" max="30" step="1" data-flow-config="boost_after_minutes" value="${Number(c.boost_after_minutes ?? 10)}"></label>
|
||||
<label title="${esc(tr('flow.simpleHeatOffTip'))}"><span>${esc(tr('flow.simpleHeatOffMinutes'))}</span><input type="number" min="10" max="120" step="1" data-flow-config="day_off_after_minutes" value="${Number(c.day_off_after_minutes ?? 30)}"></label>
|
||||
</div>
|
||||
<p class="field-note">${esc(tr('flow.simpleHeatHint'))}</p>
|
||||
${flowSimpleHeatHelp(c, node.id, helpOpen)}`;
|
||||
else if (node.kind === 'zone_thermostat') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label><div class="two"><label><span>${esc(tr('schedules.profile'))}</span><select data-flow-config="preset">${['auto', 'comfort', 'sleep', 'away', 'custom'].map(v => `<option value="${v}" ${c.preset === v ? 'selected' : ''}>${esc(zonePresetLabel(v))}</option>`).join('')}</select></label><label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.1" data-flow-config="setpoint" value="${Number(c.setpoint ?? 21)}"></label></div><div class="two"><label><span>${esc(tr('common.mode'))}</span><select data-flow-config="mode"><option value="auto" ${c.mode === 'auto' ? 'selected' : ''}>Auto</option><option value="heat" ${c.mode === 'heat' ? 'selected' : ''}>${esc(tr('mode.heat'))}</option><option value="cool" ${c.mode === 'cool' ? 'selected' : ''}>${esc(tr('mode.cool'))}</option></select></label><label><span>${esc(tr('common.power'))}</span><select data-flow-config="power"><option value="" ${c.power == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option><option value="true" ${c.power === true ? 'selected' : ''}>${esc(tr('common.on'))}</option><option value="false" ${c.power === false ? 'selected' : ''}>${esc(tr('common.off'))}</option></select></label></div><div class="two"><label><span>${esc(tr('zones.heatAnticipation'))}</span><input type="number" min="0" max="2.5" step="0.1" data-flow-config="heat_anticipation_c" value="${c.heat_anticipation_c == null ? '' : Number(c.heat_anticipation_c)}" placeholder="—"></label><label><span>${esc(tr('zones.standbyOffset'))}</span><input type="number" min="0.5" max="8" step="0.1" data-flow-config="standby_offset_c" value="${c.standby_offset_c == null ? '' : Number(c.standby_offset_c)}" placeholder="—"></label></div><details class="flow-device-options"><summary>${esc(tr('flow.thermostatDeviceOptions'))}</summary><div class="two">${flowOptionalLouverField(c, 'swing_vertical', 'vertical')}${flowOptionalLouverField(c, 'swing_horizontal', 'horizontal')}</div><p class="field-note">${esc(tr('flow.thermostatDeviceOptionsHint'))}</p></details><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
||||
else if (node.kind === 'device_action') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label>${flowActionFields(c, false)}`;
|
||||
else if (node.kind === 'device_feature_action') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label><label><span>${esc(tr('flow.deviceFeature'))}</span><select data-flow-config="feature">${flowDeviceFeatureOptions(c.feature)}</select></label>${flowDeviceFeatureValueField(c)}<p class="field-note">${esc(tr('flow.deviceFeatureHint'))}</p><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
||||
@@ -826,7 +892,8 @@ function renderFlowInspector() {
|
||||
} else if (incomingCurve && node.kind === 'ha_service_action') {
|
||||
fields += `<label><span>${esc(tr('flow.heatingCurveHaValueKey'))}</span><input data-flow-config="value_key" value="${esc(c.value_key || 'temperature')}" placeholder="temperature"></label><p class="field-note flow-value-binding-note">${esc(tr('flow.heatingCurveHaBindingHint'))}</p>`;
|
||||
}
|
||||
host.innerHTML = `<div class="flow-inspector-head"><div><span class="eyebrow">${esc(tr('flow.blockSettings'))}</span><h3>${esc(flowNodeTitle(meta))}</h3></div><div class="flow-inspector-head-actions"><button type="button" class="secondary flow-inspector-expand" data-action="flow-toggle-inspector" aria-label="${esc(tr('flow.expandSettings'))}">${uiIcon('expand-vertical')}</button><button type="button" class="secondary flow-inspector-close" data-action="flow-clear-selection" aria-label="${esc(tr('flow.clearSelection'))}">${uiIcon('close')}</button><button type="button" class="danger flow-inspector-delete" data-flow-remove="${esc(node.id)}">${esc(tr('actions.delete'))}</button></div></div>${fields}<div class="flow-inspector-meta"><small>ID</small><code>${esc(node.id)}</code></div>`;
|
||||
const description = meta.descriptionKey ? `<p class="flow-inspector-description">${esc(tr(meta.descriptionKey))}</p>` : '';
|
||||
host.innerHTML = `<div class="flow-inspector-head"><div><span class="eyebrow">${esc(tr('flow.blockSettings'))}</span><h3>${esc(flowNodeTitle(meta))}</h3></div><div class="flow-inspector-head-actions"><button type="button" class="secondary flow-inspector-expand" data-action="flow-toggle-inspector" aria-label="${esc(tr('flow.expandSettings'))}">${uiIcon('expand-vertical')}</button><button type="button" class="secondary flow-inspector-close" data-action="flow-clear-selection" aria-label="${esc(tr('flow.clearSelection'))}">${uiIcon('close')}</button><button type="button" class="danger flow-inspector-delete" data-flow-remove="${esc(node.id)}">${esc(tr('actions.delete'))}</button></div></div>${description}${fields}<div class="flow-inspector-meta"><small>ID</small><code>${esc(node.id)}</code></div>`;
|
||||
}
|
||||
|
||||
function flowTextComparisonFields(c, numeric = false) { const options = numeric ? flowOperatorOptions(c.operator || 'eq') : [['eq', '='], ['neq', '≠']].map(([v, l]) => `<option value="${v}" ${v === (c.operator || 'eq') ? 'selected' : ''}>${l}</option>`).join(''); return `<div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-flow-config="operator">${options}</select></label><label><span>${esc(tr('flow.value'))}</span><input data-flow-config="value" value="${esc(c.value ?? '')}"></label></div>`; }
|
||||
@@ -1736,6 +1803,8 @@ function updateFlowConfig(input) {
|
||||
else if (wasLouver) node.config.value = '';
|
||||
}
|
||||
else if (node.kind === 'device_state' && key === 'value' && ['swing_vertical', 'swing_horizontal'].includes(node.config.field)) node.config.value = Number(input.value);
|
||||
else if (node.kind === 'zone_heat_cycle' && ['day_target', 'night_target', 'day_min', 'boost_after_minutes', 'day_off_after_minutes'].includes(key))
|
||||
node.config[key] = Number(input.value);
|
||||
else if (node.kind === 'zone_thermostat' && ['heat_anticipation_c', 'standby_offset_c'].includes(key))
|
||||
node.config[key] = input.value === '' ? null : Number(input.value);
|
||||
else if (['swing_vertical', 'swing_horizontal'].includes(key)) node.config[key] = input.value === '' ? null : Number(input.value);
|
||||
|
||||
Reference in new issue
Block a user