Compare commits

...

2 Commits

Author SHA1 Message Date
CodedSakura a46f8d85a0 implement metadata controlled block overlays (WIP) 2026-07-27 16:49:05 +03:00
CodedSakura 521ff4e757 move metadata to it's own file 2026-07-27 15:04:55 +02:00
12 changed files with 149 additions and 76 deletions

View File

@ -37,6 +37,7 @@
<button data-alt="i" data-button="planner:zoom:+">Zoom in</button>
<button data-alt="o" data-button="planner:zoom:-">Zoom out</button>
<button data-alt="c" data-toggle="planner:toggle-casing" data-toggled="false">Show casing</button>
<button data-alt="r" data-toggle="planner:toggle-block-recipe-overlay" data-toggled="true">Show block recipes</button>
<button data-alt="c" data-button="dialogs:welcome">Show welcome</button>
</div>
</nav>
@ -90,7 +91,7 @@
<template>
<div class="grid m2">
<template>
<div></div>
<div class="planner--cell"></div>
</template>
</div>
</template>
@ -125,7 +126,7 @@
</section>
</aside>
<footer>
<div>pre-alpha 1.4</div>
<div>pre-alpha 1.5</div>
<div>
<a href="https://discord.gg/Eq72HwPqfg">Discord server</a>
</div>

View File

