Compare commits

...

2 Commits

Author SHA1 Message Date
CodedSakura 7948a27f39 finalize file saving 2026-07-13 17:07:10 +03:00
CodedSakura 880017ddf3 switch from main calling initializers to modules registering for load callback 2026-07-13 09:31:38 +03:00
12 changed files with 326 additions and 84 deletions

View File

@ -97,5 +97,22 @@
</footer> </footer>
</section> </section>
</div> </div>
<div class="dialog hidden" data-dialog="fileSave:nameDesign">
<section>
<header>Save your design</header>
<main class="list-choice">
<form id="fileSave:nameDesign:form" data-form="fileSave:nameDesign">
<label>
Name:
<input name="name" autofocus />
</label>
</form>
</main>
<footer>
<button data-dialog-action="cancel">Cancel</button>
<button form="fileSave:nameDesign:form">Save</button>
</footer>
</section>
</div>
</body> </body>
</html> </html>

View File

@ -1,29 +1,50 @@
import { DomList } from "./dom-list.js"; import { DomList } from "./dom-list.js";
import { addEventToElement, setText } from "./dom-utils.js"; import { addEventToElement, setText } from "./dom-utils.js";
import { emitEvent, listenTo } from "./events.js"; import { awaitEvent, emitEvent, listenTo } from "./events.js";
import { registerLoadCallback } from "./main.js";
export function initializeDialogs() { registerLoadCallback(() => {
listenTo("fileLoad:chooseDesign", designChoiceDialog); listenTo("fileLoad:chooseDesign", designChoiceDialog);
} listenTo("fileSave:nameDesign", designNameDialog);
});
function designChoiceDialog(e) { function initializeDialog(dialogName, endEvent, { emptyValue, onDismiss } = {}) {
const dialog = document.querySelector('[data-dialog="fileLoad:chooseDesign"]'); const dialog = document.querySelector(`[data-dialog="${dialogName}"]`);
const dismiss = (data) => { const dismiss = (data) => {
dialog.classList.add("hidden"); dialog.classList.add("hidden");
emitEvent("fileLoad:chooseDesign:choice", data); console.log("dimiss > emit", data);
emitEvent(endEvent, data);
if (onDismiss) {
onDismiss();
}
} }
addEventToElement( addEventToElement(
"dialog-action", "cancel", "click", "dialog-action", "cancel", "click",
() => dismiss(-1), () => dismiss(emptyValue),
{ element: dialog }, { element: dialog },
); );
dialog.addEventListener("click", e => { dialog.addEventListener("click", e => {
if (e.target !== e.currentTarget) return; if (e.target !== e.currentTarget) return;
dismiss(-1); dismiss(emptyValue);
}); });
queueMicrotask(() => {
dialog.classList.remove("hidden");
});
return {
dialog,
dismiss,
};
}
function designChoiceDialog(e) {
const {
dialog, dismiss
} = initializeDialog("fileLoad:chooseDesign", "fileLoad:chooseDesign:choice", -1);
const list = new DomList(dialog.querySelector(":scope > section > main")); const list = new DomList(dialog.querySelector(":scope > section > main"));
list.clear(); list.clear();
@ -35,6 +56,20 @@ function designChoiceDialog(e) {
button.addEventListener("click", () => dismiss(designIndex)); button.addEventListener("click", () => dismiss(designIndex));
list.addItem(button); list.addItem(button);
} }
}
dialog.classList.remove("hidden");
function designNameDialog() {
const {
dismiss
} = initializeDialog(
"fileSave:nameDesign",
"fileSave:nameDesign:named",
{
emptyValue: null
}
);
awaitEvent("fileSave:nameDesign:submit").then(e => {
dismiss(new FormData(e.detail.target).get("name"));
});
} }

View File

