implement block placing, better layout, resizing, begin tooltips
This commit is contained in:
parent
8dcb131ff2
commit
5781faaa47
|
|
@ -0,0 +1,20 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = 120
|
||||||
|
tab_width = 4
|
||||||
|
|
||||||
|
[{*.cjs,*.es6,*.js,*.mjs}]
|
||||||
|
indent_size = 2
|
||||||
|
ij_continuation_indent_size = 4
|
||||||
|
ij_javascript_spaces_within_imports = true
|
||||||
|
ij_javascript_spaces_within_array_initializer_brackets = true
|
||||||
|
ij_javascript_spaces_within_object_literal_braces = true
|
||||||
|
|
||||||
|
[*.html]
|
||||||
|
indent_size = 2
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
13
index.html
13
index.html
|
|
@ -14,10 +14,11 @@
|
||||||
<button>View</button>
|
<button>View</button>
|
||||||
</nav>
|
</nav>
|
||||||
<nav id="left-menu" data-state="planning">
|
<nav id="left-menu" data-state="planning">
|
||||||
|
<span data-resize="left-menu"></span>
|
||||||
<section id="block-picker">
|
<section id="block-picker">
|
||||||
<label>
|
<label>
|
||||||
Filter:
|
Filter:
|
||||||
<input data-input="filter">
|
<input data-input="block-filter">
|
||||||
</label>
|
</label>
|
||||||
<div class="list" id="block-list">
|
<div class="list" id="block-list">
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -29,7 +30,7 @@
|
||||||
<main>
|
<main>
|
||||||
<div data-state="initialization">
|
<div data-state="initialization">
|
||||||
Select configuration file:
|
Select configuration file:
|
||||||
<button data-button="loadConfiguration" data-configuration="default">Default (2026-07-06)</button>
|
<button data-button="load-configuration" data-configuration="default">Default (2026-07-06)</button>
|
||||||
<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">
|
||||||
|
|
@ -44,7 +45,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<nav id="right-menu"></nav>
|
<nav id="right-menu">
|
||||||
<nav id="bottom-menu"></nav>
|
<span data-resize="right-menu"></span>
|
||||||
|
</nav>
|
||||||
|
<nav id="bottom-menu">
|
||||||
|
<span data-resize="bottom-menu"></span>
|
||||||
|
</nav>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -103,6 +103,7 @@ export function makeAtlasImage(index) {
|
||||||
const y = atlasIndexY * size[1];
|
const y = atlasIndexY * size[1];
|
||||||
|
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
|
div.draggable = false;
|
||||||
div.classList.add("atlas-texture");
|
div.classList.add("atlas-texture");
|
||||||
div.style.backgroundImage = `url(${atlasBlobURL})`;
|
div.style.backgroundImage = `url(${atlasBlobURL})`;
|
||||||
div.style.setProperty("--atlas-resolution", `${atlasResolution}px`);
|
div.style.setProperty("--atlas-resolution", `${atlasResolution}px`);
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,13 @@ export function addEventToInput(dataValue, event, callback, props = {}) {
|
||||||
export function addEventToButton(dataValue, event, callback, props = {}) {
|
export function addEventToButton(dataValue, event, callback, props = {}) {
|
||||||
addEventToElement("button", dataValue, event, callback, props);
|
addEventToElement("button", dataValue, event, callback, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function debounce(fn, milliseconds = 200) {
|
||||||
|
let timeout = null;
|
||||||
|
return (e) => {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
timeout = setTimeout(() => fn(e), milliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
export function emitEvent(name) {
|
export function emitEvent(name, data = null) {
|
||||||
|
if (data !== null && data !== undefined) {
|
||||||
|
window.dispatchEvent(new CustomEvent(`ncp:${name}`, { detail: data }));
|
||||||
|
} else {
|
||||||
window.dispatchEvent(new Event(`ncp:${name}`));
|
window.dispatchEvent(new Event(`ncp:${name}`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listenTo(name, callback) {
|
export function listenTo(name, callback) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
export class Filter {
|
||||||
|
match = [];
|
||||||
|
negate = [];
|
||||||
|
|
||||||
|
constructor(query) {
|
||||||
|
query ??= "";
|
||||||
|
query = query.trim();
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/16261693
|
||||||
|
for (let match of (query.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [])) {
|
||||||
|
const negate = match[0] === '-';
|
||||||
|
if (negate) {
|
||||||
|
match = match.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match[0] === '"' && match[match.length - 1] === '"') {
|
||||||
|
match = match.slice(1, match.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
match = match.trim().toLocaleLowerCase();
|
||||||
|
|
||||||
|
if (!match) continue;
|
||||||
|
|
||||||
|
if (negate) {
|
||||||
|
this.negate.push(match);
|
||||||
|
} else {
|
||||||
|
this.match.push(match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromInput(dataValue) {
|
||||||
|
const elem = document.querySelector(`[data-input='${dataValue}']`);
|
||||||
|
return new Filter(elem.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(...data) {
|
||||||
|
for (const datum of data) {
|
||||||
|
const normalized = datum.trim().toLocaleLowerCase().trim();
|
||||||
|
for (const n of this.negate) {
|
||||||
|
if (normalized.includes(n)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const m of this.match) {
|
||||||
|
if (normalized.includes(m)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.match.length === 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,54 +1,15 @@
|
||||||
import { atlasMetadata, makeAtlasImage } from "./atlas.js";
|
import { atlasMetadata, makeAtlasImage } from "./atlas.js";
|
||||||
import { DomList } from "./dom-list.js";
|
import { DomList } from "./dom-list.js";
|
||||||
import { addEventToButton, addEventToInput, getElements } from "./dom-utils.js";
|
import { addEventToButton, addEventToInput, debounce, getElements } from "./dom-utils.js";
|
||||||
import { emitEvent, listenTo } from "./events.js";
|
import { emitEvent, listenTo } from "./events.js";
|
||||||
import { loadConfiguration } from "./configuration.js";
|
import { loadConfiguration } from "./configuration.js";
|
||||||
import { getState, setState, STATE } from "./state.js";
|
import { getState, setState, STATE } from "./state.js";
|
||||||
|
import { initializePlanner } from "./planner.js";
|
||||||
let multiblockSize = [ 7, 7, 5 ]; // width (screen x), depth (screen y), height (xy grids)
|
import { initializeMenuResizing } from "./resize-menus.js";
|
||||||
let suppressHover = false;
|
|
||||||
let activeItem = -1;
|
|
||||||
|
|
||||||
function cellHoverEvent(e) {
|
|
||||||
if (suppressHover) return;
|
|
||||||
|
|
||||||
const cells = document.querySelectorAll("#planner-list [data-x]");
|
|
||||||
cells.forEach(c => delete c.dataset.highlight);
|
|
||||||
if (e.type === "mouseenter") {
|
|
||||||
cells.forEach(c => {
|
|
||||||
if (c.dataset.y === e.target.dataset.y && c.dataset.z === e.target.dataset.z) {
|
|
||||||
c.dataset.highlight = "x";
|
|
||||||
}
|
|
||||||
if (c.dataset.x === e.target.dataset.x && c.dataset.z === e.target.dataset.z) {
|
|
||||||
c.dataset.highlight = "y";
|
|
||||||
}
|
|
||||||
if (c.dataset.x === e.target.dataset.x && c.dataset.y === e.target.dataset.y) {
|
|
||||||
c.dataset.highlight = "z";
|
|
||||||
}
|
|
||||||
if (c === e.target) {
|
|
||||||
delete c.dataset.highlight;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
emitEvent("setup-start");
|
emitEvent("setup-start");
|
||||||
|
|
||||||
window.addEventListener("keypress", e => {
|
|
||||||
console.log(e);
|
|
||||||
if (e.key === "H") {
|
|
||||||
suppressHover = !suppressHover;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
getElements("state", "*").forEach(e => {
|
getElements("state", "*").forEach(e => {
|
||||||
e.classList.add("hidden");
|
e.classList.add("hidden");
|
||||||
if (e.dataset.state === getState()) {
|
if (e.dataset.state === getState()) {
|
||||||
|
|
@ -57,7 +18,10 @@ window.addEventListener("load", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
addEventToInput("configuration", "input", loadConfiguration);
|
addEventToInput("configuration", "input", loadConfiguration);
|
||||||
addEventToButton("loadConfiguration", "click", loadConfiguration)
|
addEventToButton("load-configuration", "click", loadConfiguration);
|
||||||
|
addEventToInput("block-filter", "input", debounce(e => {
|
||||||
|
emitEvent("block-picker:filter", e.target.value);
|
||||||
|
}));
|
||||||
|
|
||||||
listenTo("state", () => {
|
listenTo("state", () => {
|
||||||
console.log("State changed", getState());
|
console.log("State changed", getState());
|
||||||
|
|
@ -71,44 +35,8 @@ window.addEventListener("load", () => {
|
||||||
|
|
||||||
listenTo("config:end", () => setState(STATE.planning));
|
listenTo("config:end", () => setState(STATE.planning));
|
||||||
|
|
||||||
listenTo("config:end", () => {
|
initializePlanner();
|
||||||
console.log(atlasMetadata);
|
initializeMenuResizing();
|
||||||
const grid = new DomList(document.querySelector("#block-list"));
|
|
||||||
grid.clear();
|
|
||||||
|
|
||||||
atlasMetadata.forEach((m, i) => {
|
|
||||||
const item = grid.makeItem();
|
|
||||||
item.dataset.index = i.toString(10);
|
|
||||||
item.appendChild(makeAtlasImage(i));
|
|
||||||
item.addEventListener("click", listClickEvent);
|
|
||||||
grid.addItem(item);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
listenTo("config:end", () => {
|
|
||||||
const list = new DomList(document.querySelector("#planner-list"));
|
|
||||||
list.clear();
|
|
||||||
|
|
||||||
for (let z = 0; z < multiblockSize[2]; z++) {
|
|
||||||
const item = list.makeItem();
|
|
||||||
item.style.setProperty("--width", multiblockSize[1]);
|
|
||||||
const grid = new DomList(item);
|
|
||||||
grid.clear();
|
|
||||||
for (let y = 0; y < multiblockSize[1]; y++) {
|
|
||||||
for (let x = 0; x < multiblockSize[0]; x++) {
|
|
||||||
const cell = grid.makeItem();
|
|
||||||
cell.dataset.x = x.toString(10);
|
|
||||||
cell.dataset.y = y.toString(10);
|
|
||||||
cell.dataset.z = z.toString(10);
|
|
||||||
cell.appendChild(makeAtlasImage(-1));
|
|
||||||
cell.addEventListener("mouseenter", cellHoverEvent);
|
|
||||||
cell.addEventListener("mouseleave", cellHoverEvent);
|
|
||||||
grid.addItem(cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list.addItem(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
emitEvent("setup-end");
|
emitEvent("setup-end");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
import {atlasMetadata, makeAtlasImage} from "./atlas.js";
|
||||||
|
import {DomList} from "./dom-list.js";
|
||||||
|
import { emitEvent, listenTo } from "./events.js";
|
||||||
|
import { Filter } from "./filter.js";
|
||||||
|
|
||||||
|
let suppressHover = false;
|
||||||
|
let multiblockSize = [7, 7, 5]; // width (screen x), depth (screen y), height (xy grids)
|
||||||
|
let activeItem = -1;
|
||||||
|
|
||||||
|
function cellHoverEvent(e) {
|
||||||
|
if (suppressHover) return;
|
||||||
|
|
||||||
|
const cells = document.querySelectorAll("#planner-list [data-x]");
|
||||||
|
cells.forEach(c => {
|
||||||
|
delete c.dataset.highlight;
|
||||||
|
delete c.dataset.highlightDistance;
|
||||||
|
});
|
||||||
|
if (e.type === "mouseenter") {
|
||||||
|
cells.forEach(c => {
|
||||||
|
if (c.dataset.y === e.target.dataset.y && c.dataset.z === e.target.dataset.z) {
|
||||||
|
c.dataset.highlight = "x";
|
||||||
|
c.dataset.highlightDistance = Math.abs(c.dataset.x - e.target.dataset.x).toString(10);
|
||||||
|
}
|
||||||
|
if (c.dataset.x === e.target.dataset.x && c.dataset.z === e.target.dataset.z) {
|
||||||
|
c.dataset.highlight = "y";
|
||||||
|
c.dataset.highlightDistance = Math.abs(c.dataset.y - e.target.dataset.y).toString(10);
|
||||||
|
}
|
||||||
|
if (c.dataset.x === e.target.dataset.x && c.dataset.y === e.target.dataset.y) {
|
||||||
|
c.dataset.highlight = "z";
|
||||||
|
c.dataset.highlightDistance = Math.abs(c.dataset.z - e.target.dataset.z).toString(10);
|
||||||
|
}
|
||||||
|
if (c === e.target) {
|
||||||
|
delete c.dataset.highlight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cellClickEvent(e) {
|
||||||
|
if (e.type === "contextmenu") {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.buttons !== 1 && e.buttons !== 2 && e.type !== "mouseup") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.buttons === 1 && activeItem === -1) return;
|
||||||
|
|
||||||
|
if (e.type === "mousedown") {
|
||||||
|
window.addEventListener("mouseup", cellClickEvent);
|
||||||
|
emitEvent("planner:drag:start");
|
||||||
|
}
|
||||||
|
if (e.type === "mouseup") {
|
||||||
|
window.removeEventListener("mouseup", cellClickEvent);
|
||||||
|
emitEvent("planner:drag:end");
|
||||||
|
|
||||||
|
const cells = document.querySelectorAll("#planner-list [data-x]");
|
||||||
|
cells.forEach(c => {
|
||||||
|
if (c.dataset.preview) {
|
||||||
|
c.dataset.tile = c.dataset.preview;
|
||||||
|
emitEvent("planner:cell:change", c);
|
||||||
|
delete c.dataset.preview;
|
||||||
|
}
|
||||||
|
if (c.dataset.previewRemove) {
|
||||||
|
delete c.dataset.previewRemove;
|
||||||
|
c.replaceChildren(makeAtlasImage(-1));
|
||||||
|
c.dataset.tile = (-1).toString(10);
|
||||||
|
emitEvent("planner:cell:change", c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.buttons === 2) {
|
||||||
|
// remove
|
||||||
|
|
||||||
|
if (e.target.tile === "-1") return;
|
||||||
|
|
||||||
|
e.target.dataset.previewRemove = "yes";
|
||||||
|
return;
|
||||||
|
} else if (e.buttons === 1) {
|
||||||
|
// add
|
||||||
|
if (e.target.dataset.preview === activeItem.toString(10)) return;
|
||||||
|
|
||||||
|
e.target.dataset.preview = activeItem.toString(10);
|
||||||
|
e.target.replaceChildren(makeAtlasImage(activeItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.target.dataset.preview === activeItem.toString(10)) return;
|
||||||
|
|
||||||
|
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 = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initializePlanner() {
|
||||||
|
listenTo("block-picker:filter", e => {
|
||||||
|
buildBlockPicker();
|
||||||
|
});
|
||||||
|
|
||||||
|
listenTo("config:end", buildBlockPicker);
|
||||||
|
|
||||||
|
listenTo("config:end", buildPlanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBlockPicker() {
|
||||||
|
const filter = Filter.fromInput("block-filter");
|
||||||
|
console.log(filter);
|
||||||
|
|
||||||
|
const grid = new DomList(document.querySelector("#block-list"));
|
||||||
|
grid.clear();
|
||||||
|
|
||||||
|
atlasMetadata.forEach((m, i) => {
|
||||||
|
const item = grid.makeItem();
|
||||||
|
item.dataset.index = i.toString(10);
|
||||||
|
if (!filter.test(m.modules["plannerator:display_name"].display_name)) {
|
||||||
|
item.classList.add("hidden");
|
||||||
|
}
|
||||||
|
item.appendChild(makeAtlasImage(i));
|
||||||
|
item.addEventListener("click", listClickEvent);
|
||||||
|
grid.addItem(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPlanner() {
|
||||||
|
document.addEventListener("keydown", e => {
|
||||||
|
console.log(e);
|
||||||
|
if (e.key === "H") {
|
||||||
|
suppressHover = !suppressHover;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = new DomList(document.querySelector("#planner-list"));
|
||||||
|
list.clear();
|
||||||
|
|
||||||
|
for (let z = 0; z < multiblockSize[2]; z++) {
|
||||||
|
const item = list.makeItem();
|
||||||
|
item.style.setProperty("--width", multiblockSize[1]);
|
||||||
|
const grid = new DomList(item);
|
||||||
|
grid.clear();
|
||||||
|
for (let y = 0; y < multiblockSize[1]; y++) {
|
||||||
|
for (let x = 0; x < multiblockSize[0]; x++) {
|
||||||
|
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 = (-1).toString(10);
|
||||||
|
cell.appendChild(makeAtlasImage(-1));
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { recall, save } from "./storage.js";
|
||||||
|
|
||||||
|
export function initializeMenuResizing() {
|
||||||
|
const handles = document.querySelectorAll('[data-resize]');
|
||||||
|
handles.forEach(handle => {
|
||||||
|
handle.addEventListener("mousedown", startResize);
|
||||||
|
const stored = recall(`${handle.dataset.resize}-size`);
|
||||||
|
if (stored) {
|
||||||
|
document.body.style.setProperty(`--${handle.dataset.resize}-size`, stored);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startResize(e) {
|
||||||
|
window.addEventListener("mouseup", endResize);
|
||||||
|
window.addEventListener("mousemove", resize);
|
||||||
|
document.body.dataset.resizing = e.target.dataset.resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resize(e) {
|
||||||
|
const resizing = document.body.dataset.resizing;
|
||||||
|
let size = 0.25;
|
||||||
|
switch (resizing) {
|
||||||
|
case "left-menu":
|
||||||
|
size = e.pageX / window.innerWidth;
|
||||||
|
break;
|
||||||
|
case "right-menu":
|
||||||
|
size = 1 - e.pageX / window.innerWidth;
|
||||||
|
break;
|
||||||
|
case "bottom-menu":
|
||||||
|
size = 1 - e.pageY / window.innerHeight;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
size = Math.max(0.05, Math.min(size, 0.5));
|
||||||
|
document.body.style.setProperty(`--${resizing}-size`, `${size*100}%`);
|
||||||
|
save(`${resizing}-size`, `${size*100}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function endResize() {
|
||||||
|
window.removeEventListener("mouseup", endResize);
|
||||||
|
window.removeEventListener("mousemove", resize);
|
||||||
|
delete document.body.dataset.resizing;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
export function save(key, value) {
|
||||||
|
localStorage.setItem(key, JSON.stringify(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function recall(key, defaultValue = null) {
|
||||||
|
const item = localStorage.getItem(key);
|
||||||
|
if (item) {
|
||||||
|
return JSON.parse(item);
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
124
style.css
124
style.css
|
|
@ -1,6 +1,22 @@
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-family: sans-serif;
|
||||||
|
max-height: 100svh;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--scale-factor: 2;
|
--scale-factor: 2;
|
||||||
--default-size: 16px;
|
--default-size: 16px;
|
||||||
|
|
||||||
|
--dark-bg: #990099;
|
||||||
|
--menu-border-color: #666;
|
||||||
|
--resize-handle-color: #666;
|
||||||
|
--resize-handle-width: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-state].hidden {
|
[data-state].hidden {
|
||||||
|
|
@ -11,6 +27,9 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
.list .hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(var(--width), calc(var(--scale-factor) * var(--default-size)));
|
grid-template-columns: repeat(var(--width), calc(var(--scale-factor) * var(--default-size)));
|
||||||
|
|
@ -25,34 +44,107 @@
|
||||||
|
|
||||||
body {
|
body {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: var(--left-nav, 400px) 1fr var(--right-nav, 300px);
|
grid-template-columns: var(--left-menu-size, 15%) 1fr var(--right-menu-size, 10%);
|
||||||
grid-template-rows: 40px 1fr var(--bottom-nav, 200px);
|
grid-template-rows: 28px 1fr var(--bottom-menu-size, 15%);
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
#top-menu {
|
#top-menu {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
|
|
||||||
|
background-color: var(--dark-bg);
|
||||||
}
|
}
|
||||||
#left-menu {
|
#left-menu {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
|
border-right: 1px solid var(--menu-border-color)
|
||||||
}
|
}
|
||||||
main {
|
main {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
#right-menu {
|
#right-menu {
|
||||||
grid-column: 3;
|
grid-column: 3;
|
||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
|
border-left: 1px solid var(--menu-border-color)
|
||||||
}
|
}
|
||||||
#bottom-menu {
|
#bottom-menu {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
grid-row: 3;
|
grid-row: 3;
|
||||||
|
border-top: 1px solid var(--menu-border-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
#left-menu, #right-menu, #bottom-menu {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
[data-resize] {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
#left-menu [data-resize], #right-menu [data-resize] {
|
||||||
|
border-left: 1px solid var(--resize-handle-color);
|
||||||
|
border-right: 1px solid var(--resize-handle-color);
|
||||||
|
margin: auto;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: calc(var(--resize-handle-width) * 2 - 1px);
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
#left-menu [data-resize] {
|
||||||
|
cursor: w-resize;
|
||||||
|
right: calc(var(--resize-handle-width) * -1);
|
||||||
|
}
|
||||||
|
#right-menu [data-resize] {
|
||||||
|
cursor: e-resize;
|
||||||
|
left: calc(var(--resize-handle-width) * -1);
|
||||||
|
}
|
||||||
|
#bottom-menu [data-resize] {
|
||||||
|
border-top: 1px solid var(--resize-handle-color);
|
||||||
|
border-bottom: 1px solid var(--resize-handle-color);
|
||||||
|
cursor: s-resize;
|
||||||
|
margin: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: calc(var(--resize-handle-width) * -1);
|
||||||
|
width: 1em;
|
||||||
|
height: calc(var(--resize-handle-width) * 2 - 1px);
|
||||||
|
}
|
||||||
|
body[data-resizing] {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
body[data-resizing="left-menu"] {
|
||||||
|
cursor: w-resize;
|
||||||
|
}
|
||||||
|
body[data-resizing="right-menu"] {
|
||||||
|
cursor: e-resize;
|
||||||
|
}
|
||||||
|
body[data-resizing="bottom-menu"] {
|
||||||
|
cursor: s-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.atlas-texture {
|
.atlas-texture {
|
||||||
image-rendering: -moz-crisp-edges; /* Firefox fallback */
|
image-rendering: -moz-crisp-edges; /* Firefox fallback */
|
||||||
image-rendering: pixelated; /* Modern browsers */
|
image-rendering: pixelated; /* Modern browsers */
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
|
||||||
background-size: calc(var(--scale-factor) * var(--atlas-resolution))
|
background-size: calc(var(--scale-factor) * var(--atlas-resolution))
|
||||||
calc(var(--scale-factor) * var(--atlas-resolution));
|
calc(var(--scale-factor) * var(--atlas-resolution));
|
||||||
|
|
||||||
|
|
@ -61,15 +153,26 @@ main {
|
||||||
|
|
||||||
background-position-x: calc(var(--scale-factor) * var(--raw-x) * -1);
|
background-position-x: calc(var(--scale-factor) * var(--raw-x) * -1);
|
||||||
background-position-y: calc(var(--scale-factor) * var(--raw-y) * -1);
|
background-position-y: calc(var(--scale-factor) * var(--raw-y) * -1);
|
||||||
|
|
||||||
|
opacity: var(--atlas-opacity, 1);
|
||||||
}
|
}
|
||||||
.atlas-air {
|
.atlas-air {
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
[data-preview], [data-preview-remove] {
|
||||||
|
--atlas-opacity: var(--atlas-preview-opacity, 0.5);
|
||||||
|
}
|
||||||
[data-highlight] {
|
[data-highlight] {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
@ -77,29 +180,32 @@ main {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
[data-highlight-distance="1"] {
|
||||||
|
--highlight-color: var(--highlight-near-color, #4848);
|
||||||
|
}
|
||||||
[data-highlight='x'] .atlas-texture:after, [data-highlight='x'] .atlas-air:after {
|
[data-highlight='x'] .atlas-texture:after, [data-highlight='x'] .atlas-air:after {
|
||||||
inset:
|
inset:
|
||||||
calc(var(--scale-factor) * var(--size-y, var(--default-size)) * 0.4)
|
calc(var(--scale-factor) * var(--size-y, var(--default-size)) * 0.4)
|
||||||
0
|
0
|
||||||
calc(var(--scale-factor) * var(--size-y, var(--default-size)) * 0.4)
|
calc(var(--scale-factor) * var(--size-y, var(--default-size)) * 0.4)
|
||||||
0;
|
0;
|
||||||
background-color: var(--higlight-color, #f808);
|
background-color: var(--highlight-color, #f808);
|
||||||
}
|
}
|
||||||
[data-highlight='y'] .atlas-texture:after, [data-highlight='y'] .atlas-air:after {
|
[data-highlight='y'] .atlas-texture:after, [data-highlight='y'] .atlas-air:after {
|
||||||
inset:
|
inset:
|
||||||
0
|
0
|
||||||
calc(var(--scale-factor) * var(--size-y, var(--default-size)) * 0.4)
|
calc(var(--scale-factor) * var(--size-x, var(--default-size)) * 0.4)
|
||||||
0
|
0
|
||||||
calc(var(--scale-factor) * var(--size-y, var(--default-size)) * 0.4);
|
calc(var(--scale-factor) * var(--size-x, var(--default-size)) * 0.4);
|
||||||
background-color: var(--higlight-color, #f808);
|
background-color: var(--highlight-color, #f808);
|
||||||
}
|
}
|
||||||
[data-highlight='z'] .atlas-texture:after, [data-highlight='z'] .atlas-air:after {
|
[data-highlight='z'] .atlas-texture:after, [data-highlight='z'] .atlas-air:after {
|
||||||
inset:
|
inset:
|
||||||
calc(var(--scale-factor) * var(--size-y, var(--default-size)) * 0.4)
|
calc(var(--scale-factor) * var(--size-y, var(--default-size)) * 0.4)
|
||||||
|
calc(var(--scale-factor) * var(--size-x, var(--default-size)) * 0.4)
|
||||||
calc(var(--scale-factor) * var(--size-y, var(--default-size)) * 0.4)
|
calc(var(--scale-factor) * var(--size-y, var(--default-size)) * 0.4)
|
||||||
calc(var(--scale-factor) * var(--size-y, var(--default-size)) * 0.4)
|
calc(var(--scale-factor) * var(--size-x, var(--default-size)) * 0.4);
|
||||||
calc(var(--scale-factor) * var(--size-y, var(--default-size)) * 0.4);
|
background-color: var(--highlight-color, #f808);
|
||||||
background-color: var(--higlight-color, #f808);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#block-list [data-active] {
|
#block-list [data-active] {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue