Compare commits

..

2 Commits

Author SHA1 Message Date
CodedSakura c63cada6e7 add error popups 2026-07-17 18:41:55 +03:00
CodedSakura 70fb6efa41 allow loading custom configurations 2026-07-17 13:35:43 +03:00
9 changed files with 83 additions and 34 deletions

View File

@ -12,8 +12,10 @@
<button data-alt="n" data-button="file:new">New</button> <button data-alt="n" data-button="file:new">New</button>
<button data-alt="o" data-button="file:load">Open</button> <button data-alt="o" data-button="file:load">Open</button>
<button data-alt="s" data-button="file:save">Save</button> <button data-alt="s" data-button="file:save">Save</button>
<div data-section="Recent Files" data-alt="r"> <div data-section="Configuiration" data-alt="c">
<button disabled>-</button> <button data-alt="d" data-button="load-configuration" data-configuration="default">Default</button>
<button data-alt="e" data-button="load-configuration" data-configuration="e2ee">E2EE</button>
<button data-alt="s" data-button="load-configuration">Select file...</button>
</div> </div>
</div> </div>
<div data-section="Edit" data-alt="e"> <div data-section="Edit" data-alt="e">
@ -181,5 +183,15 @@
</footer> </footer>
</section> </section>
</div> </div>
<div class="dialog hidden dialog--error" data-dialog="error:user">
<section>
<header>Error</header>
<main data-text="error">
</main>
<footer>
<button data-dialog-action="cancel">Close</button>
</footer>
</section>
</div>
</body> </body>
</html> </html>

View File

