beggingings of file export, improve button and input events
This commit is contained in:
parent
3e2a17f5cd
commit
b69bcacb78
File diff suppressed because one or more lines are too long
36
index.html
36
index.html
|
|
@ -9,21 +9,24 @@
|
||||||
<body>
|
<body>
|
||||||
<nav id="top-menu" class="menu-bar">
|
<nav id="top-menu" class="menu-bar">
|
||||||
<div data-section="File" data-alt="f">
|
<div data-section="File" data-alt="f">
|
||||||
<button data-alt="n" data-button="new-file">New</button>
|
<button data-alt="n" data-button="file:new">New</button>
|
||||||
<button data-alt="o" data-button="open-file">Open</button>
|
<button data-alt="o" data-button="file:load">Open</button>
|
||||||
|
<button data-alt="s" data-button="file:save">Save</button>
|
||||||
<div data-section="Recent Files" data-alt="r">
|
<div data-section="Recent Files" data-alt="r">
|
||||||
<button disabled>-</button>
|
<button disabled>-</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-section="Edit" data-alt="e">
|
<div data-section="Edit" data-alt="e">
|
||||||
<button data-alt="u" data-button="undo">Undo</button>
|
<button data-alt="u" data-button="edit:undo">Undo</button>
|
||||||
<button data-alt="r" data-button="redo" disabled>Redo</button>
|
<button data-alt="r" data-button="edit:redo" disabled>Redo</button>
|
||||||
</div>
|
</div>
|
||||||
<div data-section="Select" data-alt="s">
|
<div data-section="Select" data-alt="s">
|
||||||
<button data-alt="i">Invert</button>
|
<button data-alt="i">Invert</button>
|
||||||
</div>
|
</div>
|
||||||
<div data-section="View" data-alt="v">
|
<div data-section="View" data-alt="v">
|
||||||
<button data-alt="c" data-toggle data-toggled="false" data-button="show-casing">Show casing</button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<aside id="left-menu" data-state="planning">
|
<aside id="left-menu" data-state="planning">
|
||||||
|
|
@ -31,9 +34,9 @@
|
||||||
<section id="block-picker">
|
<section id="block-picker">
|
||||||
<label>
|
<label>
|
||||||
Filter:
|
Filter:
|
||||||
<input data-input="block-filter">
|
<input data-input="block-picker:filter">
|
||||||
</label>
|
</label>
|
||||||
<div class="list" id="block-list" data-retain-tooltip-hover>
|
<div class="list" id="block-picker-list" data-retain-tooltip-hover>
|
||||||
<template>
|
<template>
|
||||||
<div class="m1"></div>
|
<div class="m1"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -47,6 +50,21 @@
|
||||||
<input type="file" data-input="configuration" accept=".ncpf.json" />
|
<input type="file" data-input="configuration" accept=".ncpf.json" />
|
||||||
</div>
|
</div>
|
||||||
<div data-state="planning">
|
<div data-state="planning">
|
||||||
|
<header class="status-bar">
|
||||||
|
<div>
|
||||||
|
<!--left-->
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<!--center-->
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<!--right-->
|
||||||
|
<label title="Planner zoom level" class="m1">
|
||||||
|
Zoom:
|
||||||
|
<select data-input="planner:zoom"></select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
<div class="list" id="planner" data-retain-tooltip-hover>
|
<div class="list" id="planner" data-retain-tooltip-hover>
|
||||||
<template>
|
<template>
|
||||||
<div class="grid m2">
|
<div class="grid m2">
|
||||||
|
|
@ -61,9 +79,9 @@
|
||||||
<aside id="right-menu">
|
<aside id="right-menu">
|
||||||
<span data-resize="right-menu"></span>
|
<span data-resize="right-menu"></span>
|
||||||
</aside>
|
</aside>
|
||||||
<footer id="bottom-menu">
|
<aside id="bottom-menu">
|
||||||
<span data-resize="bottom-menu"></span>
|
<span data-resize="bottom-menu"></span>
|
||||||
</footer>
|
</aside>
|
||||||
<div class="dialog hidden" data-dialog="fileLoad:chooseDesign">
|
<div class="dialog hidden" data-dialog="fileLoad:chooseDesign">
|
||||||
<section>
|
<section>
|
||||||
<header>Choose a design to open</header>
|
<header>Choose a design to open</header>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ 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.replaceChildren(...this.list.querySelectorAll("template, [data-keep]"));
|
this.list.replaceChildren(...this.list.querySelectorAll(":scope > template, :scope > [data-keep]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
makeItem() {
|
makeItem() {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { emitEvent } from "./events.js";
|
||||||
|
|
||||||
export function getElements(dataType, dataValue, {
|
export function getElements(dataType, dataValue, {
|
||||||
additionalDataFilters = [],
|
additionalDataFilters = [],
|
||||||
additionalPropertyFilters = [],
|
additionalPropertyFilters = [],
|
||||||
|
|
@ -25,6 +27,10 @@ export function addEventToButton(dataValue, event, callback, props = {}) {
|
||||||
addEventToElement("button", dataValue, event, callback, props);
|
addEventToElement("button", dataValue, event, callback, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function emitOnButtonClick(dataValue, eventName, props = {}) {
|
||||||
|
addEventToButton(dataValue, "click", e => emitEvent(eventName, e), props);
|
||||||
|
}
|
||||||
|
|
||||||
export function setText(dataValue, text, props = {}) {
|
export function setText(dataValue, text, props = {}) {
|
||||||
getElements("text", dataValue, props)
|
getElements("text", dataValue, props)
|
||||||
.forEach(e => e.innerText = text);
|
.forEach(e => e.innerText = text);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,13 @@ export function emitEvent(name, data = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listenTo(name, callback) {
|
export function listenTo(name, callback) {
|
||||||
window.addEventListener(`ncp:${name}`, callback);
|
if (!Array.isArray(name)) {
|
||||||
|
name = [name];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const n of name) {
|
||||||
|
window.addEventListener(`ncp:${n}`, callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function awaitEvent(name) {
|
export function awaitEvent(name) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
import { awaitEvent, emitEvent } from "./events.js";
|
import { awaitEvent, emitEvent, listenTo } from "./events.js";
|
||||||
import { clearPlanner, loadPlanner } from "./planner.js";
|
import { findFullEntry, stripEntryToMinimums } from "./ncpf-utils.js";
|
||||||
import { getAtlasIndexFromMetadata } from "./atlas.js";
|
import { getPlannerMultiblockSize, getPlannerMultiblockType, loadPlanner, readPlannerTiles } from "./planner.js";
|
||||||
|
import { getAtlasIndexFromMetadata, getAtlasMetadata } from "./atlas.js";
|
||||||
|
import { resetSolver } from "./solver.js";
|
||||||
|
|
||||||
|
export function initializeFileEvents() {
|
||||||
|
listenTo("file:load", () => showOpenFileDialog());
|
||||||
|
listenTo("file:new", () => {});
|
||||||
|
listenTo("file:save", () => saveFile("test"));
|
||||||
|
}
|
||||||
|
|
||||||
export function showOpenFileDialog() {
|
export function showOpenFileDialog() {
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
|
|
@ -26,6 +34,7 @@ export async function loadExampleFile(filename) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadFile(data) {
|
export async function loadFile(data) {
|
||||||
|
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");
|
||||||
|
|
||||||
|
|
@ -45,4 +54,64 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveFile(name) {
|
||||||
|
const multiblockType = getPlannerMultiblockType();
|
||||||
|
|
||||||
|
const designAsAtlas = readPlannerTiles();
|
||||||
|
const designBlocksAtlasMap = Object.fromEntries(
|
||||||
|
[...new Set(designAsAtlas.flat(3))]
|
||||||
|
.filter(v => v !== -1)
|
||||||
|
.sort((a, b) => a - b)
|
||||||
|
.map((ai, di) => [ ai, { ...getAtlasMetadata(ai), designIndex: di } ])
|
||||||
|
);
|
||||||
|
const designAsBlocks = designAsAtlas
|
||||||
|
.map(xPlane => xPlane
|
||||||
|
.map(yRow => yRow.map(atlasIndex => designBlocksAtlasMap[atlasIndex]))
|
||||||
|
);
|
||||||
|
const designAsBlockIndices = designAsBlocks
|
||||||
|
.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)
|
||||||
|
.sort(([a], [b]) => a - b)
|
||||||
|
.map(([, m]) => stripEntryToMinimums(m));
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
addons: [],
|
||||||
|
configurations: {
|
||||||
|
[multiblockType]: {
|
||||||
|
blocks: minimizedBlocks,
|
||||||
|
coolant_recipes: [], // FIXME
|
||||||
|
},
|
||||||
|
},
|
||||||
|
designs: [
|
||||||
|
{
|
||||||
|
block_recipes: [], // FIXME
|
||||||
|
coolant_recipe: 0, // FIXME
|
||||||
|
design: designAsBlockIndices,
|
||||||
|
dimensions: getPlannerMultiblockSize(),
|
||||||
|
type: multiblockType,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(designBlocksAtlasMap);
|
||||||
|
console.log(data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
export function initialize3dArray(x, y, z, initialValue = undefined) {
|
||||||
|
return Array.from({ length: x })
|
||||||
|
.fill(null)
|
||||||
|
.map(() => Array.from({ length: y })
|
||||||
|
.fill(null)
|
||||||
|
.map(() => Array.from({ length: z }).fill(initialValue))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function objectKeepKeys(object, ...keys) {
|
||||||
|
if (Array.isArray(keys[0])) keys = keys[0];
|
||||||
|
return Object.fromEntries(
|
||||||
|
keys
|
||||||
|
.filter(k => k in object)
|
||||||
|
.map(k => [ k, object[k] ])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ import { loadConfiguration } from "./configuration.js";
|
||||||
import { initializeDialogs } from "./dialogs.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 { loadExampleFile, showOpenFileDialog } from "./files.js";
|
import { initializeFileEvents, loadExampleFile, saveFile, showOpenFileDialog } from "./files.js";
|
||||||
import { initializePlanner } from "./planner.js";
|
import { initializePlanner } from "./planner.js";
|
||||||
import { initializeMenuResizing } from "./resize-menus.js";
|
import { initializeMenuResizing } from "./resize-menus.js";
|
||||||
import { getState, setState, STATE } from "./state.js";
|
import { getState, setState, STATE } from "./state.js";
|
||||||
|
|
@ -19,12 +19,31 @@ window.addEventListener("load", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
getElements("button", "*").forEach(elem => {
|
||||||
|
elem.addEventListener("click", e => {
|
||||||
|
emitEvent(elem.dataset.button, e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
getElements("toggle", "*").forEach(elem => {
|
||||||
|
elem.addEventListener("click", e => {
|
||||||
|
const wasChecked = e.currentTarget.dataset.toggled === "true";
|
||||||
|
const newState = !wasChecked;
|
||||||
|
e.currentTarget.dataset.toggled = newState.toString();
|
||||||
|
emitEvent(elem.dataset.toggle, { state: newState, event: e });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
getElements("input", "*").forEach(elem => {
|
||||||
|
elem.addEventListener("keydown", e => e.stopPropagation());
|
||||||
|
elem.addEventListener("input", debounce(e => {
|
||||||
|
emitEvent(elem.dataset.input + ":input", e);
|
||||||
|
}));
|
||||||
|
elem.addEventListener("change", e => {
|
||||||
|
emitEvent(elem.dataset.input + ":change", e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
addEventToInput("configuration", "input", loadConfiguration);
|
addEventToInput("configuration", "input", loadConfiguration);
|
||||||
addEventToButton("load-configuration", "click", loadConfiguration);
|
addEventToButton("load-configuration", "click", loadConfiguration);
|
||||||
addEventToInput("block-filter", "input", debounce(e => {
|
|
||||||
emitEvent("block-picker:filter", e.target.value);
|
|
||||||
}));
|
|
||||||
addEventToButton("open-file", "click", showOpenFileDialog);
|
|
||||||
|
|
||||||
listenTo("state", () => {
|
listenTo("state", () => {
|
||||||
console.log("State changed", getState());
|
console.log("State changed", getState());
|
||||||
|
|
@ -38,6 +57,7 @@ window.addEventListener("load", () => {
|
||||||
|
|
||||||
listenTo("config:end", () => setState(STATE.planning));
|
listenTo("config:end", () => setState(STATE.planning));
|
||||||
|
|
||||||
|
initializeFileEvents();
|
||||||
initializePlanner();
|
initializePlanner();
|
||||||
initializeMenuResizing();
|
initializeMenuResizing();
|
||||||
initializeTooltips();
|
initializeTooltips();
|
||||||
|
|
@ -50,4 +70,5 @@ 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"));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { configuration } from "./configuration.js";
|
import { configuration } from "./configuration.js";
|
||||||
|
import { objectKeepKeys } from "./js-utils.js";
|
||||||
|
|
||||||
export function checkEquivalence(a, b) {
|
export function checkEquivalence(a, b) {
|
||||||
if (!a.type || !b.type || a.type !== b.type) return false;
|
if (!a.type || !b.type || a.type !== b.type) return false;
|
||||||
|
|
@ -17,6 +18,18 @@ export function checkEquivalence(a, b) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stripEntryToMinimums(entry) {
|
||||||
|
if (!entry.type) throw new Error("must have a type");
|
||||||
|
switch (entry.type) {
|
||||||
|
case "legacy_block":
|
||||||
|
return objectKeepKeys(entry, "type", "name", "metadata", "blockstate");
|
||||||
|
|
||||||
|
case "oredict":
|
||||||
|
return objectKeepKeys(entry, "type", "oredict");
|
||||||
|
}
|
||||||
|
throw new Error(`unknown entry type ${entry.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
export function findFullEntry(type, entry, { configurationHint }) {
|
export function findFullEntry(type, entry, { configurationHint }) {
|
||||||
const possibleEntries = [
|
const possibleEntries = [
|
||||||
...Object.entries(configuration.configuration),
|
...Object.entries(configuration.configuration),
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,181 @@
|
||||||
import { atlases, makeAtlasImage } from "./atlas.js";
|
import { atlases, makeAtlasImage } from "./atlas.js";
|
||||||
import { DomList } from "./dom-list.js";
|
import { DomList } from "./dom-list.js";
|
||||||
|
import { getElements } from "./dom-utils.js";
|
||||||
import { emitEvent, listenTo } from "./events.js";
|
import { emitEvent, listenTo } from "./events.js";
|
||||||
import { Filter } from "./filter.js";
|
import { Filter } from "./filter.js";
|
||||||
|
import { initialize3dArray } from "./js-utils.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 { addEventToButton } from "./dom-utils.js";
|
|
||||||
|
const zoomOptions = [ 0.5, 1, 2, 3, 4, 5 ];
|
||||||
|
const zoomDefault = 2;
|
||||||
|
const zoomStorageKey = "zoom-size";
|
||||||
|
|
||||||
let suppressHover = false;
|
let suppressHover = false;
|
||||||
let multiblockSize = [9, 7, 9]; // width (screen x), height (xy grids), depth (screen y)
|
let multiblockSize = [9, 7, 9]; // width (screen x), height (xy grids), depth (screen y)
|
||||||
let multiblockType = "nuclearcraft:overhaul_sfr";
|
let multiblockType = "nuclearcraft:overhaul_sfr";
|
||||||
let activeItem = -1;
|
let activeItem = -1;
|
||||||
|
|
||||||
|
export function initializePlanner() {
|
||||||
|
listenTo("config:end", buildBlockPicker);
|
||||||
|
listenTo("config:end", buildPlanner);
|
||||||
|
|
||||||
|
listenTo("block-picker:filter:input", buildBlockPicker);
|
||||||
|
|
||||||
|
listenTo("planner:toggle-casing", e => {
|
||||||
|
const planner = document.getElementById("planner");
|
||||||
|
planner.dataset.casing = e.detail.state ? "show" : "hide";
|
||||||
|
});
|
||||||
|
|
||||||
|
const zoomLevel = recall(zoomStorageKey, zoomDefault);
|
||||||
|
document.getElementById("planner").style
|
||||||
|
.setProperty("--scale-factor", zoomLevel);
|
||||||
|
document.querySelectorAll('select[data-input="planner:zoom"]').forEach(elem => {
|
||||||
|
for (const op of zoomOptions) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = op.toString(10);
|
||||||
|
option.innerText = `${op * 100}%`;
|
||||||
|
option.selected = zoomLevel === op;
|
||||||
|
elem.appendChild(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
listenTo("planner:zoom:change", e => {
|
||||||
|
const value = Number(e.detail.currentTarget.value);
|
||||||
|
if (zoomOptions.includes(value)) {
|
||||||
|
emitEvent("planner:zoom:set", value);
|
||||||
|
} else {
|
||||||
|
console.warn("bad planner zoom value", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
listenTo(["planner:zoom:+", "planner:zoom:-"], e => {
|
||||||
|
const zoomIn = e.type === "ncp:planner:zoom:+";
|
||||||
|
const currentScale = Number(document.getElementById("planner").style
|
||||||
|
.getPropertyValue("--scale-factor") ?? zoomDefault);
|
||||||
|
const currentIndex = zoomOptions.indexOf(currentScale);
|
||||||
|
if (zoomIn && currentIndex < zoomOptions.length - 1) {
|
||||||
|
emitEvent("planner:zoom:set", zoomOptions[currentIndex + 1]);
|
||||||
|
}
|
||||||
|
if (!zoomIn && currentIndex > 0) {
|
||||||
|
emitEvent("planner:zoom:set", zoomOptions[currentIndex - 1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
listenTo("planner:zoom:set", e => {
|
||||||
|
const scale = e.detail;
|
||||||
|
const scaleIndex = zoomOptions.indexOf(scale);
|
||||||
|
document.getElementById("planner").style
|
||||||
|
.setProperty("--scale-factor", scale.toString(10));
|
||||||
|
|
||||||
|
document.querySelectorAll('select[data-input="planner:zoom"]')
|
||||||
|
.forEach(elem => elem.value = scale);
|
||||||
|
|
||||||
|
getElements("button", "planner:zoom:+")
|
||||||
|
.forEach(e => e.disabled = scaleIndex >= zoomOptions.length - 1);
|
||||||
|
getElements("button", "planner:zoom:-")
|
||||||
|
.forEach(e => e.disabled = scaleIndex <= 0);
|
||||||
|
|
||||||
|
save(zoomStorageKey, scale);
|
||||||
|
});
|
||||||
|
listenTo("block-picker:selection", clearSelectedCells);
|
||||||
|
|
||||||
|
document.addEventListener("keydown", e => {
|
||||||
|
if (e.key === "H") {
|
||||||
|
suppressHover = !suppressHover;
|
||||||
|
console.log("Suppressing hover:", suppressHover);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBlockPicker() {
|
||||||
|
const filter = Filter.fromInput("block-picker:filter");
|
||||||
|
|
||||||
|
const grid = new DomList(document.getElementById("block-picker-list"));
|
||||||
|
grid.clear();
|
||||||
|
|
||||||
|
atlases.flatMap(a => a.metadata)
|
||||||
|
.filter(m => m.configuration === multiblockType && m.configurationList === "blocks")
|
||||||
|
.forEach(m => {
|
||||||
|
const item = grid.makeItem();
|
||||||
|
item.dataset.index = m.index.toString(10);
|
||||||
|
const tooltipData = [...getBlockTooltip(m)];
|
||||||
|
item.dataset.tooltip = createTooltip(tooltipData).toString(10);
|
||||||
|
if (!filter.test(...tooltipDataToPlain(tooltipData))) {
|
||||||
|
item.classList.add("hidden");
|
||||||
|
}
|
||||||
|
item.appendChild(makeAtlasImage(m.index));
|
||||||
|
item.addEventListener("click", listClickEvent);
|
||||||
|
grid.addItem(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function listClickEvent(e) {
|
||||||
|
const items = document.querySelectorAll("#block-picker-list [data-index]");
|
||||||
|
if (e.currentTarget.dataset.active !== undefined) {
|
||||||
|
items.forEach(i => delete i.dataset.active);
|
||||||
|
activeItem = -1;
|
||||||
|
delete e.currentTarget.dataset.active;
|
||||||
|
emitEvent("block-picker:selection", activeItem);
|
||||||
|
emitEvent("block-picker:selection:clear");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
items.forEach(i => delete i.dataset.active);
|
||||||
|
activeItem = Number(e.currentTarget.dataset.index);
|
||||||
|
e.currentTarget.dataset.active = "";
|
||||||
|
emitEvent("block-picker:selection", activeItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPlanner(textures) {
|
||||||
|
// handle case when 1st argument is event
|
||||||
|
if (!Array.isArray(textures)) textures = undefined;
|
||||||
|
|
||||||
|
const planner = document.getElementById("planner");
|
||||||
|
planner.dataset.casing = "hide";
|
||||||
|
|
||||||
|
const list = new DomList(planner);
|
||||||
|
list.clear();
|
||||||
|
|
||||||
|
for (let y = 0; y < multiblockSize[1]; y++) {
|
||||||
|
const item = list.makeItem();
|
||||||
|
item.style.setProperty("--width", multiblockSize[0]);
|
||||||
|
item.dataset.y = y.toString(10);
|
||||||
|
if (y === 0 || y === multiblockSize[1] - 1) {
|
||||||
|
item.dataset.casing = "";
|
||||||
|
}
|
||||||
|
const grid = new DomList(item);
|
||||||
|
grid.clear();
|
||||||
|
for (let z = 0; z < multiblockSize[2]; z++) {
|
||||||
|
for (let x = 0; x < multiblockSize[0]; x++) {
|
||||||
|
const texture = textures ? textures[x][y][z] : -1;
|
||||||
|
if (textures && !texture) {
|
||||||
|
console.log(x, y, z, textures);
|
||||||
|
}
|
||||||
|
const cell = grid.makeItem();
|
||||||
|
cell.dataset.x = x.toString(10);
|
||||||
|
cell.dataset.y = y.toString(10);
|
||||||
|
cell.dataset.z = z.toString(10);
|
||||||
|
cell.dataset.tile = texture.toString(10);
|
||||||
|
if (x === 0 || x === multiblockSize[0] - 1
|
||||||
|
|| y === 0 || y === multiblockSize[1] - 1
|
||||||
|
|| z === 0 || z === multiblockSize[2] - 1) {
|
||||||
|
cell.dataset.casing = "";
|
||||||
|
}
|
||||||
|
cell.appendChild(makeAtlasImage(texture));
|
||||||
|
cell.addEventListener("mouseenter", cellHoverEvent);
|
||||||
|
cell.addEventListener("mouseleave", cellHoverEvent);
|
||||||
|
cell.addEventListener("mousedown", cellClickEvent);
|
||||||
|
cell.addEventListener("mouseenter", cellClickEvent);
|
||||||
|
cell.addEventListener("mouseleave", cellClickEvent);
|
||||||
|
cell.addEventListener("contextmenu", cellClickEvent);
|
||||||
|
grid.addItem(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.addItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function cellHoverEvent(e) {
|
function cellHoverEvent(e) {
|
||||||
if (suppressHover) return;
|
if (suppressHover) return;
|
||||||
|
if (activeItem === -1) return;
|
||||||
|
|
||||||
const cells = document.querySelectorAll("#planner [data-x]");
|
const cells = document.querySelectorAll("#planner [data-x]");
|
||||||
cells.forEach(c => {
|
cells.forEach(c => {
|
||||||
|
|
@ -50,11 +213,15 @@ function cellClickEvent(e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.buttons === 1 && activeItem === -1) return;
|
if (activeItem === -1) {
|
||||||
|
cellSelectEvent(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.type === "mousedown") {
|
if (e.type === "mousedown") {
|
||||||
window.addEventListener("mouseup", cellClickEvent);
|
window.addEventListener("mouseup", cellClickEvent);
|
||||||
emitEvent("planner:drag:start");
|
emitEvent("planner:drag:start");
|
||||||
|
clearSelectedCells();
|
||||||
}
|
}
|
||||||
if (e.type === "mouseup") {
|
if (e.type === "mouseup") {
|
||||||
window.removeEventListener("mouseup", cellClickEvent);
|
window.removeEventListener("mouseup", cellClickEvent);
|
||||||
|
|
@ -97,105 +264,35 @@ function cellClickEvent(e) {
|
||||||
e.target.dataset.preview = activeItem.toString(10);
|
e.target.dataset.preview = activeItem.toString(10);
|
||||||
e.target.replaceChildren(makeAtlasImage(activeItem));
|
e.target.replaceChildren(makeAtlasImage(activeItem));
|
||||||
}
|
}
|
||||||
|
function cellSelectEvent(e) {
|
||||||
function listClickEvent(e) {
|
if (e.type === "mousedown" && !e.ctrlKey && e.buttons !== 2) {
|
||||||
const items = document.querySelectorAll("#block-list [data-index]");
|
clearSelectedCells();
|
||||||
items.forEach(i => delete i.dataset.active);
|
|
||||||
activeItem = Number(e.currentTarget.dataset.index);
|
|
||||||
e.currentTarget.dataset.active = "";
|
|
||||||
}
|
}
|
||||||
|
if (e.buttons === 2) {
|
||||||
export function initializePlanner() {
|
// remove
|
||||||
listenTo("block-picker:filter", buildBlockPicker);
|
const wasSelected = "selected" in e.target.dataset;
|
||||||
|
delete e.target.dataset.selected;
|
||||||
listenTo("config:end", buildBlockPicker);
|
if (wasSelected) {
|
||||||
|
emitEvent("planner:selection:removed", e.target);
|
||||||
listenTo("config:end", buildPlanner);
|
|
||||||
|
|
||||||
addEventToButton("show-casing", "click", e => {
|
|
||||||
const wasChecked = e.currentTarget.dataset.toggled === "true";
|
|
||||||
const show = !wasChecked;
|
|
||||||
e.currentTarget.dataset.toggled = show.toString();
|
|
||||||
|
|
||||||
const planner = document.querySelector("#planner");
|
|
||||||
planner.dataset.casing = show ? "show" : "hide";
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("keydown", e => {
|
|
||||||
if (e.key === "H") {
|
|
||||||
suppressHover = !suppressHover;
|
|
||||||
}
|
}
|
||||||
|
} else if (e.buttons === 1) {
|
||||||
|
// add
|
||||||
|
const wasSelected = "selected" in e.target.dataset;
|
||||||
|
e.target.dataset.selected = "";
|
||||||
|
if (!wasSelected) {
|
||||||
|
emitEvent("planner:selection:added", e.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function clearSelectedCells() {
|
||||||
|
getSelectedCells()
|
||||||
|
.forEach(cell => {
|
||||||
|
delete cell.dataset.selected;
|
||||||
|
emitEvent("planner:selection:removed", cell);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
export function getSelectedCells() {
|
||||||
function buildBlockPicker() {
|
return document.querySelectorAll("#planner [data-selected]");
|
||||||
const filter = Filter.fromInput("block-filter");
|
|
||||||
|
|
||||||
const grid = new DomList(document.querySelector("#block-list"));
|
|
||||||
grid.clear();
|
|
||||||
|
|
||||||
atlases.flatMap(a => a.metadata)
|
|
||||||
.filter(m => m.configuration === multiblockType && m.configurationList === "blocks")
|
|
||||||
.forEach(m => {
|
|
||||||
const item = grid.makeItem();
|
|
||||||
item.dataset.index = m.index.toString(10);
|
|
||||||
const tooltipData = [...getBlockTooltip(m)];
|
|
||||||
item.dataset.tooltip = createTooltip(tooltipData).toString(10);
|
|
||||||
if (!filter.test(...tooltipDataToPlain(tooltipData))) {
|
|
||||||
item.classList.add("hidden");
|
|
||||||
}
|
|
||||||
item.appendChild(makeAtlasImage(m.index));
|
|
||||||
item.addEventListener("click", listClickEvent);
|
|
||||||
grid.addItem(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildPlanner(textures) {
|
|
||||||
// handle case when 1st argument is event
|
|
||||||
if (!Array.isArray(textures)) textures = undefined;
|
|
||||||
|
|
||||||
const planner = document.querySelector("#planner");
|
|
||||||
planner.dataset.casing = "hide";
|
|
||||||
|
|
||||||
const list = new DomList(planner);
|
|
||||||
list.clear();
|
|
||||||
|
|
||||||
for (let y = 0; y < multiblockSize[1]; y++) {
|
|
||||||
const item = list.makeItem();
|
|
||||||
item.style.setProperty("--width", multiblockSize[0]);
|
|
||||||
if (y === 0 || y === multiblockSize[1] - 1) {
|
|
||||||
item.dataset.casing = "";
|
|
||||||
}
|
|
||||||
const grid = new DomList(item);
|
|
||||||
grid.clear();
|
|
||||||
for (let z = 0; z < multiblockSize[2]; z++) {
|
|
||||||
for (let x = 0; x < multiblockSize[0]; x++) {
|
|
||||||
const texture = textures ? textures[x][y][z] : -1;
|
|
||||||
if (textures && !texture) {
|
|
||||||
console.log(x, y, z, textures);
|
|
||||||
}
|
|
||||||
const cell = grid.makeItem();
|
|
||||||
cell.dataset.x = x.toString(10);
|
|
||||||
cell.dataset.y = y.toString(10);
|
|
||||||
cell.dataset.z = z.toString(10);
|
|
||||||
cell.dataset.tile = texture.toString(10);
|
|
||||||
if (x === 0 || x === multiblockSize[0] - 1
|
|
||||||
|| y === 0 || y === multiblockSize[1] - 1
|
|
||||||
|| z === 0 || z === multiblockSize[2] - 1) {
|
|
||||||
cell.dataset.casing = "";
|
|
||||||
}
|
|
||||||
cell.appendChild(makeAtlasImage(texture));
|
|
||||||
cell.addEventListener("mouseenter", cellHoverEvent);
|
|
||||||
cell.addEventListener("mouseleave", cellHoverEvent);
|
|
||||||
cell.addEventListener("mousedown", cellClickEvent);
|
|
||||||
cell.addEventListener("mouseenter", cellClickEvent);
|
|
||||||
cell.addEventListener("mouseleave", cellClickEvent);
|
|
||||||
cell.addEventListener("contextmenu", cellClickEvent);
|
|
||||||
grid.addItem(cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list.addItem(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearPlanner(size, type) {
|
export function clearPlanner(size, type) {
|
||||||
|
|
@ -209,6 +306,25 @@ export function loadPlanner(size, type, textures) {
|
||||||
buildPlanner(textures);
|
buildPlanner(textures);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function readPlannerTiles() {
|
||||||
|
const planner = document.getElementById("planner");
|
||||||
|
const tiles = initialize3dArray(...multiblockSize, -1);
|
||||||
|
const grids = [...planner.children].filter(e => e.dataset.y);
|
||||||
|
for (let y = 0; y < multiblockSize[1]; y++) {
|
||||||
|
const grid = grids[y];
|
||||||
|
const gridTiles = [...grid.children].filter(e => e.dataset.tile);
|
||||||
|
for (let z = 0; z < multiblockSize[2]; z++) {
|
||||||
|
for (let x = 0; x < multiblockSize[0]; x++) {
|
||||||
|
tiles[x][y][z] = Number(gridTiles[x + z * multiblockSize[0]].dataset.tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPlannerMultiblockType = () => multiblockType;
|
||||||
|
export const getPlannerMultiblockSize = () => multiblockSize;
|
||||||
|
|
||||||
function* getBlockTooltip(metadata) {
|
function* getBlockTooltip(metadata) {
|
||||||
const data = lookupFromMetadata(metadata);
|
const data = lookupFromMetadata(metadata);
|
||||||
|
|
||||||
|
|
@ -336,7 +452,7 @@ function parseRuleAsTooltip(rule, props) {
|
||||||
}
|
}
|
||||||
if (rule.min === rule.max) {
|
if (rule.min === rule.max) {
|
||||||
return `exactly ${rule.min} axial ${block}`;
|
return `exactly ${rule.min} axial ${block}`;
|
||||||
} else if (rule.max === 6) {
|
} else if (rule.max === 3) {
|
||||||
return `at least ${rule.min} axial ${block}`;
|
return `at least ${rule.min} axial ${block}`;
|
||||||
} else if (rule.min === 0) {
|
} else if (rule.min === 0) {
|
||||||
return `no more than ${rule.max} axial ${block}`;
|
return `no more than ${rule.max} axial ${block}`;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { configuration } from "./configuration.js";
|
||||||
|
import { emitEvent } from "./events.js";
|
||||||
|
|
||||||
|
export function resetSolver(design = undefined) {
|
||||||
|
console.log("solver", configuration, design);
|
||||||
|
emitEvent("solver:reset:done");
|
||||||
|
}
|
||||||
36
style.css
36
style.css
|
|
@ -85,6 +85,14 @@ main {
|
||||||
border-top: 1px solid var(--menu-border-color)
|
border-top: 1px solid var(--menu-border-color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-bar {
|
||||||
|
border-bottom: 1px solid var(--status-bar-color, #4444);
|
||||||
|
}
|
||||||
|
.status-bar > :nth-child(3) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
#left-menu, #right-menu, #bottom-menu {
|
#left-menu, #right-menu, #bottom-menu {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -173,11 +181,13 @@ input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
|
||||||
width: calc(var(--scale-factor) * var(--size-x, var(--default-size)));
|
width: calc(var(--scale-factor) * var(--size-x, var(--default-size)));
|
||||||
height: calc(var(--scale-factor) * var(--size-y, var(--default-size)));
|
height: calc(var(--scale-factor) * var(--size-y, var(--default-size)));
|
||||||
outline: calc(var(--scale-factor) * 0.5px) solid var(--cell-empty-outline-color, #ccc);
|
outline: calc(var(--scale-factor) * 0.5px) solid var(--cell-empty-outline-color, #ccc);
|
||||||
|
outline-offset: calc(var(--scale-factor) * -0.5px);
|
||||||
|
|
||||||
opacity: var(--atlas-opacity, 1);
|
opacity: var(--atlas-opacity, 1);
|
||||||
}
|
}
|
||||||
.atlas-air:hover, .atlas-texture:hover {
|
.atlas-air:hover, .atlas-texture:hover {
|
||||||
outline: calc(var(--scale-factor) * 1px) solid var(--cell-hover-outline-color, #8888);
|
outline: calc(var(--scale-factor) * 1px) solid var(--cell-hover-outline-color, #8888);
|
||||||
|
outline-offset: calc(var(--scale-factor) * -1px);
|
||||||
}
|
}
|
||||||
[data-preview], [data-preview-remove] {
|
[data-preview], [data-preview-remove] {
|
||||||
--atlas-opacity: var(--atlas-preview-opacity, 0.5);
|
--atlas-opacity: var(--atlas-preview-opacity, 0.5);
|
||||||
|
|
@ -222,16 +232,24 @@ input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
|
||||||
[data-casing="hide"] [data-casing] {
|
[data-casing="hide"] [data-casing] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
[data-selected] {
|
||||||
|
outline: calc(var(--scale-factor) * 1px) dashed var(--cell-selected-outline, #f80);
|
||||||
|
outline-offset: calc(var(--scale-factor) * -1px);
|
||||||
|
}
|
||||||
|
[data-selected] .atlas-air {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
#block-list [data-active] {
|
#block-picker-list [data-active] {
|
||||||
outline: calc(var(--scale-factor) * 1px) solid var(--block-active-outline-color, #000);
|
outline: calc(var(--scale-factor) * 1px) solid var(--block-active-outline-color, #000);
|
||||||
}
|
}
|
||||||
#block-list [data-index]:hover:not([data-active]) {
|
#block-picker-list [data-index]:hover:not([data-active]) {
|
||||||
outline: calc(var(--scale-factor) * 1px) solid var(--block-hover-outline-color, #6666);
|
outline: calc(var(--scale-factor) * 1px) solid var(--block-hover-outline-color, #6666);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-tooltip] {
|
[data-tooltip] {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
[data-tooltip] > .tooltip {
|
[data-tooltip] > .tooltip {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
@ -287,14 +305,20 @@ input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
|
||||||
.menu-bar .alt-key {
|
.menu-bar .alt-key {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
.menu-bar [data-section]:has(> [data-toggle]) > button:not([data-toggle]) {
|
.menu-bar [data-section]:has(> [data-toggle]) > button {
|
||||||
padding-left: 1em;
|
display: grid;
|
||||||
|
grid-template-columns: 1.25em 1fr;
|
||||||
|
}
|
||||||
|
.menu-bar [data-section]:has(> [data-toggle]) > button::before {
|
||||||
|
content: '';
|
||||||
|
inline-size: 1.25em;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
.menu-bar button[data-toggle][data-toggled="false"]::before {
|
.menu-bar button[data-toggle][data-toggled="false"]::before {
|
||||||
content: '[ ] ';
|
content: '☐';
|
||||||
}
|
}
|
||||||
.menu-bar button[data-toggle][data-toggled="true"]::before {
|
.menu-bar button[data-toggle][data-toggled="true"]::before {
|
||||||
content: '[x] ';
|
content: '☑';
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog.hidden {
|
.dialog.hidden {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue