Browse Source

Move filters into their own menu. Add an info menu

This allows for multiple filters to be applied simultaneously. This commit
also moves some misc stuff, like the help and donate buttons, into a new
info menu. This also fixes touches outside of a popout menu not causing
the menu to close.
master
Fen Dweller 3 years ago
parent
commit
29a1f62c4d
3 changed files with 307 additions and 174 deletions
  1. +59
    -1
      macrovision.css
  2. +26
    -15
      macrovision.html
  3. +222
    -158
      macrovision.js

+ 59
- 1
macrovision.css View File

@@ -230,6 +230,9 @@ body.show-extra-options .options-block.options-block-optional {
width: 100%; width: 100%;
text-align: center; text-align: center;
margin-top: 10px; margin-top: 10px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
} }


.options-banner-button { .options-banner-button {
@@ -246,7 +249,6 @@ body.show-extra-options .options-block.options-block-optional {
border-color: #666; border-color: #666;
border-width: 3pt; border-width: 3pt;
border-style: outset; border-style: outset;
min-width: 85%;
max-width: 100%; max-width: 100%;
} }


@@ -1038,4 +1040,60 @@ body.screenshot-mode .scroll-button {
#ground { #ground {
--ground-color: #000; --ground-color: #000;
background-color: var(--ground-color); background-color: var(--ground-color);
}

.filter-holder {
user-select: none;
-webkit-user-select: none;
}

.filter-holder > select {
font-size: 200%;
width: 100%;
}

.filter-holder > div {
font-size: 200%;
flex-basis: 150pt;
}

.filter-holder {
display: flex;
align-items: center;
justify-content: left space-between;
padding: 10px 20px 10px 10px;
background: gray;
border-color: darkslategray;
border-width: 5px;
border-style: solid;
}

.filter-holder.enabled {
background: green;
border-color: darkgreen;
}

.info-holder > div,
.info-holder > i {
font-size: 200%;
margin: 10px;
}

.info-holder {
display: flex;
align-items: center;
justify-content: left;
padding: 10px 20px 10px 10px;
background: gray;
border-color: darkslategray;
border-width: 5px;
border-style: solid;
text-decoration: none;
user-select: none;
-webkit-user-select: none;
color: white;
}

.info-holder:hover {
background-color: lightgray;
} }

+ 26
- 15
macrovision.html View File

@@ -57,6 +57,23 @@
</div> </div>
<div class="popout-menu" id="settings-menu"> <div class="popout-menu" id="settings-menu">
</div>
<div class="popout-menu" id="filters-menu">
</div>
<div class="popout-menu" id="info-menu">
<a class="info-holder" target="_blank" href="https://www.notion.so/Macrovision-5c7f9377424743358ddf6db5671f439e">
<i class="fas fa-question-circle"></i>
<div>Help</div>
</a>
<a class="info-holder" target="_blank" href="https://docs.google.com/forms/d/e/1FAIpQLSdCiZ2_GVVdXV0bqty4rymbCCwaFq-PLdwmK1vUIakPjv7f2g/viewform">
<i class="fas fa-paper-plane"></i>
<div>Submit Your Character</div>
</a>
<a class="info-holder" target="_blank" href="https://ko-fi.com/P5P5ACDA">
<i class="fas fa-donate"></i>
<div>Donate</div>
</a>
</div> </div>
<div id="menubar"> <div id="menubar">
<span class="menubar-group"> <span class="menubar-group">
@@ -66,6 +83,12 @@
<button id="toggle-settings"> <button id="toggle-settings">
<i class="fas fa-cogs"></i> <i class="fas fa-cogs"></i>
</button> </button>
<button id="toggle-filters">
<i class="fas fa-filter"></i>
</button>
<button id="toggle-info">
<i class="fas fa-info-circle"></i>
</button>
</span> </span>
<span class="menubar-group"> <span class="menubar-group">
<button id="copy-screenshot"> <button id="copy-screenshot">
@@ -80,27 +103,15 @@
</span> </span>
<span class="menubar-group" id="spawners"> <span class="menubar-group" id="spawners">