@ -1,6 +1,7 @@
import { emitEvent } from "./events.js";
import { checkEquivalence } from "./ncpf-utils.js";
import { registerLoadCallback } from "./main.js";
import { getPlannerMultiblockType } from "./planner.js";
const defaultSize = 16; // px
@ -192,7 +193,10 @@ export function getAtlasMetadata(index) {
return null;
}
export function getAtlasIndexFromMetadata(metadata, configuration, configurationList = null) {
export function getAtlasIndexFromMetadata(metadata, configuration = null, configurationList = null) {
if (!configuration) {
configuration = getPlannerMultiblockType();
}
let index = 0;
for (const atlas of atlases) {
for (const atlasItemMeta of atlas.metadata) {

View File

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

View File

@ -9,9 +9,10 @@ import {
readPlannerCellData
} from "./planner.js";
import { getAtlasIndexFromMetadata, getAtlasMetadata } from "./atlas.js";
import { getSolverMetadata, resetSolver } from "./solver.js";
import { resetSolver } from "./solver.js";
import { registerLoadCallback } from "./main.js";
import { DevError, UserError } from "./errors.js";
import { getMetadata } from "./metadata.js";
registerLoadCallback(() => {
listenTo("file:load", () => showOpenFileDialog(({ data, filename }) => loadFile(data, filename)));
@ -131,7 +132,7 @@ export async function saveFile(name = undefined) {
if (!fullBlock || !fullBlock.modules || !("ncpf:block_recipes" in fullBlock.modules)) return;
const designIndex = designBlocksAtlasMap[Number(designAsCellData[x][y][z].tile)].designIndex;
const solverMetadata = getSolverMetadata(designAsCellData[x][y][z].solverMetadata);
const solverMetadata = getMetadata(designAsCellData[x][y][z].solverMetadata);
const minimizedBlock = minimizedBlocks[designIndex];
let recipeIndex = -1;
@ -168,7 +169,7 @@ export async function saveFile(name = undefined) {
if (multiblockType === "nuclearcraft:overhaul_sfr") {
const coolantRecipes = [];
let coolantRecipeIndex;
const plannerMetadata = getSolverMetadata(getPlannerDom());
const plannerMetadata = getMetadata(getPlannerDom());
coolantRecipes.push(stripEntryToMinimums(plannerMetadata.coolant_recipe));
coolantRecipeIndex = 0;

View File

@ -10,7 +10,9 @@ import { awaitEvent, emitEvent, listenTo } from "./events.js";
import "./files.js"
import "./js-utils.js"
import "./menu-bar.js";
import "./metadata.js";
import "./ncpf-utils.js";
import "./overlays.js";
import "./planner.js";
import "./planner-resize.js";
import "./recipe-selector.js";

58
scripts/metadata.js Normal file
View File

@ -0,0 +1,58 @@
import { clearCellOverlays, processCellOverlays } from "./overlays.js";
const metadata = {};
let metadataCounter = 0;
function createMetadata(data) {
const id = metadataCounter;
metadata[id] = data;
metadataCounter++;
return id;
}
export function setDomMetadata(domNode, data) {
if (domNode.dataset.solverMetadata) {
metadata[Number(domNode.dataset.solverMetadata)] = data;
return;
}
domNode.dataset.solverMetadata = createMetadata(data).toString(10);
}
export function setCellMetadata(node, key, value) {
if (!node.dataset.solverMetadata) {
node.dataset.solverMetadata = createMetadata({ [key]: value }).toString(10);
} else {
metadata[Number(node.dataset.solverMetadata)][key] = value;
}
processCellOverlays(node, key, true);
}
export function getMetadata(id) {
if (typeof id === "object") {
// assume DOM element
if (id.dataset && id.dataset.solverMetadata) {
return getMetadata(id.dataset.solverMetadata);
}
return null;
}
if (typeof id === "string") {
return getMetadata(Number(id));
}
return metadata[id];
}
export function getCellMetadata(node, key) {
return metadata[Number(node.dataset.solverMetadata)][key];
}
export function deleteMetadata(id) {
if (typeof id === "object") {
// assume DOM object
if (id.dataset && id.dataset.solverMetadata) {
clearCellOverlays(id);
return deleteMetadata(id.dataset.solverMetadata);
}
}
delete metadata[id];
}
export function clearCellMetadata(node, key) {
if (!node.dataset.solverMetadata) return;
delete metadata[Number(node.dataset.solverMetadata)][key];
processCellOverlays(node, key, false);
}

21
scripts/overlays.js Normal file
View File

@ -0,0 +1,21 @@
import { getCellMetadata } from "./metadata.js";
import { getAtlasIndexFromMetadata, makeAtlasImage } from "./atlas.js";
export function processCellOverlays(node, key = null, added = null) {
if (key) {
const metadata = getCellMetadata(node, key);
const atlasIndex = getAtlasIndexFromMetadata(metadata);
if (atlasIndex && atlasIndex !== -1) {
const overlayNode = makeAtlasImage(atlasIndex);
overlayNode.dataset.overlay = key;
overlayNode.classList.add("planner--overlay");
node.appendChild(overlayNode);
}
// FIXME: handle remove
}
// TODO: handle generic update
}
export function clearCellOverlays(node) {
// TODO
}

View File

@ -5,7 +5,7 @@ import {
createPlannerPlane,
getPlannerDom,
getPlannerMultiblockSize, getPlannerMultiblockType,
maxMultiblockSize
maxMultiblockSize, minMultiblockSize
} from "./planner.js";
import { DomList } from "./dom-list.js";
@ -20,7 +20,7 @@ registerLoadCallback(() => {
awaitEvent("dev-mode:end").then(async () => {
await awaitEvent("fileLoad:end");
initializeResize();
// initializeResize();
});
});
@ -67,7 +67,7 @@ function highlight(axis, n, add) {
});
}
function resize(axis, n, add, e) {
function resize(axis, n, add) {
const planner = getPlannerDom();
const size = getPlannerMultiblockSize();
if (!add) {
@ -133,20 +133,20 @@ function initializeResize() {
newDiv.dataset.casing = "";
}
if (!casing && size[1] > maxMultiblockSize[type][1]) {
if (!casing && size[1] > minMultiblockSize[type][1]) {
newDiv.appendChild(createButton({ axis: "y", add: false, n: gridIndex }));
}
if (!casing) {
for (let x = 0; x < size[0] - 1 && size[0] < maxMultiblockSize[type][0]; x++) {
newDiv.appendChild(createButton({ axis: "x", add: true, n: x+1, topLeft: true }));
}
for (let x = 0; x < size[0] - 2 && size[0] > maxMultiblockSize[type][0]; x++) {
for (let x = 0; x < size[0] - 2 && size[0] > minMultiblockSize[type][0]; x++) {
newDiv.appendChild(createButton({ axis: "x", add: false, n: x+1, topLeft: true }));
}
for (let z = 0; z < size[2] - 1 && size[2] < maxMultiblockSize[type][2]; z++) {
newDiv.appendChild(createButton({ axis: "z", add: true, n: z+1, topLeft: true }));
}
for (let z = 0; z < size[2] - 2 && size[2] > maxMultiblockSize[type][2]; z++) {
for (let z = 0; z < size[2] - 2 && size[2] > minMultiblockSize[type][2]; z++) {
newDiv.appendChild(createButton({ axis: "z", add: false, n: z+1, topLeft: true }));
}
}
@ -158,13 +158,13 @@ function initializeResize() {
for (let x = 0; x < size[0] - 1 && size[0] < maxMultiblockSize[type][0]; x++) {
newDiv.appendChild(createButton({ axis: "x", add: true, n: x+1, topLeft: false }));
}
for (let x = 0; x < size[0] - 2 && size[0] > maxMultiblockSize[type][0]; x++) {
for (let x = 0; x < size[0] - 2 && size[0] > minMultiblockSize[type][0]; x++) {
newDiv.appendChild(createButton({ axis: "x", add: false, n: x+1, topLeft: false }));
}
for (let z = 0; z < size[2] - 1 && size[2] < maxMultiblockSize[type][2]; z++) {
newDiv.appendChild(createButton({ axis: "z", add: true, n: z+1, topLeft: false }));
}
for (let z = 0; z < size[2] - 2 && size[2] > maxMultiblockSize[type][2]; z++) {
for (let z = 0; z < size[2] - 2 && size[2] > minMultiblockSize[type][2]; z++) {
newDiv.appendChild(createButton({ axis: "z", add: false, n: z+1, topLeft: false }));
}
}
@ -186,13 +186,14 @@ function fixPlannerNumbering() {
const planner = getPlannerDom();
const size = getPlannerMultiblockSize();
const grids = planner.querySelectorAll(":scope > .planner--resize > .grid");
grids.forEach(grid => {
grids.forEach((grid, y) => {
grid.style.setProperty("--width", size[0].toString(10));
const tiles = [...grid.querySelectorAll("[data-tile]")];
for (let z = 0; z < size[2]; z++) {
for (let x = 0; x < size[0]; x++) {
const tile = tiles.shift();
tile.dataset.x = x.toString(10);
tile.dataset.y = y.toString(10);
tile.dataset.z = z.toString(10);
}
}

View File

@ -6,9 +6,9 @@ import { initialize3dArray } from "./js-utils.js";
import { recall, save } from "./storage.js";
import { registerLoadCallback } from "./main.js";
import { getBlockPickerActiveItem, getBlockPickerRecipe } from "./block-picker.js";
import { deleteSolverMetadata, getSolverMetadata, setSolverMetadata } from "./solver.js";
import { clearCellMetadata, getMetadata, setCellMetadata } from "./metadata.js";
import { retainTooltipHover } from "./tooltip.js";
import { checkEquivalence, findFullEntry, findFullEntryAnywhere, lookupFromMetadata } from "./ncpf-utils.js";
import { checkEquivalence, lookupFromMetadata } from "./ncpf-utils.js";
const zoomOptions = [ 0.5, 1, 2, 3, 4, 5 ];
const zoomDefault = 2;
@ -17,6 +17,9 @@ const zoomStorageKey = "zoom-size";
const casingStorageKey = "casing";
const casingDefault = false; // hidden by default
const recipeOverlayStorageKey = "recipeOverlay";
const recipeOverlayDefault = true; // shown by default
const defaultMultiblockSize = [9, 7, 9]; // width (screen x), height (xy grids), depth (screen y)
const defaultMultiblockType = "nuclearcraft:overhaul_sfr";
export const maxMultiblockSize = {
@ -42,6 +45,15 @@ registerLoadCallback(() => {
document.body.dataset.casing = e.detail.state ? "show" : "hide";
});
const recipeOverlayShow = recall(recipeOverlayStorageKey, recipeOverlayDefault);
document.body.dataset.recipeOverlay = recipeOverlayShow ? "show" : "hide";
getElements("toggle", "planner:toggle-block-recipe-overlay")
.forEach(e => e.dataset.toggled = recipeOverlayShow.toString());
listenTo("planner:toggle-block-recipe-overlay", e => {
save(recipeOverlayStorageKey, e.detail.state);
document.body.dataset.recipeOverlay = e.detail.state ? "show" : "hide";
});
const zoomLevel = recall(zoomStorageKey, zoomDefault);
document.getElementById("planner").style
.setProperty("--scale-factor", zoomLevel);
@ -238,7 +250,7 @@ function cellClickEvent(e) {
if (e.target.tile === "-1") return;
e.target.dataset.previewRemove = "yes";
deleteSolverMetadata(e.target);
clearCellMetadata(e.target, "block_recipe");
} else if (e.buttons === 1) {
// add
if (e.target.dataset.preview === activeItem.toString(10)) return;
@ -247,7 +259,7 @@ function cellClickEvent(e) {
e.target.replaceChildren(makeAtlasImage(activeItem));
const pickerRecipe = getBlockPickerRecipe();
if (pickerRecipe) {
setSolverMetadata(e.target, { block_recipe: pickerRecipe });
setCellMetadata(e.target, "block_recipe", pickerRecipe);
}
}
}
@ -327,7 +339,7 @@ export function* getCellTooltip(cell) {
}
const metadata = getAtlasMetadata(cell.dataset.tile)
const data = lookupFromMetadata(metadata);
const solverMetadata = getSolverMetadata(cell);
const solverMetadata = getMetadata(cell);
yield data.modules["plannerator:display_name"].display_name;

View File

@ -4,15 +4,15 @@ import { configuration } from "./configuration.js";
import { getPlannerDom, getPlannerMultiblockType, getSelectedCells } from "./planner.js";
import { DomList } from "./dom-list.js";
import { getAtlasIndexFromMetadata, getAtlasMetadata, makeAtlasImage } from "./atlas.js";
import { deleteSolverMetadata, getSolverMetadata, setSolverMetadata } from "./solver.js";
import { clearCellMetadata, getMetadata, setCellMetadata, setDomMetadata } from "./metadata.js";
import {
checkEquivalence,
findFullEntry,
findFullEntryAnywhere,
findFullEntryFromAtlasMetadata,
lookupFromMetadata, stripEntryToMinimums
stripEntryToMinimums
} from "./ncpf-utils.js";
import { createTooltip, setTooltip, tooltipDataToPlain } from "./tooltip.js";
import { setTooltip, tooltipDataToPlain } from "./tooltip.js";
import { Filter } from "./filter.js";
import { debounce } from "./dom-utils.js";
import { getBlockPickerActiveItem, setBlockPickerRecipe } from "./block-picker.js";
@ -154,11 +154,11 @@ function rescanDesignRecipes() {
const designItem = designRecipes.makeItem();
const plannerDom = getPlannerDom();
const plannerMetadata = getSolverMetadata(plannerDom);
const plannerMetadata = getMetadata(plannerDom);
let currentSelection = plannerMetadata?.coolant_recipe;
if (!plannerMetadata) {
currentSelection = defaultRecipes[multiblockType].coolant_recipe;
setSolverMetadata(plannerDom, {
setDomMetadata(plannerDom, {
coolant_recipe: currentSelection,
});
}
@ -169,7 +169,7 @@ function rescanDesignRecipes() {
title: "Coolant Recipe",
entryListType: "coolant_recipes",
recipes: coolantRecipes,
updateSelection: r => setSolverMetadata(plannerDom, { coolant_recipe: r }),
updateSelection: r => setDomMetadata(plannerDom, { coolant_recipe: r }),
multiblockType,
currentSelection,
});
@ -204,7 +204,7 @@ function rescanSelectionRecipes() {
return;
}
const cellRecipes = selectedCells.map(v => getSolverMetadata(v)?.block_recipe);
const cellRecipes = selectedCells.map(v => getMetadata(v)?.block_recipe);
const recipeSet = cellRecipes.some(v => v);
const recipeAllSame = cellRecipes.every(v => v === cellRecipes[0] || checkEquivalence(v, cellRecipes[0]));
console.log(">", cellRecipes, recipeSet, recipeAllSame);
@ -238,11 +238,11 @@ function rescanSelectionRecipes() {
multiblockType,
updateSelection: (e) => {
if (e === recipes[0]) {
selectedCells.forEach(v => deleteSolverMetadata(v));
selectedCells.forEach(v => clearCellMetadata(v, "block_recipe"));
return;
}
const stripped = stripEntryToMinimums(e);
selectedCells.forEach(v => setSolverMetadata(v, { block_recipe: stripped }));
selectedCells.forEach(v => setCellMetadata(v, "block_recipe", stripped));
if (pickerTile !== -1) {
setBlockPickerRecipe(e);
}

View File

@ -1,49 +1,7 @@
import { configuration } from "./configuration.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) {
if (typeof id === "object") {
// assume DOM object
if (id.dataset && id.dataset.solverMetadata) {
return deleteSolverMetadata(id.dataset.solverMetadata);
}
}
delete solverMetadata[id];
}
import { setCellMetadata, setDomMetadata } from "./metadata.js";
export function resetSolver(design = undefined, designConfiguration) {
const planner = getPlannerDom();
@ -57,7 +15,7 @@ export function resetSolver(design = undefined, designConfiguration) {
designCoolantRecipe,
{ configurationHint: design.type },
);
setSolverMetadata(planner, { coolant_recipe: fullCoolantRecipe });
setDomMetadata(planner, { coolant_recipe: fullCoolantRecipe });
const designBlockRecipes = design.block_recipes.flat(3);
for (let x = 0; x < design.dimensions[0]; x++) {
@ -67,7 +25,7 @@ export function resetSolver(design = undefined, designConfiguration) {
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 });
setCellMetadata(getPlannerCellDom(x, y, z), "block_recipe", blockRecipe);
}
}
}

View File

@ -240,12 +240,18 @@ input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
calc(var(--scale-factor) * var(--size-x, var(--default-size)) * 0.4);
background-color: var(--highlight-color, #f808);
}
[data-casing="hide"] #planner .grid {
--width-override: calc(var(--width) - 2);
}
[data-casing="hide"] [data-casing] {
display: none;
}
[data-recipe-overlay="hide"] .planner--overlay[data-overlay="block_recipe"] {
display: none;
}
[data-selected] {
outline: calc(var(--scale-factor) * 1px) dashed var(--cell-selected-outline, #f80);
outline-offset: calc(var(--scale-factor) * -1px);
@ -254,6 +260,15 @@ input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
outline: none;
}
.planner--cell {
position: relative;
}
.planner--cell > .planner--overlay {
position: absolute;
inset: 0;
opacity: var(--overlay-opacity, 0.8);
}
.planner--resize {
--resize-width: var(--width);
--resize-height: var(--height);