@ -88,7 +88,7 @@ export async function buildAtlas(data) {
})); }));
if (textures.length === 0) { if (textures.length === 0) {
throw new Error("Configuration processing failed - no textured blocks found"); throw new UserError("Configuration processing failed - no textured blocks found");
} }
await Promise.all(textures.map(getTextureSize)); await Promise.all(textures.map(getTextureSize));
@ -97,7 +97,7 @@ export async function buildAtlas(data) {
let offset = 0; let offset = 0;
for (const t of Object.values(Object.groupBy(textures, t => t.size.toString()))) { for (const t of Object.values(Object.groupBy(textures, t => t.size.toString()))) {
if (t[0].size[0] !== t[0].size[1]) { if (t[0].size[0] !== t[0].size[1]) {
throw new Error("Configuration processing failed - textures must be square"); throw new UserError("Configuration processing failed - textures must be square");
} }
const dedupTextureMap = new Map(); const dedupTextureMap = new Map();

View File

@ -1,9 +1,17 @@
import { buildAtlas } from "./atlas.js"; import { buildAtlas } from "./atlas.js";
import { emitEvent } from "./events.js"; import { emitEvent } from "./events.js";
import { setState, STATE } from "./state.js"; import { setState, STATE } from "./state.js";
import { registerLoadCallback } from "./main.js";
import { addEventToButton } from "./dom-utils.js";
import { showOpenFileDialog } from "./files.js";
import { DevError, UserError } from "./errors.js";
export let configuration = {}; export let configuration = {};
registerLoadCallback(() => {
addEventToButton("load-configuration", "click", loadConfiguration);
});
export async function loadConfiguration(event) { export async function loadConfiguration(event) {
setState(STATE.initialization); setState(STATE.initialization);
@ -12,12 +20,16 @@ export async function loadConfiguration(event) {
if (typeof event === "string") { if (typeof event === "string") {
configData = await fetch(`./configurations/${event}.ncpf.json`) configData = await fetch(`./configurations/${event}.ncpf.json`)
.then(data => data.json()); .then(data => data.json());
} else if (event.target.dataset.configuration) { } else if (event.currentTarget?.dataset?.configuration) {
const configuration = event.target.dataset.configuration; const configuration = event.currentTarget.dataset.configuration;
console.log(configuration); console.log(configuration);
configData = await fetch(`./configurations/${configuration}.ncpf.json`) configData = await fetch(`./configurations/${configuration}.ncpf.json`)
.then(data => data.json()); .then(data => data.json());
} else if (event.target.files[0]) { } else if (event.currentTarget?.tagName === "BUTTON") {
const { data } = await new Promise(resolve => showOpenFileDialog(resolve));
console.log(data);
configData = data;
} else if (event?.target?.files && event.target.files[0]) {
const file = event.target.files[0]; const file = event.target.files[0];
configData = await new Promise(resolve => { configData = await new Promise(resolve => {
const reader = new FileReader(); const reader = new FileReader();
@ -27,11 +39,11 @@ export async function loadConfiguration(event) {
reader.readAsText(file); reader.readAsText(file);
}); });
} else { } else {
throw new Error("Configuration load failed - unrecognised event"); throw new DevError("Configuration load failed - unrecognised event");
} }
if ((configData.designs ?? []).length > 0) { if ((configData.designs ?? []).length > 0) {
throw new Error("Configuration load failed - contains designs, likely not full configuration"); throw new UserError("Configuration load failed - contains designs, not a full configuration!");
} }
emitEvent("config:load:end"); emitEvent("config:load:end");

View File

@ -9,6 +9,7 @@ registerLoadCallback(() => {
listenTo("dialogs:welcome", () => welcomeDialog(true)); listenTo("dialogs:welcome", () => welcomeDialog(true));
listenTo("fileLoad:chooseDesign", designChoiceDialog); listenTo("fileLoad:chooseDesign", designChoiceDialog);
listenTo("fileSave:nameDesign", designNameDialog); listenTo("fileSave:nameDesign", designNameDialog);
listenTo("error:user", errorDialog);
}); });
function initializeDialog(dialogName, endEvent, { emptyValue, onDismiss } = {}) { function initializeDialog(dialogName, endEvent, { emptyValue, onDismiss } = {}) {
@ -88,3 +89,8 @@ function designNameDialog() {
dismiss(new FormData(e.detail.target).get("name")); dismiss(new FormData(e.detail.target).get("name"));
}); });
} }
function errorDialog(e) {
const { dialog } = initializeDialog("error:user", "error:dialog:close");
dialog.querySelectorAll('[data-text="error"]').forEach(elem => elem.innerText = e.detail);
}

15
scripts/errors.js Normal file
View File

@ -0,0 +1,15 @@
import { emitEvent } from "./events.js";
export class UserError extends Error {
constructor(message) {
super(message);
emitEvent("error:user", message);
}
}
export class DevError extends Error {
constructor(message) {
super(message);
emitEvent("error:dev", message);
}
}

View File

@ -10,14 +10,15 @@ import {
import { getAtlasIndexFromMetadata, getAtlasMetadata } from "./atlas.js"; import { getAtlasIndexFromMetadata, getAtlasMetadata } from "./atlas.js";
import { getSolverMetadata, resetSolver } from "./solver.js"; import { getSolverMetadata, resetSolver } from "./solver.js";
import { registerLoadCallback } from "./main.js"; import { registerLoadCallback } from "./main.js";
import { DevError, UserError } from "./errors.js";
registerLoadCallback(() => { registerLoadCallback(() => {
listenTo("file:load", () => showOpenFileDialog()); listenTo("file:load", () => showOpenFileDialog(({ data, filename }) => loadFile(data, filename)));
listenTo("file:new", () => {}); listenTo("file:new", () => {});
listenTo("file:save", () => saveFile()); listenTo("file:save", () => saveFile());
}); });
export function showOpenFileDialog() { export function showOpenFileDialog(callback) {
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
input.accept = ".ncpf.json"; input.accept = ".ncpf.json";
@ -31,7 +32,7 @@ export function showOpenFileDialog() {
}); });
reader.readAsText(input.files[0]); reader.readAsText(input.files[0]);
}); });
void loadFile(data, filename); callback({ data, filename });
}); });
input.click(); input.click();
} }
@ -44,8 +45,8 @@ export async function loadExampleFile(filename) {
} }
export async function loadFile(data, name) { export async function loadFile(data, name) {
if (!data.designs) throw new Error("invalid file"); if (!data.designs) throw new UserError("File must contain a list of designs!");
if (data.designs.length === 0) throw new Error("no designs"); if (data.designs.length === 0) throw new UserError("Fils must contain at least one design!");
let design = data.designs[0]; let design = data.designs[0];
if (data.designs.length > 1) { if (data.designs.length > 1) {
@ -55,7 +56,7 @@ export async function loadFile(data, name) {
design = data.designs[res.detail]; design = data.designs[res.detail];
} }
if (!design) throw new Error("no design"); if (!design) throw new DevError("Must choose a design");
const blocks = data.configuration[design.type].blocks const blocks = data.configuration[design.type].blocks
.map(block => getAtlasIndexFromMetadata(block, design.type, "blocks")); .map(block => getAtlasIndexFromMetadata(block, design.type, "blocks"));

View File

@ -3,9 +3,10 @@ import "./block-picker.js";
import { loadConfiguration } from "./configuration.js"; import { loadConfiguration } from "./configuration.js";
import "./dialogs.js"; import "./dialogs.js";
import "./dom-list.js"; import "./dom-list.js";
import { addEventToButton, addEventToInput, debounce, getElements } from "./dom-utils.js"; import { debounce, getElements } from "./dom-utils.js";
import { awaitEvent, emitEvent, listenTo } from "./events.js"; import "./errors.js";
import "./files.js" import { emitEvent, listenTo } from "./events.js";
import { loadExampleFile } from "./files.js"
import "./js-utils.js" import "./js-utils.js"
import "./menu-bar.js"; import "./menu-bar.js";
import "./ncpf-utils.js"; import "./ncpf-utils.js";
@ -16,7 +17,6 @@ import "./solver.js";
import { getState, setState, STATE } from "./state.js"; import { getState, setState, STATE } from "./state.js";
import "./storage.js"; import "./storage.js";
import "./tooltip.js"; import "./tooltip.js";
import { loadExampleFile } from "./files.js";
let loadCallbacks = []; let loadCallbacks = [];
export function registerLoadCallback(cb) { export function registerLoadCallback(cb) {
@ -38,6 +38,16 @@ window.addEventListener("load", () => {
} }
}); });
listenTo("state", () => {
console.log("State changed", getState());
getElements("state", "*").forEach(e => {
e.classList.add("hidden");
if (e.dataset.state === getState()) {
e.classList.remove("hidden");
}
});
});
getElements("button", "*").forEach(elem => { getElements("button", "*").forEach(elem => {
elem.addEventListener("click", e => { elem.addEventListener("click", e => {
emitEvent(elem.dataset.button, e); emitEvent(elem.dataset.button, e);
@ -67,19 +77,6 @@ window.addEventListener("load", () => {
}); });
}); });
addEventToInput("configuration", "input", loadConfiguration);
addEventToButton("load-configuration", "click", loadConfiguration);
listenTo("state", () => {
console.log("State changed", getState());
getElements("state", "*").forEach(e => {
e.classList.add("hidden");
if (e.dataset.state === getState()) {
e.classList.remove("hidden");
}
});
});
listenTo("config:end", () => setState(STATE.planning)); listenTo("config:end", () => setState(STATE.planning));
runLoadCallbacks(); runLoadCallbacks();

View File

@ -1,5 +1,6 @@
import { configuration } from "./configuration.js"; import { configuration } from "./configuration.js";
import { objectKeepKeys } from "./js-utils.js"; import { objectKeepKeys } from "./js-utils.js";
import { DevError } from "./errors.js";
export function checkEquivalence(a, b) { export function checkEquivalence(a, b) {
if (!a || !b || !a.type || !b.type || a.type !== b.type) return false; if (!a || !b || !a.type || !b.type || a.type !== b.type) return false;
@ -31,7 +32,7 @@ export function checkEquivalence(a, b) {
} }
export function stripEntryToMinimums(entry) { export function stripEntryToMinimums(entry) {
if (!entry.type) throw new Error("must have a type"); if (!entry.type) throw new DevError("must have a type");
switch (entry.type) { switch (entry.type) {
case "legacy_block": case "legacy_block":
return objectKeepKeys(entry, "type", "name", "metadata", "blockstate"); return objectKeepKeys(entry, "type", "name", "metadata", "blockstate");
@ -48,7 +49,7 @@ export function stripEntryToMinimums(entry) {
elements: entry.elements.map(stripEntryToMinimums), elements: entry.elements.map(stripEntryToMinimums),
}; };
} }
throw new Error(`unknown entry type ${entry.type}`); throw new DevError(`unknown entry type ${entry.type}`);
} }
export function findFullEntry(type, entry, { configurationHint, all = false } = {}) { export function findFullEntry(type, entry, { configurationHint, all = false } = {}) {

View File

@ -308,6 +308,11 @@ function* getRecipeTooltip(recipe, metadata) {
console.log("unknown recipe", recipe, metadata); console.log("unknown recipe", recipe, metadata);
} }
if (!metadata) {
console.trace("no metadata for recipe", recipe, metadata);
return;
}
if (metadata.configurationAddon) { if (metadata.configurationAddon) {
yield { yield {
type: "italics", type: "italics",