@ -1,4 +1,5 @@
import { deleteTooltip } from "./tooltip.js"; import { deleteTooltip } from "./tooltip.js";
import { deleteSolverMetadata } from "./solver.js";
export class DomList { export class DomList {
list; list;
@ -10,7 +11,10 @@ export class DomList {
} }
clear() { clear() {
this.list.querySelectorAll("[data-tooltip]").forEach(e => deleteTooltip(Number(e.dataset.tooltip))) this.list.querySelectorAll("[data-tooltip]")
.forEach(e => deleteTooltip(Number(e.dataset.tooltip)));
this.list.querySelectorAll("[data-solver-metadata]")
.forEach(e => deleteSolverMetadata(Number(e.dataset.solverMetadata)));
this.list.replaceChildren(...this.list.querySelectorAll(":scope > template, :scope > [data-keep]")); this.list.replaceChildren(...this.list.querySelectorAll(":scope > template, :scope > [data-keep]"));
} }

View File

@ -1,20 +1,29 @@
import { awaitEvent, emitEvent, listenTo } from "./events.js"; import { awaitEvent, emitEvent, listenTo } from "./events.js";
import { findFullEntry, stripEntryToMinimums } from "./ncpf-utils.js"; import { checkEquivalence, findFullEntry, stripEntryToMinimums } from "./ncpf-utils.js";
import { getPlannerMultiblockSize, getPlannerMultiblockType, loadPlanner, readPlannerTiles } from "./planner.js"; import {
getPlannerDom,
getPlannerMultiblockSize,
getPlannerMultiblockType,
loadPlanner,
readPlannerCellData
} from "./planner.js";
import { getAtlasIndexFromMetadata, getAtlasMetadata } from "./atlas.js"; import { getAtlasIndexFromMetadata, getAtlasMetadata } from "./atlas.js";
import { resetSolver } from "./solver.js"; import { getSolverMetadata, resetSolver } from "./solver.js";
import { registerLoadCallback } from "./main.js";
export function initializeFileEvents() { registerLoadCallback(() => {
listenTo("file:load", () => showOpenFileDialog()); listenTo("file:load", () => showOpenFileDialog());
listenTo("file:new", () => {}); listenTo("file:new", () => {});
listenTo("file:save", () => saveFile("test")); listenTo("file:save", () => saveFile());
} });
export function showOpenFileDialog() { export function showOpenFileDialog() {
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
input.accept = ".ncpf.json"; input.accept = ".ncpf.json";
input.addEventListener("change", async () => { input.addEventListener("change", async () => {
const basename = input.files[0].name.split(/[/\\]/).pop();
const filename = basename.replace(/\.json$/, "").replace(/\.ncpf$/, "");
const data = await new Promise(resolve => { const data = await new Promise(resolve => {
const reader = new FileReader(); const reader = new FileReader();
reader.addEventListener("load", () => { reader.addEventListener("load", () => {
@ -22,7 +31,7 @@ export function showOpenFileDialog() {
}); });
reader.readAsText(input.files[0]); reader.readAsText(input.files[0]);
}); });
void loadFile(data); void loadFile(data, filename);
}); });
input.click(); input.click();
} }
@ -31,10 +40,10 @@ export async function loadExampleFile(filename) {
const data = await fetch(`./examples/${filename}.ncpf.json`) const data = await fetch(`./examples/${filename}.ncpf.json`)
.then(data => data.json()); .then(data => data.json());
return loadFile(data); return loadFile(data);
// return loadFile(data, filename);
} }
export async function loadFile(data) { export async function loadFile(data, name) {
console.log(data);
if (!data.designs) throw new Error("invalid file"); if (!data.designs) throw new Error("invalid file");
if (data.designs.length === 0) throw new Error("no designs"); if (data.designs.length === 0) throw new Error("no designs");
@ -54,13 +63,36 @@ export async function loadFile(data) {
const designWithAtlas = design.design.map(a => a.map(b => b.map(c => c === -1 ? -1 : blocks[c]))); const designWithAtlas = design.design.map(a => a.map(b => b.map(c => c === -1 ? -1 : blocks[c])));
loadPlanner(design.dimensions, design.type, designWithAtlas); loadPlanner(design.dimensions, design.type, designWithAtlas);
resetSolver(design); resetSolver(design, data.configuration[design.type]);
if (name) {
getPlannerDom().dataset.name = name;
}
console.log("load file", data);
} }
export async function saveFile(name) { export async function saveFile(name = undefined) {
if (!name) {
name = getPlannerDom().dataset.name;
}
if (!name) {
emitEvent("fileSave:nameDesign");
const nameEvent = await awaitEvent("fileSave:nameDesign:named");
name = nameEvent.detail;
console.log(nameEvent)
}
if (!name) {
name = "unnamed";
}
const multiblockType = getPlannerMultiblockType(); const multiblockType = getPlannerMultiblockType();
const designAsAtlas = readPlannerTiles(); const designAsCellData = readPlannerCellData();
const designAsAtlas = designAsCellData
.map(xPlane => xPlane.map(yRow => yRow.map(c => Number(c.tile))));
const designBlocksAtlasMap = Object.fromEntries( const designBlocksAtlasMap = Object.fromEntries(
[...new Set(designAsAtlas.flat(3))] [...new Set(designAsAtlas.flat(3))]
.filter(v => v !== -1) .filter(v => v !== -1)
@ -74,44 +106,93 @@ export async function saveFile(name) {
const designAsBlockIndices = designAsBlocks const designAsBlockIndices = designAsBlocks
.map(xPlane => xPlane.map(yRow => yRow.map(b => b?.designIndex ?? -1))); .map(xPlane => xPlane.map(yRow => yRow.map(b => b?.designIndex ?? -1)));
const designBlocksWithRecipes = designAsBlocks
.map(xPlane => xPlane
.map(yRow => yRow
.map(block => {
if (!block) return;
return findFullEntry("blocks", block, { configurationHint: multiblockType });
})
.filter(b => b && b.modules && "ncpf:block_recipes" in b.modules)
)
.filter(r => r.length > 0)
)
.filter(p => p.length > 0);
console.log("designBlocksWithRecipes", designBlocksWithRecipes)
const minimizedBlocks = Object.entries(designBlocksAtlasMap) const minimizedBlocks = Object.entries(designBlocksAtlasMap)
.sort(([a], [b]) => a - b) .sort(([a], [b]) => a - b)
.map(([, m]) => stripEntryToMinimums(m)); .map(([, m]) => stripEntryToMinimums(m));
const data = { const blockRecipes = designAsBlocks
addons: [], .map((xPlane, x) => xPlane
configurations: { .map((yRow, y) => yRow
[multiblockType]: { .map((block, z) => {
blocks: minimizedBlocks, if (!block) return;
coolant_recipes: [], // FIXME const fullBlock = findFullEntry(
}, "blocks",
}, block,
designs: [ { configurationHint: multiblockType },
{ );
block_recipes: [], // FIXME if (!fullBlock || !fullBlock.modules || !("ncpf:block_recipes" in fullBlock.modules)) return;
coolant_recipe: 0, // FIXME
design: designAsBlockIndices,
dimensions: getPlannerMultiblockSize(),
type: multiblockType,
},
],
version: 1,
};
console.log(designBlocksAtlasMap); const designIndex = designBlocksAtlasMap[Number(designAsCellData[x][y][z].tile)].designIndex;
console.log(data); const solverMetadata = getSolverMetadata(designAsCellData[x][y][z].solverMetadata);
const minimizedBlock = minimizedBlocks[designIndex];
let recipeIndex = -1;
if (multiblockType === "nuclearcraft:overhaul_sfr") {
if (!minimizedBlock.modules) {
minimizedBlock.modules = {};
}
if (!minimizedBlock.modules["ncpf:block_recipes"]) {
minimizedBlock.modules["ncpf:block_recipes"] = {
recipes: [],
};
}
const recipes = minimizedBlock.modules["ncpf:block_recipes"].recipes;
const recipe = solverMetadata["block_recipe"];
recipeIndex = recipes.findIndex(r => checkEquivalence(r, recipe));
if (recipeIndex === -1) {
recipes.push(stripEntryToMinimums(recipe));
recipeIndex = recipes.length - 1;
}
}
return recipeIndex;
})
.filter(b => b != null)
)
.filter(r => r.length > 0)
)
.filter(p => p.length > 0);
let fileData = null;
if (multiblockType === "nuclearcraft:overhaul_sfr") {
const coolantRecipes = [];
let coolantRecipeIndex = -1;
const plannerMetadata = getSolverMetadata(getPlannerDom());
coolantRecipes.push(stripEntryToMinimums(plannerMetadata["coolant_recipe"]));
coolantRecipeIndex = 0;
fileData = {
addons: [],
configuration: {
[multiblockType]: {
blocks: minimizedBlocks,
coolant_recipes: coolantRecipes,
},
},
designs: [
{
block_recipes: blockRecipes,
coolant_recipe: coolantRecipeIndex,
design: designAsBlockIndices,
dimensions: getPlannerMultiblockSize(),
type: multiblockType,
},
],
version: 1,
};
}
console.log(`save file (${name})`, fileData);
if (fileData) {
const blob = new Blob([ JSON.stringify(fileData) ], {
type: "application/json",
});
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = `${name}.ncpf.json`;
a.click();
}
} }

View File

@ -1,13 +1,20 @@
import { loadConfiguration } from "./configuration.js"; import { loadConfiguration } from "./configuration.js";
import { initializeDialogs } from "./dialogs.js";
import { addEventToButton, addEventToInput, debounce, getElements } from "./dom-utils.js"; import { addEventToButton, addEventToInput, debounce, getElements } from "./dom-utils.js";
import { awaitEvent, emitEvent, listenTo } from "./events.js"; import { awaitEvent, emitEvent, listenTo } from "./events.js";
import { initializeFileEvents, loadExampleFile, saveFile, showOpenFileDialog } from "./files.js"; import { loadExampleFile } from "./files.js";
import { initializePlanner } from "./planner.js";
import { initializeMenuResizing } from "./resize-menus.js";
import { getState, setState, STATE } from "./state.js"; import { getState, setState, STATE } from "./state.js";
import { initializeTooltips } from "./tooltip.js"; import "./menu-bar.js";
import { initializeMenuBar } from "./menu-bar.js"; import "./dialogs.js";
let loadCallbacks = [];
export function registerLoadCallback(cb) {
queueMicrotask(() => {
loadCallbacks.push(cb);
});
}
function runLoadCallbacks() {
loadCallbacks.forEach(cb => cb());
}
window.addEventListener("load", () => { window.addEventListener("load", () => {
emitEvent("setup-start"); emitEvent("setup-start");
@ -41,6 +48,12 @@ window.addEventListener("load", () => {
emitEvent(elem.dataset.input + ":change", e); emitEvent(elem.dataset.input + ":change", e);
}); });
}); });
getElements("form", "*").forEach(elem => {
elem.addEventListener("submit", e => {
e.preventDefault();
emitEvent(elem.dataset.form + ":submit", e);
});
});
addEventToInput("configuration", "input", loadConfiguration); addEventToInput("configuration", "input", loadConfiguration);
addEventToButton("load-configuration", "click", loadConfiguration); addEventToButton("load-configuration", "click", loadConfiguration);
@ -57,12 +70,7 @@ window.addEventListener("load", () => {
listenTo("config:end", () => setState(STATE.planning)); listenTo("config:end", () => setState(STATE.planning));
initializeFileEvents(); runLoadCallbacks();
initializePlanner();
initializeMenuResizing();
initializeTooltips();
initializeMenuBar();
initializeDialogs();
emitEvent("setup-end"); emitEvent("setup-end");
@ -70,5 +78,4 @@ window.addEventListener("load", () => {
awaitEvent("config:end").then(() => loadExampleFile("tiny reactor")); awaitEvent("config:end").then(() => loadExampleFile("tiny reactor"));
// awaitEvent("config:end").then(() => loadExampleFile("multi-reactor")); // awaitEvent("config:end").then(() => loadExampleFile("multi-reactor"));
awaitEvent("solver:reset:done").then(() => emitEvent("file:save"));
}); });

View File

@ -1,8 +1,10 @@
import { positionAsTooltip } from "./tooltip.js"; import { positionAsTooltip } from "./tooltip.js";
import { registerLoadCallback } from "./main.js";
let activeMenuBar = null; let activeMenuBar = null;
export function initializeMenuBar() { registerLoadCallback(() => {
console.log("menubar-load");
document.querySelectorAll(".menu-bar").forEach(menuBar => { document.querySelectorAll(".menu-bar").forEach(menuBar => {
menuBar.querySelectorAll("button").forEach(button => { menuBar.querySelectorAll("button").forEach(button => {
button.setAttribute("tabindex", "-1"); button.setAttribute("tabindex", "-1");
@ -66,7 +68,7 @@ export function initializeMenuBar() {
}); });
// TODO: keyboard navigation with alt and arrow keys // TODO: keyboard navigation with alt and arrow keys
} });
function handleGlobalClick() { function handleGlobalClick() {

View File

@ -9,9 +9,17 @@ export function checkEquivalence(a, b) {
&& a.metadata === b.metadata && a.metadata === b.metadata
&& (a.blockstate === undefined && b.blockstate === undefined && (a.blockstate === undefined && b.blockstate === undefined
|| JSON.stringify(a.blockstate) === JSON.stringify(b.blockstate)); || JSON.stringify(a.blockstate) === JSON.stringify(b.blockstate));
case "legacy_fluid":
return a.name === b.name;
case "oredict": case "oredict":
return a.oredict === b.oredict; return a.oredict === b.oredict;
case "list":
return a.elements.length === b.elements.length &&
a.elements.every((aE, i) => checkEquivalence(aE, b.elements[i]));
default: default:
console.warn("unknown type", a.type, a, b); console.warn("unknown type", a.type, a, b);
} }
@ -24,8 +32,17 @@ export function stripEntryToMinimums(entry) {
case "legacy_block": case "legacy_block":
return objectKeepKeys(entry, "type", "name", "metadata", "blockstate"); return objectKeepKeys(entry, "type", "name", "metadata", "blockstate");
case "legacy_fluid":
return objectKeepKeys(entry, "type", "name");
case "oredict": case "oredict":
return objectKeepKeys(entry, "type", "oredict"); return objectKeepKeys(entry, "type", "oredict");
case "list":
return {
type: entry.type,
elements: entry.elements.map(stripEntryToMinimums),
};
} }
throw new Error(`unknown entry type ${entry.type}`); throw new Error(`unknown entry type ${entry.type}`);
} }

View File

@ -7,6 +7,7 @@ import { initialize3dArray } from "./js-utils.js";
import { recall, save } from "./storage.js"; import { recall, save } from "./storage.js";
import { createTooltip, tooltipDataToPlain } from "./tooltip.js"; import { createTooltip, tooltipDataToPlain } from "./tooltip.js";
import { lookupFromMetadata, resolveBlockName } from "./ncpf-utils.js"; import { lookupFromMetadata, resolveBlockName } from "./ncpf-utils.js";
import { registerLoadCallback } from "./main.js";
const zoomOptions = [ 0.5, 1, 2, 3, 4, 5 ]; const zoomOptions = [ 0.5, 1, 2, 3, 4, 5 ];
const zoomDefault = 2; const zoomDefault = 2;
@ -17,7 +18,7 @@ let multiblockSize = [9, 7, 9]; // width (screen x), height (xy grids), depth (s
let multiblockType = "nuclearcraft:overhaul_sfr"; let multiblockType = "nuclearcraft:overhaul_sfr";
let activeItem = -1; let activeItem = -1;
export function initializePlanner() { registerLoadCallback(() => {
listenTo("config:end", buildBlockPicker); listenTo("config:end", buildBlockPicker);
listenTo("config:end", buildPlanner); listenTo("config:end", buildPlanner);
@ -84,7 +85,7 @@ export function initializePlanner() {
console.log("Suppressing hover:", suppressHover); console.log("Suppressing hover:", suppressHover);
} }
}); });
} });
function buildBlockPicker() { function buildBlockPicker() {
const filter = Filter.fromInput("block-picker:filter"); const filter = Filter.fromInput("block-picker:filter");
@ -306,7 +307,7 @@ export function loadPlanner(size, type, textures) {
buildPlanner(textures); buildPlanner(textures);
} }
export function readPlannerTiles() { export function readPlannerCellData() {
const planner = document.getElementById("planner"); const planner = document.getElementById("planner");
const tiles = initialize3dArray(...multiblockSize, -1); const tiles = initialize3dArray(...multiblockSize, -1);
const grids = [...planner.children].filter(e => e.dataset.y); const grids = [...planner.children].filter(e => e.dataset.y);
@ -315,12 +316,21 @@ export function readPlannerTiles() {
const gridTiles = [...grid.children].filter(e => e.dataset.tile); const gridTiles = [...grid.children].filter(e => e.dataset.tile);
for (let z = 0; z < multiblockSize[2]; z++) { for (let z = 0; z < multiblockSize[2]; z++) {
for (let x = 0; x < multiblockSize[0]; x++) { for (let x = 0; x < multiblockSize[0]; x++) {
tiles[x][y][z] = Number(gridTiles[x + z * multiblockSize[0]].dataset.tile); tiles[x][y][z] = { ...gridTiles[x + z * multiblockSize[0]].dataset };
} }
} }
} }
return tiles; return tiles;
} }
export function getPlannerDom() {
return document.getElementById("planner");
}
export function getPlannerCellsDom() {
return document.querySelectorAll("#planner [data-tile]");
}
export function getPlannerCellDom(x, y, z) {
return document.querySelector(`#planner [data-x="${x}"][data-y="${y}"][data-z="${z}"]`);
}
export const getPlannerMultiblockType = () => multiblockType; export const getPlannerMultiblockType = () => multiblockType;
export const getPlannerMultiblockSize = () => multiblockSize; export const getPlannerMultiblockSize = () => multiblockSize;