</span>
<span class="menubar-group" id="filters">
</span>
<span class="menubar-group" id="search">
<input id="search-box" type="text" placeholder="Search...">
<span class="menubar-group" id="search">
<input id="search-box" type="text" placeholder="Search...">
</span>
</span> </span>
<span class="menubar-group"> <span class="menubar-group">
<button id="open-help">
<i class="far fa-question-circle"></i>
<span class="sr-only">Help</span>
</button>
</span> </span>
</div> </div>
<div id="main-area"> <div id="main-area">
<div id="options" class=""> <div id="options" class="">
<div class="options-banner-buttons">
<a href='https://ko-fi.com/P5P5ACDA' target='_blank'><img style='border:0px;height:36px;transform: translateZ(0);' src='https://cdn.ko-fi.com/cdn/kofi5.png?v=2' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
<a target="_blank" href="https://docs.google.com/forms/d/e/1FAIpQLSdCiZ2_GVVdXV0bqty4rymbCCwaFq-PLdwmK1vUIakPjv7f2g/viewform"
class="options-banner-button">Submit Your Character</a>
</div>
<h3 class="options-header">World Info</h3> <h3 class="options-header">World Info</h3>
<div id="options-world"> <div id="options-world">
<div class="options-label"> <div class="options-label">


+ 222
- 158
macrovision.js View File

