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>
|
||||
<nav id="top-menu" class="menu-bar">
|
||||
<div data-section="File" data-alt="f">
|
||||
<button data-alt="n" data-button="new-file">New</button>
|
||||
<button data-alt="o" data-button="open-file">Open</button>
|
||||
<button data-alt="n" data-button="file:new">New</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">
|
||||
<button disabled>-</button>
|
||||
</div>
|
||||
</div>
|
||||
<div data-section="Edit" data-alt="e">
|
||||
<button data-alt="u" data-button="undo">Undo</button>
|
||||
<button data-alt="r" data-button="redo" disabled>Redo</button>
|
||||
<button data-alt="u" data-button="edit:undo">Undo</button>
|
||||
<button data-alt="r" data-button="edit:redo" disabled>Redo</button>
|
||||
</div>
|
||||
<div data-section="Select" data-alt="s">
|
||||
<button data-alt="i">Invert</button>
|
||||
</div>
|
||||
<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>
|
||||
</nav>
|
||||
<aside id="left-menu" data-state="planning">
|
||||
|
|
@ -31,9 +34,9 @@
|
|||
<section id="block-picker">
|
||||
<label>
|
||||
Filter:
|
||||
<input data-input="block-filter">
|
||||
<input data-input="block-picker:filter">
|
||||
</label>
|
||||
<div class="list" id="block-list" data-retain-tooltip-hover>
|
||||
<div class="list" id="block-picker-list" data-retain-tooltip-hover>
|
||||
<template>
|
||||
<div class="m1"></div>
|
||||
</template>
|
||||
|
|
@ -47,6 +50,21 @@
|
|||
<input type="file" data-input="configuration" accept=".ncpf.json" />
|
||||
</div>
|
||||
<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>
|
||||
<template>
|
||||
<div class="grid m2">
|
||||
|
|
@ -61,9 +79,9 @@
|
|||
<aside id="right-menu">
|
||||
<span data-resize="right-menu"></span>
|
||||
</aside>
|
||||
<footer id="bottom-menu">
|
||||
<aside id="bottom-menu">
|
||||
<span data-resize="bottom-menu"></span>
|
||||
</footer>
|
||||
</aside>
|
||||
<div class="dialog hidden" data-dialog="fileLoad:chooseDesign">
|
||||
<section>
|
||||
<header>Choose a design to open</header>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export class DomList {
|
|||
|
||||
clear() {
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { emitEvent } from "./events.js";
|
||||
|
||||
export function getElements(dataType, dataValue, {
|
||||
additionalDataFilters = [],
|
||||
additionalPropertyFilters = [],
|
||||
|
|
@ -25,6 +27,10 @@ export function addEventToButton(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 = {}) {
|
||||
getElements("text", dataValue, props)
|
||||
.forEach(e => e.innerText = text);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,13 @@ export function emitEvent(name, data = null) {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
import { awaitEvent, emitEvent } from "./events.js";
|
||||
import { clearPlanner, loadPlanner } from "./planner.js";
|
||||
import { getAtlasIndexFromMetadata } from "./atlas.js";
|
||||
import { awaitEvent, emitEvent, listenTo } from "./events.js";
|
||||
import { findFullEntry, stripEntryToMinimums } from "./ncpf-utils.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() {
|
||||
const input = document.createElement("input");
|
||||
|
|
@ -26,6 +34,7 @@ export async function loadExampleFile(filename) {
|
|||
}
|
||||
|
||||
export async function loadFile(data) {
|
||||
console.log(data);
|
||||
if (!data.designs) throw new Error("invalid file");
|
||||
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])));
|
||||
|
||||
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 { addEventToButton, addEventToInput, debounce, getElements } from "./dom-utils.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 { initializeMenuResizing } from "./resize-menus.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);
|
||||
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", () => {
|
||||
console.log("State changed", getState());
|
||||
|
|
@ -38,6 +57,7 @@ window.addEventListener("load", () => {
|
|||
|
||||
listenTo("config:end", () => setState(STATE.planning));
|
||||
|
||||
initializeFileEvents();
|
||||
initializePlanner();
|
||||
initializeMenuResizing();
|
||||
initializeTooltips();
|
||||
|
|
@ -50,4 +70,5 @@ window.addEventListener("load", () => {
|
|||
|
||||
awaitEvent("config:end").then(() => loadExampleFile("tiny 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 { objectKeepKeys } from "./js-utils.js";
|
||||
|
||||
export function checkEquivalence(a, b) {
|
||||
if (!a.type || !b.type || a.type !== b.type) return false;
|
||||
|
|
@ -17,6 +18,18 @@ export function checkEquivalence(a, b) {
|
|||
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 }) {
|
||||
const possibleEntries = [
|
||||
...Object.entries(configuration.configuration),
|
||||
|
|
|
|||
|
|
@ -1,18 +1,181 @@
|
|||
import { atlases, makeAtlasImage } from "./atlas.js";
|
||||
import { DomList } from "./dom-list.js";
|
||||
import { getElements } from "./dom-utils.js";
|
||||
import { emitEvent, listenTo } from "./events.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 { 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 multiblockSize = [9, 7, 9]; // width (screen x), height (xy grids), depth (screen y)
|
||||
let multiblockType = "nuclearcraft:overhaul_sfr";
|
||||
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) {
|
||||
if (suppressHover) return;
|
||||
if (activeItem === -1) return;
|
||||
|
||||
const cells = document.querySelectorAll("#planner [data-x]");
|
||||
cells.forEach(c => {
|
||||
|
|
@ -50,11 +213,15 @@ function cellClickEvent(e) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (e.buttons === 1 && activeItem === -1) return;
|
||||
if (activeItem === -1) {
|
||||
cellSelectEvent(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.type === "mousedown") {
|
||||
window.addEventListener("mouseup", cellClickEvent);
|
||||
emitEvent("planner:drag:start");
|
||||
clearSelectedCells();
|
||||
}
|
||||
if (e.type === "mouseup") {
|
||||
window.removeEventListener("mouseup", cellClickEvent);
|
||||
|
|
@ -97,105 +264,35 @@ function cellClickEvent(e) {
|
|||
e.target.dataset.preview = activeItem.toString(10);
|
||||
e.target.replaceChildren(makeAtlasImage(activeItem));
|
||||
}
|
||||
|
||||
function listClickEvent(e) {
|
||||
const items = document.querySelectorAll("#block-list [data-index]");
|
||||
items.forEach(i => delete i.dataset.active);
|
||||
activeItem = Number(e.currentTarget.dataset.index);
|
||||
e.currentTarget.dataset.active = "";
|
||||
function cellSelectEvent(e) {
|
||||
if (e.type === "mousedown" && !e.ctrlKey && e.buttons !== 2) {
|
||||
clearSelectedCells();
|
||||
}
|
||||
|
||||
export function initializePlanner() {
|
||||
listenTo("block-picker:filter", buildBlockPicker);
|
||||
|
||||
listenTo("config:end", buildBlockPicker);
|
||||
|
||||
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;
|
||||
if (e.buttons === 2) {
|
||||
// remove
|
||||
const wasSelected = "selected" in e.target.dataset;
|
||||
delete e.target.dataset.selected;
|
||||
if (wasSelected) {
|
||||
emitEvent("planner:selection:removed", e.target);
|
||||
}
|
||||
} 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);
|
||||
});
|
||||
}
|
||||
|
||||
function buildBlockPicker() {
|
||||
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 getSelectedCells() {
|
||||
return document.querySelectorAll("#planner [data-selected]");
|
||||
}
|
||||
|
||||
export function clearPlanner(size, type) {
|
||||
|
|
@ -209,6 +306,25 @@ export function loadPlanner(size, type, 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) {
|
||||
const data = lookupFromMetadata(metadata);
|
||||
|
||||
|
|
@ -336,7 +452,7 @@ function parseRuleAsTooltip(rule, props) {
|
|||
}
|
||||
if (rule.min === rule.max) {
|
||||
return `exactly ${rule.min} axial ${block}`;
|
||||
} else if (rule.max === 6) {
|
||||
} else if (rule.max === 3) {
|
||||
return `at least ${rule.min} axial ${block}`;
|
||||
} else if (rule.min === 0) {
|
||||
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)
|
||||
}
|
||||
|
||||
.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 {
|
||||
position: relative;
|
||||
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)));
|
||||
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-offset: calc(var(--scale-factor) * -0.5px);
|
||||
|
||||
opacity: var(--atlas-opacity, 1);
|
||||
}
|
||||
.atlas-air:hover, .atlas-texture:hover {
|
||||
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] {
|
||||
--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] {
|
||||
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);
|
||||
}
|
||||
#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);
|
||||
}
|
||||
|
||||
[data-tooltip] {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
[data-tooltip] > .tooltip {
|
||||
position: fixed;
|
||||
|
|
@ -287,14 +305,20 @@ input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
|
|||
.menu-bar .alt-key {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.menu-bar [data-section]:has(> [data-toggle]) > button:not([data-toggle]) {
|
||||
padding-left: 1em;
|
||||
.menu-bar [data-section]:has(> [data-toggle]) > button {
|
||||
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 {
|
||||
content: '[ ] ';
|
||||
content: '☐';
|
||||
}
|
||||
.menu-bar button[data-toggle][data-toggled="true"]::before {
|
||||
content: '[x] ';
|
||||
content: '☑';
|
||||
}
|
||||
|
||||
.dialog.hidden {
|
||||
|
|
|
|||
Loading…
Reference in New Issue