View File

@ -1,6 +1,7 @@
import { recall, save } from "./storage.js"; import { recall, save } from "./storage.js";
import { registerLoadCallback } from "./main.js";
export function initializeMenuResizing() { registerLoadCallback(() => {
const handles = document.querySelectorAll('[data-resize]'); const handles = document.querySelectorAll('[data-resize]');
handles.forEach(handle => { handles.forEach(handle => {
handle.addEventListener("mousedown", startResize); handle.addEventListener("mousedown", startResize);
@ -9,7 +10,7 @@ export function initializeMenuResizing() {
document.body.style.setProperty(`--${handle.dataset.resize}-size`, stored); document.body.style.setProperty(`--${handle.dataset.resize}-size`, stored);
} }
}); });
} });
function startResize(e) { function startResize(e) {
window.addEventListener("mouseup", endResize); window.addEventListener("mouseup", endResize);

View File

@ -1,7 +1,72 @@
import { configuration } from "./configuration.js"; import { configuration } from "./configuration.js";
import { emitEvent } from "./events.js"; import { emitEvent } from "./events.js";
import { registerLoadCallback } from "./main.js";
import { getPlannerCellDom, getPlannerDom } from "./planner.js";
import { findFullEntry } from "./ncpf-utils.js";
registerLoadCallback(() => {
});
const solverMetadata = {};
let solverMetadataCounter = 0;
function createSolverMetadata(data) {
const id = solverMetadataCounter;
solverMetadata[id] = data;
solverMetadataCounter++;
return id;
}
export function setSolverMetadata(domNode, data) {
if (domNode.dataset.solverMetadata) {
solverMetadata[Number(domNode.dataset.solverMetadata)] = data;
return;
}
domNode.dataset.solverMetadata = createSolverMetadata(data).toString(10);
}
export function getSolverMetadata(id) {
if (typeof id === "object") {
// assume DOM element
if (id.dataset && id.dataset.solverMetadata) {
return getSolverMetadata(id.dataset.solverMetadata);
}
return null;
}
if (typeof id === "string") {
return getSolverMetadata(Number(id));
}
return solverMetadata[id];
}
export function deleteSolverMetadata(id) {
delete solverMetadata[id];
}
export function resetSolver(design = undefined, designConfiguration) {
const planner = getPlannerDom();
if (design && designConfiguration) {
planner.dataset.type = design.type;
if (design.type === "nuclearcraft:overhaul_sfr") {
// design has coolant_recipe and block_recipes
const designCoolantRecipe = designConfiguration.coolant_recipes[design.coolant_recipe];
const fullCoolantRecipe = findFullEntry(
"coolant_recipes",
designCoolantRecipe,
{ configurationHint: design.type },
);
setSolverMetadata(planner, { coolant_recipe: fullCoolantRecipe });
const designBlockRecipes = design.block_recipes.flat(3);
for (let x = 0; x < design.dimensions[0]; x++) {
for (let y = 0; y < design.dimensions[1]; y++) {
for (let z = 0; z < design.dimensions[2]; z++) {
const block = designConfiguration.blocks[design.design[x][y][z]];
if (!block || !block.modules || !block.modules["ncpf:block_recipes"]) continue;
const blockRecipeIndex = designBlockRecipes.shift();
const blockRecipe = block.modules["ncpf:block_recipes"].recipes[blockRecipeIndex];
setSolverMetadata(getPlannerCellDom(x, y, z), { block_recipe: blockRecipe });
}
}
}
}
}
export function resetSolver(design = undefined) {
console.log("solver", configuration, design);
emitEvent("solver:reset:done"); emitEvent("solver:reset:done");
} }

View File

@ -1,5 +1,6 @@
import { debounce } from "./dom-utils.js"; import { debounce } from "./dom-utils.js";
import { emitEvent, listenTo } from "./events.js"; import { emitEvent, listenTo } from "./events.js";
import { registerLoadCallback } from "./main.js";
const tooltips = {}; const tooltips = {};
let tooltipsCounter = 0; let tooltipsCounter = 0;
@ -7,7 +8,7 @@ let lastCoordinates = null;
let suppressTooltips = false; let suppressTooltips = false;
let activeTooltipTarget = null; let activeTooltipTarget = null;
export function initializeTooltips() { registerLoadCallback(() => {
document.querySelectorAll("[data-retain-tooltip-hover]").forEach(elem => document.querySelectorAll("[data-retain-tooltip-hover]").forEach(elem =>
elem.addEventListener("mousemove", e => { elem.addEventListener("mousemove", e => {
if (lastCoordinates !== null) { if (lastCoordinates !== null) {
@ -40,7 +41,7 @@ export function initializeTooltips() {
suppressTooltips = !suppressTooltips; suppressTooltips = !suppressTooltips;
} }
}); });
} });
export function createTooltip(data) { export function createTooltip(data) {
const id = tooltipsCounter; const id = tooltipsCounter;

View File

@ -352,7 +352,9 @@ input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
} }
.dialog > section > footer { .dialog > section > footer {
display: flex; display: flex;
flex-direction: row-reverse; gap: 0.5em;
flex-direction: row;
justify-content: end;
} }
.dialog > section > .list-choice { .dialog > section > .list-choice {
display: flex; display: flex;