@@ -1452,7 +1452,7 @@ function drawHorizontalScale(ifDirty = false) {
// calling the constructor -- e.g. making a list of authors and // calling the constructor -- e.g. making a list of authors and
// owners. So, this function is used to generate that information. // owners. So, this function is used to generate that information.
// It is invoked like makeEntity so that it can be dropped in easily, // It is invoked like makeEntity so that it can be dropped in easily,
// but returns an object that lets you construct many copies of an entity,
// but returns an object that lets you construct many copies of an entity,
// rather than creating a new entity. // rather than creating a new entity.
function createEntityMaker(info, views, sizes, forms) { function createEntityMaker(info, views, sizes, forms) {
const maker = {}; const maker = {};
@@ -3679,15 +3679,6 @@ document.addEventListener("DOMContentLoaded", () => {
prepareMenu(); prepareMenu();
prepareEntities(); prepareEntities();


document.querySelector("#open-help").addEventListener("click", (e) => {
setHelpDate();
document.querySelector("#open-help").classList.remove("highlighted");
window.open(
"https://www.notion.so/Macrovision-5c7f9377424743358ddf6db5671f439e",
"_blank"
);
});

document document
.querySelector("#copy-screenshot") .querySelector("#copy-screenshot")
.addEventListener("click", (e) => { .addEventListener("click", (e) => {
@@ -3734,10 +3725,18 @@ document.addEventListener("DOMContentLoaded", () => {
e.stopPropagation(); e.stopPropagation();
}); });


document.querySelector("#sidebar-menu").addEventListener("touchstart", (e) => {
e.stopPropagation();
});

document.addEventListener("click", (e) => { document.addEventListener("click", (e) => {
document.querySelector("#sidebar-menu").classList.remove("visible"); document.querySelector("#sidebar-menu").classList.remove("visible");
}); });


document.addEventListener("touchstart", (e) => {
document.querySelector("#sidebar-menu").classList.remove("visible");
});

document document
.querySelector("#toggle-settings") .querySelector("#toggle-settings")
.addEventListener("click", (e) => { .addEventListener("click", (e) => {
@@ -3767,10 +3766,96 @@ document.addEventListener("DOMContentLoaded", () => {
e.stopPropagation(); e.stopPropagation();
}); });


document.querySelector("#settings-menu").addEventListener("touchstart", (e) => {
e.stopPropagation();
});

document.addEventListener("click", (e) => { document.addEventListener("click", (e) => {
document.querySelector("#settings-menu").classList.remove("visible"); document.querySelector("#settings-menu").classList.remove("visible");
}); });


document.addEventListener("touchstart", (e) => {
document.querySelector("#settings-menu").classList.remove("visible");
});

document.querySelector("#toggle-filters").addEventListener("click", (e) => {
const popoutMenu = document.querySelector("#filters-menu");
if (popoutMenu.classList.contains("visible")) {
popoutMenu.classList.remove("visible");
} else {
document
.querySelectorAll(".popout-menu")
.forEach((menu) => menu.classList.remove("visible"));
const rect = e.target.getBoundingClientRect();
popoutMenu.classList.add("visible");
popoutMenu.style.left = rect.x + rect.width + 10 + "px";
popoutMenu.style.top = rect.y + rect.height + 10 + "px";

let menuWidth = popoutMenu.getBoundingClientRect().width;
let screenWidth = window.innerWidth;

if (menuWidth * 1.5 > screenWidth) {
popoutMenu.style.left = 25 + "px";
}
}
e.stopPropagation();
});

document.querySelector("#filters-menu").addEventListener("click", (e) => {
e.stopPropagation();
});

document.querySelector("#filters-menu").addEventListener("touchstart", (e) => {
e.stopPropagation();
});

document.addEventListener("click", (e) => {
document.querySelector("#filters-menu").classList.remove("visible");
});

document.addEventListener("touchstart", (e) => {
document.querySelector("#filters-menu").classList.remove("visible");
});

document.querySelector("#toggle-info").addEventListener("click", (e) => {
const popoutMenu = document.querySelector("#info-menu");
if (popoutMenu.classList.contains("visible")) {
popoutMenu.classList.remove("visible");
} else {
document
.querySelectorAll(".popout-menu")
.forEach((menu) => menu.classList.remove("visible"));
const rect = e.target.getBoundingClientRect();
popoutMenu.classList.add("visible");
popoutMenu.style.left = rect.x + rect.width + 10 + "px";
popoutMenu.style.top = rect.y + rect.height + 10 + "px";

let menuWidth = popoutMenu.getBoundingClientRect().width;
let screenWidth = window.innerWidth;

if (menuWidth * 1.5 > screenWidth) {
popoutMenu.style.left = 25 + "px";
}
}
e.stopPropagation();
});

document.querySelector("#info-menu").addEventListener("click", (e) => {
e.stopPropagation();
});

document.querySelector("#info-menu").addEventListener("touchstart", (e) => {
e.stopPropagation();
});

document.addEventListener("click", (e) => {
document.querySelector("#info-menu").classList.remove("visible");
});

document.addEventListener("touchstart", (e) => {
document.querySelector("#info-menu").classList.remove("visible");
});

window.addEventListener("unload", () => { window.addEventListener("unload", () => {
saveScene("autosave"); saveScene("autosave");
setUserSettings(exportUserSettings()); setUserSettings(exportUserSettings());
@@ -4845,6 +4930,8 @@ document.addEventListener("DOMContentLoaded", () => {
); );
} }
}; };

updateFilter();
}); });


let searchText = ""; let searchText = "";
@@ -4898,13 +4985,6 @@ function makeCustomEntity(url, x = 0.5, y = 0.5) {
} }


const filterDefs = { const filterDefs = {
none: {
id: "none",
name: "No Filter",
extract: (maker) => [],
render: (name) => name,
sort: (tag1, tag2) => tag1[1].localeCompare(tag2[1]),
},
author: { author: {
id: "author", id: "author",
name: "Authors", name: "Authors",
@@ -5027,6 +5107,8 @@ const filterDefs = {
}, },
}; };


const filterStates = {};

const sizeCategories = { const sizeCategories = {
atomic: math.unit(100, "angstroms"), atomic: math.unit(100, "angstroms"),
microscopic: math.unit(100, "micrometers"), microscopic: math.unit(100, "micrometers"),
@@ -5079,20 +5161,18 @@ function prepareEntities() {
return x.name.localeCompare(y.name); return x.name.localeCompare(y.name);
}); });
const holder = document.querySelector("#spawners"); const holder = document.querySelector("#spawners");
const filterHolder = document.querySelector("#filters");
const filterMenu = document.querySelector("#filters-menu");


const categorySelect = document.createElement("select"); const categorySelect = document.createElement("select");
categorySelect.id = "category-picker"; categorySelect.id = "category-picker";
const filterSelect = document.createElement("select");
filterSelect.id = "filter-picker";


holder.appendChild(categorySelect); holder.appendChild(categorySelect);
filterHolder.appendChild(filterSelect);


const filterSets = {}; const filterSets = {};


Object.values(filterDefs).forEach((filter) => { Object.values(filterDefs).forEach((filter) => {
filterSets[filter.id] = new Set(); filterSets[filter.id] = new Set();
filterStates[filter.id] = false;
}); });


Object.entries(availableEntities).forEach(([category, entityList]) => { Object.entries(availableEntities).forEach(([category, entityList]) => {
@@ -5183,76 +5263,36 @@ function prepareEntities() {
}); });


Object.values(filterDefs).forEach((filter) => { Object.values(filterDefs).forEach((filter) => {
const option = document.createElement("option");
option.innerText = filter.name;
option.value = filter.id;
filterSelect.appendChild(option);
const filterHolder = document.createElement("label");
filterHolder.setAttribute("for", "filter-toggle-" + filter.id);
filterHolder.classList.add("filter-holder");

const filterToggle = document.createElement("input");
filterToggle.type = "checkbox";
filterToggle.id = "filter-toggle-" + filter.id;
filterHolder.appendChild(filterToggle);

filterToggle.addEventListener("input", e => {
filterStates[filter.id] = filterToggle.checked
if (filterToggle.checked) {
filterHolder.classList.add("enabled");
} else {
filterHolder.classList.remove("enabled");
}
clearFilter();
updateFilter();
});

const filterLabel = document.createElement("div");
filterLabel.innerText = filter.name;
filterHolder.appendChild(filterLabel);


const filterNameSelect = document.createElement("select"); const filterNameSelect = document.createElement("select");
filterNameSelect.classList.add("filter-select"); filterNameSelect.classList.add("filter-select");
filterNameSelect.id = "filter-" + filter.id; filterNameSelect.id = "filter-" + filter.id;
filterHolder.appendChild(filterNameSelect); filterHolder.appendChild(filterNameSelect);


const button = document.createElement("button");
button.classList.add("filter-button");
button.id = "create-filtered-" + filter.id + "-button";
filterHolder.appendChild(button);

const counter = document.createElement("div");
counter.classList.add("button-counter");
counter.innerText = "10";
button.appendChild(counter);
const i = document.createElement("i");
i.classList.add("fas");
i.classList.add("fa-plus");
button.appendChild(i);

button.addEventListener("click", (e) => {
const makers = Array.from(
document.querySelector(".entity-select.category-visible")
).filter((element) => !element.classList.contains("filtered"));
const count = makers.length + 2;
let index = 1;

if (makers.length > 50) {
if (
!confirm(
"Really spawn " + makers.length + " things at once?"
)
) {
return;
}
}

const worldWidth =
(config.height.toNumber("meters") / canvasHeight) * canvasWidth;

const spawned = makers.map((element) => {
const category =
document.querySelector("#category-picker").value;
const maker = availableEntities[category][element.value];
const entity = maker.constructor();
displayEntity(
entity,
entity.view,
-worldWidth * 0.45 +
config.x +
(worldWidth * 0.9 * index) / (count - 1),
config.y
);
index += 1;
return entityIndex - 1;
});
updateSizes(true);

if (config.autoFitAdd) {
let targets = {};
spawned.forEach((key) => {
targets[key] = entities[key];
});
fitEntities(targets);
}
});
filterMenu.appendChild(filterHolder);


Array.from(filterSets[filter.id]) Array.from(filterSets[filter.id])
.map((name) => [name, filter.render(name)]) .map((name) => [name, filter.render(name)])
@@ -5269,6 +5309,14 @@ function prepareEntities() {
}); });
}); });


const spawnButton = document.createElement("button");
spawnButton.id = "spawn-all"
spawnButton.addEventListener("click", e => {
spawnAll();
});

filterMenu.appendChild(spawnButton);

console.log( console.log(
"Loaded " + Object.keys(availableEntitiesByName).length + " entities" "Loaded " + Object.keys(availableEntitiesByName).length + " entities"
); );
@@ -5298,20 +5346,49 @@ function prepareEntities() {


recomputeFilters(); recomputeFilters();


filterSelect.addEventListener("input", (e) => {
const oldSelect = document.querySelector(
".filter-select.category-visible"
);
if (oldSelect) oldSelect.classList.remove("category-visible");
ratioInfo = document.body.querySelector(".extra-info");
}


const newSelect = document.querySelector("#filter-" + e.target.value);
if (newSelect && e.target.value != "none")
newSelect.classList.add("category-visible");
function spawnAll() {
const makers = Array.from(
document.querySelector(".entity-select.category-visible")
).filter((element) => !element.classList.contains("filtered"));
const count = makers.length + 2;
let index = 1;


updateFilter();
if (makers.length > 50) {
if (!confirm("Really spawn " + makers.length + " things at once?")) {
return;
}
}

const worldWidth =
(config.height.toNumber("meters") / canvasHeight) * canvasWidth;

const spawned = makers.map((element) => {
const category = document.querySelector("#category-picker").value;
const maker = availableEntities[category][element.value];
const entity = maker.constructor();
displayEntity(
entity,
entity.view,
-worldWidth * 0.45 +
config.x +
(worldWidth * 0.9 * index) / (count - 1),
config.y
);
index += 1;
return entityIndex - 1;
}); });
updateSizes(true);


ratioInfo = document.body.querySelector(".extra-info");
if (config.autoFitAdd) {
let targets = {};
spawned.forEach((key) => {
targets[key] = entities[key];
});
fitEntities(targets);
}
} }


// Only display authors and owners if they appear // Only display authors and owners if they appear
@@ -5325,75 +5402,64 @@ function recomputeFilters() {
filterSets[filter.id] = new Set(); filterSets[filter.id] = new Set();
}); });


