浏览代码

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 年前
父节点
当前提交
29a1f62c4d
共有 3 个文件被更改,包括 307 次插入174 次删除
  1. +59
    -1
      macrovision.css
  2. +26
    -15
      macrovision.html
  3. +222
    -158
      macrovision.js

+ 59
- 1
macrovision.css 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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;


正在加载...
取消
保存