"use strict"; function editHeightmap() { void (function selectEditMode() { alertMessage.innerHTML = /* html */ `Heightmap is a core element on which all other data (rivers, burgs, states etc) is based. So the best edit approach is to erase the secondary data and let the system automatically regenerate it on edit completion.
Erase mode also allows you Convert an Image into a heightmap or use Template Editor.
You can keep the data, but you won't be able to change the coastline.
Try risk mode to change the coastline and keep the data. The data will be restored as much as possible, but it can cause unpredictable errors.
Please save the map before editing the heightmap!
Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.
`; $("#alert").dialog({ resizable: false, title: "Edit Heightmap", width: "28em", buttons: { Erase: () => enterHeightmapEditMode("erase"), Keep: () => enterHeightmapEditMode("keep"), Risk: () => enterHeightmapEditMode("risk"), Cancel: function () { $(this).dialog("close"); } } }); })(); restartHistory(); viewbox.insert("g", "#terrs").attr("id", "heights"); if (modules.editHeightmap) return; modules.editHeightmap = true; // add listeners byId("paintBrushes").on("click", openBrushesPanel); byId("applyTemplate").on("click", openTemplateEditor); byId("convertImage").on("click", openImageConverter); byId("heightmapPreview").on("click", toggleHeightmapPreview); byId("heightmap3DView").on("click", changeViewMode); byId("finalizeHeightmap").on("click", finalizeHeightmap); byId("renderOcean").on("click", mockHeightmap); byId("templateUndo").on("click", () => restoreHistory(edits.n - 1)); byId("templateRedo").on("click", () => restoreHistory(edits.n + 1)); function enterHeightmapEditMode(type) { editHeightmap.layers = Array.from(mapLayers.querySelectorAll("li:not(.buttonoff)")).map(node => node.id); // store layers preset editHeightmap.layers.forEach(l => byId(l).click()); // turn off all layers customization = 1; closeDialogs(); tip('Heightmap edit mode is active. Click on "Exit Customization" to finalize the heightmap', true); customizationMenu.style.display = "block"; toolsContent.style.display = "none"; heightmapEditMode.innerHTML = type; if (type === "erase") { undraw(); changeOnlyLand.checked = false; } else if (type === "keep") { viewbox.selectAll("#landmass, #lakes").style("display", "none"); changeOnlyLand.checked = true; } else if (type === "risk") { defs.selectAll("#land, #water").selectAll("path").remove(); viewbox.selectAll("#coastline path, #lakes path, #oceanLayers path").remove(); changeOnlyLand.checked = false; } // show convert and template buttons for Erase mode only applyTemplate.style.display = type === "erase" ? "inline-block" : "none"; convertImage.style.display = type === "erase" ? "inline-block" : "none"; // hide erosion checkbox if mode is Keep allowErosionBox.style.display = type === "keep" ? "none" : "inline-block"; // show finalize button if (!sessionStorage.getItem("noExitButtonAnimation")) { sessionStorage.setItem("noExitButtonAnimation", true); exitCustomization.style.opacity = 0; const width = 12 * uiSizeOutput.value * 11; exitCustomization.style.right = (svgWidth - width) / 2 + "px"; exitCustomization.style.bottom = svgHeight / 2 + "px"; exitCustomization.style.transform = "scale(2)"; exitCustomization.style.display = "block"; d3.select("#exitCustomization") .transition() .duration(1000) .style("opacity", 1) .transition() .duration(2000) .ease(d3.easeSinInOut) .style("right", "10px") .style("bottom", "10px") .style("transform", "scale(1)"); } else exitCustomization.style.display = "block"; openBrushesPanel(); turnButtonOn("toggleHeight"); layersPreset.value = "heightmap"; layersPreset.disabled = true; mockHeightmap(); viewbox.on("touchmove mousemove", moveCursor); } function moveCursor() { const p = d3.mouse(this), cell = findGridCell(p[0], p[1]); heightmapInfoX.innerHTML = rn(p[0]); heightmapInfoY.innerHTML = rn(p[1]); heightmapInfoCell.innerHTML = cell; heightmapInfoHeight.innerHTML = /* html */ `${grid.cells.h[cell]} (${getHeight(grid.cells.h[cell])})`; if (tooltip.dataset.main) showMainTip(); // move radius circle if drag mode is active const pressed = byId("brushesButtons").querySelector("button.pressed"); if (!pressed) return; moveCircle(p[0], p[1], brushRadius.valueAsNumber, "#333"); } // get user-friendly (real-world) height value from map data function getHeight(h) { const unit = heightUnit.value; let unitRatio = 3.281; // default calculations are in feet if (unit === "m") unitRatio = 1; // if meter else if (unit === "f") unitRatio = 0.5468; // if fathom let height = -990; if (h >= 20) height = Math.pow(h - 18, +heightExponentInput.value); else if (h < 20 && h > 0) height = ((h - 20) / h) * 50; return rn(height * unitRatio) + " " + unit; } // Exit customization mode function finalizeHeightmap() { if (viewbox.select("#heights").selectAll("*").size() < 200) return tip("Insufficient land area! There should be at least 200 land cells to finalize the heightmap", null, "error"); if (byId("imageConverter").offsetParent) return tip("Please exit the Image Conversion mode first", null, "error"); delete window.edits; // remove global variable redo.disabled = templateRedo.disabled = true; undo.disabled = templateUndo.disabled = true; customization = 0; customizationMenu.style.display = "none"; if (byId("options").querySelector(".tab > button.active").id === "toolsTab") toolsContent.style.display = "block"; layersPreset.disabled = false; exitCustomization.style.display = "none"; // hide finalize button restoreDefaultEvents(); clearMainTip(); closeDialogs(); resetZoom(); if (byId("preview")) byId("preview").remove(); if (byId("canvas3d")) enterStandardView(); const mode = heightmapEditMode.innerHTML; if (mode === "erase") regenerateErasedData(); else if (mode === "keep") restoreKeptData(); else if (mode === "risk") restoreRiskedData(); // restore initial layers //viewbox.select("#heights").remove(); byId("heights").remove(); turnButtonOff("toggleHeight"); document .getElementById("mapLayers") .querySelectorAll("li") .forEach(function (e) { if (editHeightmap.layers.includes(e.id) && !layerIsOn(e.id)) e.click(); // turn on else if (!editHeightmap.layers.includes(e.id) && layerIsOn(e.id)) e.click(); // turn off }); getCurrentPreset(); } function regenerateErasedData() { INFO && console.group("Edit Heightmap"); TIME && console.time("regenerateErasedData"); const erosionAllowed = allowErosion.checked; markFeatures(); markupGridOcean(); if (erosionAllowed) { addLakesInDeepDepressions(); openNearSeaLakes(); } OceanLayers(); calculateTemperatures(); generatePrecipitation(); reGraph(); drawCoastline(); Rivers.generate(erosionAllowed); if (!erosionAllowed) { for (const i of pack.cells.i) { const g = pack.cells.g[i]; if (pack.cells.h[i] !== grid.cells.h[g] && pack.cells.h[i] >= 20 === grid.cells.h[g] >= 20) pack.cells.h[i] = grid.cells.h[g]; } } drawRivers(); Lakes.defineGroup(); defineBiomes(); rankCells(); Cultures.generate(); Cultures.expand(); BurgsAndStates.generate(); Religions.generate(); BurgsAndStates.defineStateForms(); BurgsAndStates.generateProvinces(); BurgsAndStates.defineBurgFeatures(); drawStates(); drawBorders(); BurgsAndStates.drawStateLabels(); Rivers.specify(); Lakes.generateName(); Military.generate(); Markers.generate(); addZones(); TIME && console.timeEnd("regenerateErasedData"); INFO && console.groupEnd("Edit Heightmap"); } function restoreKeptData() { viewbox.selectAll("#landmass, #lakes").style("display", null); for (const i of pack.cells.i) { pack.cells.h[i] = grid.cells.h[pack.cells.g[i]]; } } function restoreRiskedData() { INFO && console.group("Edit Heightmap"); TIME && console.time("restoreRiskedData"); const erosionAllowed = allowErosion.checked; // assign pack data to grid cells const l = grid.cells.i.length; const biome = new Uint8Array(l); const pop = new Uint16Array(l); const road = new Uint16Array(l); const crossroad = new Uint16Array(l); const s = new Uint16Array(l); const burg = new Uint16Array(l); const state = new Uint16Array(l); const province = new Uint16Array(l); const culture = new Uint16Array(l); const religion = new Uint16Array(l); // rivers data, stored only if allowErosion is unchecked const fl = new Uint16Array(l); const r = new Uint16Array(l); const conf = new Uint8Array(l); for (const i of pack.cells.i) { const g = pack.cells.g[i]; biome[g] = pack.cells.biome[i]; culture[g] = pack.cells.culture[i]; pop[g] = pack.cells.pop[i]; road[g] = pack.cells.road[i]; crossroad[g] = pack.cells.crossroad[i]; s[g] = pack.cells.s[i]; state[g] = pack.cells.state[i]; province[g] = pack.cells.province[i]; burg[g] = pack.cells.burg[i]; religion[g] = pack.cells.religion[i]; if (!erosionAllowed) { fl[g] = pack.cells.fl[i]; r[g] = pack.cells.r[i]; conf[g] = pack.cells.conf[i]; } } // do not allow to remove land with burgs for (const i of grid.cells.i) { if (!burg[i]) continue; if (grid.cells.h[i] < 20) grid.cells.h[i] = 20; } // save culture centers x and y to restore center cell id after re-graph for (const c of pack.cultures) { if (!c.i || c.removed) continue; const p = pack.cells.p[c.center]; c.x = p[0]; c.y = p[1]; } // recalculate zones to grid zones.selectAll("g").each(function () { const zone = d3.select(this); const dataCells = zone.attr("data-cells"); const cells = dataCells ? dataCells.split(",").map(i => +i) : []; const g = cells.map(i => pack.cells.g[i]); zone.attr("data-cells", g); zone.selectAll("*").remove(); }); markFeatures(); markupGridOcean(); if (erosionAllowed) addLakesInDeepDepressions(); OceanLayers(); calculateTemperatures(); generatePrecipitation(); reGraph(); drawCoastline(); if (erosionAllowed) Rivers.generate(true); // assign saved pack data from grid back to pack const n = pack.cells.i.length; pack.cells.pop = new Float32Array(n); pack.cells.road = new Uint16Array(n); pack.cells.crossroad = new Uint16Array(n); pack.cells.s = new Uint16Array(n); pack.cells.burg = new Uint16Array(n); pack.cells.state = new Uint16Array(n); pack.cells.province = new Uint16Array(n); pack.cells.culture = new Uint16Array(n); pack.cells.religion = new Uint16Array(n); pack.cells.biome = new Uint8Array(n); if (!erosionAllowed) { pack.cells.r = new Uint16Array(n); pack.cells.conf = new Uint8Array(n); pack.cells.fl = new Uint16Array(n); } for (const i of pack.cells.i) { const g = pack.cells.g[i]; const isLand = pack.cells.h[i] >= 20; // check biome pack.cells.biome[i] = isLand && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], grid.cells.temp[g], pack.cells.h[i]); // rivers data if (!erosionAllowed) { pack.cells.r[i] = r[g]; pack.cells.conf[i] = conf[g]; pack.cells.fl[i] = fl[g]; } if (!isLand) continue; pack.cells.culture[i] = culture[g]; pack.cells.pop[i] = pop[g]; pack.cells.road[i] = road[g]; pack.cells.crossroad[i] = crossroad[g]; pack.cells.s[i] = s[g]; pack.cells.state[i] = state[g]; pack.cells.province[i] = province[g]; pack.cells.religion[i] = religion[g]; } // find closest land cell to burg const findBurgCell = function (x, y) { let i = findCell(x, y); if (pack.cells.h[i] >= 20) return i; const dist = pack.cells.c[i].map(c => (pack.cells.h[c] < 20 ? Infinity : (pack.cells.p[c][0] - x) ** 2 + (pack.cells.p[c][1] - y) ** 2)); return pack.cells.c[i][d3.scan(dist)]; }; // find best cell for burgs for (const b of pack.burgs) { if (!b.i || b.removed) continue; b.cell = findBurgCell(b.x, b.y); b.feature = pack.cells.f[b.cell]; pack.cells.burg[b.cell] = b.i; if (!b.capital && pack.cells.h[b.cell] < 20) removeBurg(b.i); if (b.capital) pack.states[b.state].center = b.cell; } for (const p of pack.provinces) { if (!p.i || p.removed) continue; const provCells = pack.cells.i.filter(i => pack.cells.province[i] === p.i); if (!provCells.length) { const state = p.state; const stateProvs = pack.states[state].provinces; if (stateProvs.includes(p.i)) pack.states[state].provinces.splice(stateProvs.indexOf(p), 1); p.removed = true; continue; } if (p.burg && !pack.burgs[p.burg].removed) p.center = pack.burgs[p.burg].cell; else { p.center = provCells[0]; p.burg = pack.cells.burg[p.center]; } } for (const c of pack.cultures) { if (!c.i || c.removed) continue; c.center = findCell(c.x, c.y); } BurgsAndStates.drawStateLabels(); drawStates(); drawBorders(); if (erosionAllowed) { Rivers.specify(); Lakes.generateName(); } // restore zones from grid zones.selectAll("g").each(function () { const zone = d3.select(this); const g = zone.attr("data-cells"); const gCells = g ? g.split(",").map(i => +i) : []; const cells = pack.cells.i.filter(i => gCells.includes(pack.cells.g[i])); zone.attr("data-cells", cells); zone.selectAll("*").remove(); const base = zone.attr("id") + "_"; // id generic part zone .selectAll("*") .data(cells) .enter() .append("polygon") .attr("points", d => getPackPolygon(d)) .attr("id", d => base + d); }); TIME && console.timeEnd("restoreRiskedData"); INFO && console.groupEnd("Edit Heightmap"); } // trigger heightmap redraw and history update if at least 1 cell is changed function updateHeightmap() { const prev = last(edits); const changed = grid.cells.h.reduce((s, h, i) => (h !== prev[i] ? s + 1 : s), 0); tip("Cells changed: " + changed); if (!changed) return; // check ocean cells are not checged if olny land edit is allowed if (changeOnlyLand.checked) { for (const i of grid.cells.i) { if (prev[i] < 20 || grid.cells.h[i] < 20) grid.cells.h[i] = prev[i]; } } mockHeightmap(); updateHistory(); } // draw or update heightmap function mockHeightmap() { const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20); const scheme = getColorScheme(); viewbox .select("#heights") .selectAll("polygon") .data(data) .join("polygon") .attr("points", d => getGridPolygon(d)) .attr("id", d => "cell" + d) .attr("fill", d => getColor(grid.cells.h[d], scheme)); } // draw or update heightmap for a selection of cells function mockHeightmapSelection(selection) { const ocean = renderOcean.checked; const scheme = getColorScheme(); selection.forEach(function (i) { let cell = viewbox.select("#heights").select("#cell" + i); if (!ocean && grid.cells.h[i] < 20) { cell.remove(); return; } if (!cell.size()) cell = viewbox .select("#heights") .append("polygon") .attr("points", getGridPolygon(i)) .attr("id", "cell" + i); cell.attr("fill", getColor(grid.cells.h[i], scheme)); }); } function updateStatistics() { const landCells = grid.cells.h.reduce((s, h) => (h >= 20 ? s + 1 : s)); byId("landmassCounter").innerText = `${landCells} (${rn((landCells / grid.cells.i.length) * 100)}%)`; byId("landmassAverage").innerText = rn(d3.mean(grid.cells.h)); } function updateHistory(noStat) { const step = edits.n; edits = edits.slice(0, step); edits[step] = grid.cells.h.slice(); edits.n = step + 1; undo.disabled = templateUndo.disabled = edits.n <= 1; redo.disabled = templateRedo.disabled = true; if (!noStat) { updateStatistics(); if (byId("preview")) drawHeightmapPreview(); // update heightmap preview if opened if (byId("canvas3d")) ThreeD.redraw(); // update 3d heightmap preview if opened } } // restoreHistory function restoreHistory(step) { edits.n = step; redo.disabled = templateRedo.disabled = edits.n >= edits.length; undo.disabled = templateUndo.disabled = edits.n <= 1; if (edits[edits.n - 1] === undefined) return; grid.cells.h = edits[edits.n - 1].slice(); mockHeightmap(); updateStatistics(); if (byId("preview")) drawHeightmapPreview(); // update heightmap preview if opened if (byId("canvas3d")) ThreeD.redraw(); // update 3d heightmap preview if opened } // restart edits from 1st step function restartHistory() { window.edits = []; // declare temp global variable window.edits.n = 0; redo.disabled = templateRedo.disabled = true; undo.disabled = templateUndo.disabled = true; updateHistory(); } function openBrushesPanel() { if ($("#brushesPanel").is(":visible")) return; $("#brushesPanel") .dialog({ title: "Paint Brushes", resizable: false, position: {my: "right top", at: "right-10 top+10", of: "svg"} }) .on("dialogclose", exitBrushMode); if (modules.openBrushesPanel) return; modules.openBrushesPanel = true; // add listeners byId("brushesButtons").on("click", e => toggleBrushMode(e)); byId("changeOnlyLand").on("click", e => changeOnlyLandClick(e)); byId("undo").on("click", () => restoreHistory(edits.n - 1)); byId("redo").on("click", () => restoreHistory(edits.n + 1)); byId("rescaleShow").on("click", () => { byId("modifyButtons").style.display = "none"; byId("rescaleSection").style.display = "block"; }); byId("rescaleHide").on("click", () => { byId("modifyButtons").style.display = "block"; byId("rescaleSection").style.display = "none"; }); byId("rescaler").on("change", e => rescale(e.target.valueAsNumber)); byId("rescaleCondShow").on("click", () => { byId("modifyButtons").style.display = "none"; byId("rescaleCondSection").style.display = "block"; }); byId("rescaleCondHide").on("click", () => { byId("modifyButtons").style.display = "block"; byId("rescaleCondSection").style.display = "none"; }); byId("rescaleExecute").on("click", rescaleWithCondition); byId("smoothHeights").on("click", smoothAllHeights); byId("disruptHeights").on("click", disruptAllHeights); byId("brushClear").on("click", startFromScratch); function exitBrushMode() { const pressed = document.querySelector("#brushesButtons > button.pressed"); if (!pressed) return; pressed.classList.remove("pressed"); viewbox.style("cursor", "default").on(".drag", null); removeCircle(); byId("brushesSliders").style.display = "none"; } const dragBrushThrottled = throttle(dragBrush, 100); function toggleBrushMode(e) { if (e.target.classList.contains("pressed")) { exitBrushMode(); return; } exitBrushMode(); byId("brushesSliders").style.display = "block"; e.target.classList.add("pressed"); viewbox.style("cursor", "crosshair").call(d3.drag().on("start", dragBrushThrottled)); } function dragBrush() { const r = brushRadius.valueAsNumber; const point = d3.mouse(this); const start = findGridCell(point[0], point[1]); d3.event.on("drag", () => { const p = d3.mouse(this); moveCircle(p[0], p[1], r, "#333"); if (~~d3.event.sourceEvent.timeStamp % 5 != 0) return; // slow down the edit const inRadius = findGridAll(p[0], p[1], r); const selection = changeOnlyLand.checked ? inRadius.filter(i => grid.cells.h[i] >= 20) : inRadius; if (selection && selection.length) changeHeightForSelection(selection, start); }); d3.event.on("end", updateHeightmap); } function changeHeightForSelection(s, start) { const power = brushPower.valueAsNumber; const interpolate = d3.interpolateRound(power, 1); const land = changeOnlyLand.checked; const lim = v => minmax(v, land ? 20 : 0, 100); const h = grid.cells.h; const brush = document.querySelector("#brushesButtons > button.pressed").id; if (brush === "brushRaise") s.forEach(i => (h[i] = h[i] < 20 ? 20 : lim(h[i] + power))); else if (brush === "brushElevate") s.forEach((i, d) => (h[i] = lim(h[i] + interpolate(d / Math.max(s.length - 1, 1))))); else if (brush === "brushLower") s.forEach(i => (h[i] = lim(h[i] - power))); else if (brush === "brushDepress") s.forEach((i, d) => (h[i] = lim(h[i] - interpolate(d / Math.max(s.length - 1, 1))))); else if (brush === "brushAlign") s.forEach(i => (h[i] = lim(h[start]))); else if (brush === "brushSmooth") s.forEach( i => (h[i] = rn((d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1)) ); else if (brush === "brushDisrupt") s.forEach(i => (h[i] = h[i] < 15 ? h[i] : lim(h[i] + power / 1.6 - Math.random() * power))); mockHeightmapSelection(s); // updateHistory(); uncomment to update history every step } function changeOnlyLandClick(e) { if (heightmapEditMode.innerHTML !== "keep") return; e.preventDefault(); tip("You cannot change the coastline in 'Keep' edit mode", false, "error"); } function rescale(v) { const land = changeOnlyLand.checked; grid.cells.h = grid.cells.h.map(h => (land && (h < 20 || h + v < 20) ? h : lim(h + v))); updateHeightmap(); byId("rescaler").value = 0; } function rescaleWithCondition() { const range = rescaleLower.value + "-" + rescaleHigher.value; const operator = conditionSign.value; const operand = rescaleModifier.valueAsNumber; if (Number.isNaN(operand)) return tip("Operand should be a number", false, "error"); if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand)) return tip("Operand should be an integer", false, "error"); HeightmapGenerator.setHeights(grid.cells.h); if (operator === "multiply") HeightmapGenerator.modify(range, 0, operand, 0); else if (operator === "divide") HeightmapGenerator.modify(range, 0, 1 / operand, 0); else if (operator === "add") HeightmapGenerator.modify(range, operand, 1, 0); else if (operator === "subtract") HeightmapGenerator.modify(range, -1 * operand, 1, 0); else if (operator === "exponent") HeightmapGenerator.modify(range, 0, 1, operand); grid.cells.h = HeightmapGenerator.getHeights(); HeightmapGenerator.cleanup(); updateHeightmap(); } function smoothAllHeights() { HeightmapGenerator.setHeights(grid.cells.h); HeightmapGenerator.smooth(4, 1.5); grid.cells.h = HeightmapGenerator.getHeights(); HeightmapGenerator.cleanup(); updateHeightmap(); } function disruptAllHeights() { grid.cells.h = grid.cells.h.map(h => (h < 15 ? h : lim(h + 2.5 - Math.random() * 4))); updateHeightmap(); } function startFromScratch() { if (changeOnlyLand.checked) return tip("Not allowed when 'Change only land cells' mode is set", false, "error"); const someHeights = grid.cells.h.some(h => h); if (!someHeights) return tip("Heightmap is already cleared, please do not click twice if not required", false, "error"); grid.cells.h = new Uint8Array(grid.cells.i.length); viewbox.select("#heights").selectAll("*").remove(); updateHistory(); } } function openTemplateEditor() { if ($("#templateEditor").is(":visible")) return; const $body = byId("templateBody"); $("#templateEditor").dialog({ title: "Template Editor", minHeight: "auto", width: "fit-content", resizable: false, position: {my: "right top", at: "right-10 top+10", of: "svg"} }); if (modules.openTemplateEditor) return; modules.openTemplateEditor = true; $("#templateBody").sortable({items: "> div", handle: ".icon-resize-vertical", containment: "#templateBody", axis: "y"}); // add listeners $body.on("click", function (ev) { const el = ev.target; if (el.classList.contains("icon-check")) { el.classList.remove("icon-check"); el.classList.add("icon-check-empty"); el.parentElement.style.opacity = 0.5; $body.dataset.changed = 1; return; } if (el.classList.contains("icon-check-empty")) { el.classList.add("icon-check"); el.classList.remove("icon-check-empty"); el.parentElement.style.opacity = 1; return; } if (el.classList.contains("icon-trash-empty")) { el.parentElement.remove(); return; } }); byId("templateEditor").on("keypress", event => { if (event.key === "Enter") { event.preventDefault(); executeTemplate(); } }); byId("templateTools").on("click", addStepOnClick); byId("templateSelect").on("change", selectTemplate); byId("templateRun").on("click", executeTemplate); byId("templateSave").on("click", downloadTemplate); byId("templateLoad").on("click", () => templateToLoad.click()); byId("templateToLoad").on("change", function () { uploadFile(this, uploadTemplate); }); function addStepOnClick(e) { if (e.target.tagName !== "BUTTON") return; const type = e.target.dataset.type; byId("templateBody").dataset.changed = 1; addStep(type); } function addStep(type, count, dist, arg4, arg5) { const $body = byId("templateBody"); $body.insertAdjacentHTML("beforeend", getStepHTML(type, count, dist, arg4, arg5)); const $elDist = $body.querySelector("div:last-child > span > .templateDist"); if ($elDist) $elDist.on("change", setRange); if (dist && $elDist && $elDist.tagName === "SELECT") { for (const option of $elDist.options) { if (option.value === dist) $elDist.value = dist; } if ($elDist.value !== dist) { const opt = document.createElement("option"); opt.value = opt.innerHTML = dist; $elDist.add(opt); $elDist.value = dist; } } } function getStepHTML(type, count, arg3, arg4, arg5) { const Trash = /* html */ ``; const Hide = /* html */ ``; const Reorder = /* html */ ``; const common = /* html */ `