document
.querySelectorAll(".entity-select.category-visible > option")
.forEach((element) => {
const entity = availableEntities[category][element.value];

Object.values(filterDefs).forEach((filter) => {
filter.extract(entity).forEach((result) => {
filterSets[filter.id].add(result);
});
availableEntities[category].forEach((entity) => {
Object.values(filterDefs).forEach((filter) => {
filter.extract(entity).forEach((result) => {
filterSets[filter.id].add(result);
}); });
}); });
});


Object.values(filterDefs).forEach((filter) => { Object.values(filterDefs).forEach((filter) => {
filterStates[filter.id] = false;
document.querySelector("#filter-toggle-" + filter.id).checked = false
document.querySelector("#filter-toggle-" + filter.id).dispatchEvent(new Event("click"))
// always show the "none" option // always show the "none" option
let found = filter.id == "none"; let found = filter.id == "none";
const filterSelect = document.querySelector("#filter-" + filter.id);
const filterSelectHolder = filterSelect.parentElement;
filterSelect.querySelectorAll("option").forEach((element) => {
if (
filterSets[filter.id].has(element.value) ||
filter.id == "none"
) {
element.classList.remove("filtered");
element.disabled = false;
found = true;
} else {
element.classList.add("filtered");
element.disabled = true;
}
});


document
.querySelectorAll("#filter-" + filter.id + " > option")
.forEach((element) => {
if (
filterSets[filter.id].has(element.value) ||
filter.id == "none"
) {
element.classList.remove("filtered");
element.disabled = false;
found = true;
} else {
element.classList.add("filtered");
element.disabled = true;
}
});

const filterOption = document.querySelector(
"#filter-picker > option[value='" + filter.id + "']"
);
if (found) { if (found) {
filterOption.classList.remove("filtered");
filterOption.disabled = false;
filterSelectHolder.style.display = "";
} else { } else {
filterOption.classList.add("filtered");
filterOption.disabled = true;
filterSelectHolder.style.display = "none";
} }
}); });

document.querySelector("#filter-picker").value = "none";
document.querySelector("#filter-picker").dispatchEvent(new Event("input"));
} }


function updateFilter() { function updateFilter() {
const category = document.querySelector("#category-picker").value; const category = document.querySelector("#category-picker").value;
const type = document.querySelector("#filter-picker").value;
const filterKeySelect = document.querySelector(
".filter-select.category-visible"
);


clearFilter();
const types = Object.values(filterDefs).filter(def => filterStates[def.id]).map(def => def.id)

const keys = {


const noFilter = !filterKeySelect;
}

types.forEach(type => {
const filterKeySelect = document.querySelector("#filter-" + type);
keys[type] = filterKeySelect.value;
})

clearFilter();


let key;
let current = document.querySelector( let current = document.querySelector(
".entity-select.category-visible" ".entity-select.category-visible"
).value; ).value;


if (!noFilter) {
key = filterKeySelect.value;
current;
}

let replace = current == ""; let replace = current == "";
let first = null; let first = null;


@@ -5403,16 +5469,18 @@ function updateFilter() {
document document
.querySelectorAll(".entity-select.category-visible > option") .querySelectorAll(".entity-select.category-visible > option")
.forEach((element) => { .forEach((element) => {
let keep = noFilter;
let keep = true


if (
!noFilter &&
filterDefs[type]
.extract(availableEntities[category][element.value])
.indexOf(key) >= 0
) {
keep = true;
}
types.forEach(type => {
if (
!(filterDefs[type]
.extract(availableEntities[category][element.value])
.indexOf(keys[type]) >= 0)
) {
keep = false;
}
})


if ( if (
searchText != "" && searchText != "" &&
@@ -5438,13 +5506,9 @@ function updateFilter() {
} }
}); });


const button = document.querySelector(
".filter-select.category-visible + button"
);

if (button) {
button.querySelector(".button-counter").innerText = count;
}
const button = document.querySelector("#spawn-all")
button.innerText = "Spawn " + count + " filtered " + (count == 1 ? "entity" : "entities") + ".";


if (replace) { if (replace) {
document.querySelector(".entity-select.category-visible").value = first; document.querySelector(".entity-select.category-visible").value = first;


Loading…
Cancel
Save