This commit is contained in:
MeowcaTheoRange 2024-06-09 07:23:14 -05:00
parent 2850588b58
commit 91235defd8
10 changed files with 736 additions and 57 deletions

View file

@ -5,6 +5,19 @@ export class EventManager {
constructor() {}
async getAllEvents() {
// DO NOT use this function for general use - may overload the server(s). get ONCE and then cache!
const currentLocalization = this.mainmanager.getCurrentLangCode();
let allEventsReqSend = fetch(`/data/${currentLocalization}/events/`);
let allEventsReq = await allEventsReqSend;
let allEvents;
if (allEventsReq.ok) allEvents = await allEventsReq.json();
return allEvents;
}
async getEvents() {
const currentLocalization = this.mainmanager.getCurrentLangCode();
const currentFloor = this.mainmanager.getCurrentFocus("floor");

View file

@ -12,6 +12,8 @@ export class GameObjManager {
font: "monospace",
fontSize: 24,
maxFontWidth: 16,
floorOpacity: 0.75,
roomOpacity: 0.75,
};
mainmanager;
@ -38,8 +40,8 @@ export class GameObjManager {
this.floorObject = this.map.kp.make([
this.map.kp.polygon(polygon.pts),
this.map.kp.outline(1, this.map.kp.BLACK),
this.map.kp.color(this.map.kp.Color.fromHex("303030")),
this.map.kp.opacity(this.opts.floorOpacity),
this.map.kp.pos(),
this.map.kp.z(5),
]);
@ -48,13 +50,19 @@ export class GameObjManager {
this.map.camBounds = bounds;
this.floorObject.onUpdate(() => {
const camScale = 1 / this.map.kp.camScale().y;
this.floorObject.outline.width = 8 * camScale;
});
this.floorObject.onDraw(() => {
const camScale = 1 / this.map.kp.camScale().y;
this.map.kp.drawPolygon({
pts: polygon.pts,
pos: this.map.kp.vec2(0),
fill: false,
outline: {
color: this.map.kp.Color.fromHex(this.opts.borderColor),
width: 8 * camScale,
},
});
this.map.kp.drawText({
text: currentFloor.name,
size: 24 * camScale,
@ -120,7 +128,7 @@ export class GameObjManager {
const obj = this.map.kp.make([
this.map.kp.polygon(polygon.pts),
this.map.kp.color(this.map.kp.Color.fromHex(this.opts.bgColor)),
this.map.kp.opacity(0.5),
this.map.kp.opacity(this.opts.roomOpacity),
this.map.kp.area(),
this.map.kp.pos(),
this.map.kp.z(6),
@ -133,8 +141,6 @@ export class GameObjManager {
this.roomObjects.set(room.id, obj);
obj.onUpdate(() => {
const camScale = 1 / this.map.kp.camScale().y;
const roomFocused = this.mainmanager.getCurrentFocus("room") == room.id;
if (roomFocused) {

View file

@ -6,7 +6,10 @@ export default new Map([
date_starting: "Starting",
date_started: "Started",
date_summary: (prefix, time) => `${prefix} ${time}`,
list_index: (index) => `, ${index}`,
parenthesis: (content) => `(${content})`,
openParenthesis: "(",
closeParenthesis: ")",
separator: (p1, p2) => `${p1} - ${p2}`,
hours_long: (hours) =>
hours == 1 ? `${hours} hour long` : `${hours} hours long`,
@ -19,6 +22,22 @@ export default new Map([
event_inspector_header: (name) => `Event ${name}`,
event_inspector_back: "Return to events panel",
event_inspector_minimize: "Toggle event inspector docking",
zoom_in_button: "Zoom in",
zoom_out_button: "Zoom out",
menu_search_button: "Search events",
menu_language_button: "Change language",
menu_about_button: "About EventMapper",
search_dialog_close_button: "Close search",
search_dialog_search_button: "Run search",
search_dialog_header_title: "Search Events",
search_filters: {
name: ({ src }) => `Found by title`,
description: ({ src }) => `Found by description`,
url: ({ src }) => `Found by URL ${src}`,
floor: ({ src }) => `Found by floor ${src}`,
room: ({ src }) => `Found by room ${src}`,
},
regexAnyWordCharacter: /[^\w]/g,
},
],
]);

View file

@ -2,6 +2,7 @@ import { EventManager } from "./events.js";
import { FloorManager } from "./floors.js";
import { GameObjManager } from "./gameobj.js";
import { LangManager } from "./lang.js";
import { TagManager } from "./tags.js";
import { UIManager } from "./ui.js";
export class EventMapperManager {
@ -32,6 +33,9 @@ export class EventMapperManager {
this.floormanager = new FloorManager();
this.floormanager.mainmanager = this;
this.tagmanager = new TagManager();
this.tagmanager.mainmanager = this;
this.uimanager = new UIManager(mapUi);
this.uimanager.mainmanager = this;
@ -54,6 +58,14 @@ export class EventMapperManager {
this.uimanager.setLoading(true);
await this.tagmanager.getTags();
this.uimanager.__updateSearchUI();
this.uimanager.setLoading(false);
this.uimanager.setLoading(true);
window.addEventListener("hashchange", () => this.hashchange());
await this.hashchange();
@ -113,6 +125,8 @@ export class EventMapperManager {
if (!this.uimanager.getEventsEmpty())
this.uimanager.setEventsMinimized(false);
// this.uimanager.setSearchDialogOpen(false);
await this.uimanager.__updateSearch();
this.uimanager.setLoading(false);
}
@ -127,6 +141,9 @@ export class EventMapperManager {
if (!this.uimanager.getEventsEmpty())
this.uimanager.setEventsMinimized(false);
if (id != null) {
this.uimanager.setSearchDialogOpen(false);
}
this.uimanager.setEventsInspector(false);
}
@ -137,6 +154,7 @@ export class EventMapperManager {
this.gameobjmanager.zoomToRoom(this.convertIdFocus(id, "room"));
this.uimanager.updateInspector();
this.uimanager.setSearchDialogOpen(false);
}
this.uimanager.setEventsInspector(id != null);
@ -294,6 +312,8 @@ export class EventMapperManager {
return this.eventmanager[func](...args);
case "floor":
return this.floormanager[func](...args);
case "tag":
return this.tagmanager[func](...args);
case "ui":
return this.uimanager[func](...args);
case "gameobj":
@ -311,6 +331,8 @@ export class EventMapperManager {
return this.eventmanager[variable];
case "floor":
return this.floormanager[variable];
case "tag":
return this.tagmanager[variable];
case "ui":
return this.uimanager[variable];
case "gameobj":

View file

@ -0,0 +1,25 @@
export class TagManager {
mainmanager;
#tags = new Map([]);
constructor() {}
async getTags() {
const currentLocalization = this.mainmanager.getCurrentLangCode();
let allTagsReqSend = fetch(`/data/${currentLocalization}/tags`);
let allTagsReq = await allTagsReqSend;
let allTags;
if (allTagsReq.ok) allTags = await allTagsReq.json();
this.#tags.clear();
allTags.forEach((tag) => this.#tags.set(tag.id, tag));
}
get allTags() {
return this.#tags;
}
}

View file

@ -20,6 +20,10 @@ export class UIManager {
this.__initZoom();
this.__initMenu();
this.__initSearchUI();
this.__initEvents();
this.__initInspector();
@ -61,8 +65,10 @@ export class UIManager {
setEventsInspector(state) {
if (
state != this.uiElements.eventsContainer.classList.contains("inspector")
)
) {
this.setEventsMinimized(false);
this.setSearchDialogOpen(false);
}
if (state) {
this.uiElements.eventsContainer.classList.add("inspector");
} else {
@ -70,6 +76,15 @@ export class UIManager {
}
}
setSearchDialogOpen(state) {
if (state) {
this.uiElements.searchDialogScrim.classList.add("open");
this.__updateSearch();
} else {
this.uiElements.searchDialogScrim.classList.remove("open");
}
}
getEventsEmpty() {
return this.uiElements.events.classList.contains("empty");
}
@ -77,6 +92,8 @@ export class UIManager {
// Init
__initUIElements() {
this.uiElements.controls = this.ui.querySelector("#controls");
// Zoom
this.uiElements.zoomButtons = this.ui.querySelector("#zoom");
this.uiElements.zoomIn =
@ -89,6 +106,12 @@ export class UIManager {
this.uiElements.floorButtons =
this.uiElements.floors.querySelector("#floor-buttons");
// Menu Bar
this.uiElements.menuBar = this.ui.querySelector("#menu-bar");
this.uiElements.menuBarSearch = this.ui.querySelector("#menu-search");
this.uiElements.menuBarLang = this.ui.querySelector("#menu-lang");
this.uiElements.menuBarAbout = this.ui.querySelector("#menu-about");
// Events
this.uiElements.eventsContainer =
this.ui.querySelector("#events-container");
@ -126,11 +149,34 @@ export class UIManager {
this.uiElements.eventsInspectorMinimizeButton =
this.uiElements.eventsInspector.querySelector("#minimize");
// Search Dialog
this.uiElements.searchDialogScrim = this.ui.querySelector(
"#search-dialog-scrim"
);
this.uiElements.searchDialog =
this.uiElements.searchDialogScrim.querySelector("#search-dialog");
this.uiElements.searchDialogHeader =
this.uiElements.searchDialog.querySelector("#search-dialog-header");
this.uiElements.searchDialogHeaderClose =
this.uiElements.searchDialogHeader.querySelector("#close");
this.uiElements.searchDialogHeaderTitle =
this.uiElements.searchDialogHeader.querySelector("#search-dialog-title");
this.uiElements.searchDialogBar =
this.uiElements.searchDialog.querySelector("#search-dialog-bar");
this.uiElements.searchDialogBarInput =
this.uiElements.searchDialogBar.querySelector("#search-dialog-input");
this.uiElements.searchDialogTags =
this.uiElements.searchDialog.querySelector("#search-dialog-tags");
this.uiElements.searchDialogList =
this.uiElements.searchDialog.querySelector("#search-list");
// Loading
this.uiElements.loading = this.ui.querySelector("#loading");
}
__initZoom() {
const currentLocalization = this.mainmanager.getCurrentLocalization();
this.uiElements.zoomIn.addEventListener("click", () =>
this.mainmanager.callManagerFunction("map", "zoomIn")
);
@ -146,6 +192,320 @@ export class UIManager {
this.mainmanager.addManagerEventListener("map", "zoom", () => {
this.setZoomDisabled(0);
});
this.uiElements.zoomIn.title = currentLocalization.zoom_in_button;
this.uiElements.zoomOut.title = currentLocalization.zoom_out_button;
}
__initMenu() {
this.uiElements.menuBarSearch.addEventListener("click", () => {
this.setSearchDialogOpen(true);
});
this.__updateMenu();
}
__updateMenu() {
const currentLocalization = this.mainmanager.getCurrentLocalization();
this.uiElements.menuBarSearch.title =
currentLocalization.menu_search_button;
this.uiElements.menuBarLang.title =
currentLocalization.menu_language_button;
this.uiElements.menuBarAbout.title = currentLocalization.menu_about_button;
}
__initSearchUI() {
this.uiElements.searchDialogHeaderClose.addEventListener("click", () => {
this.setSearchDialogOpen(false);
});
this.uiElements.searchDialogScrim.addEventListener("click", () => {
this.setSearchDialogOpen(false);
});
this.uiElements.searchDialog.addEventListener("click", (event) => {
event.stopPropagation();
});
this.uiElements.searchDialogBarInput.addEventListener("input", () => {
this.__updateSearch();
});
}
#Search__selectedTags = [];
#Search__allEvents = null;
__updateSearchUI() {
const currentLocalization = this.mainmanager.getCurrentLocalization();
this.uiElements.searchDialogHeaderClose.title =
currentLocalization.search_dialog_close_button;
this.uiElements.searchDialogHeaderTitle.textContent =
currentLocalization.search_dialog_header_title;
const tags = this.mainmanager.getManagerVariable("tag", "allTags");
this.uiElements.searchDialogTags.replaceChildren();
tags.forEach((value, id) => {
const tagButton = document.createElement("button");
tagButton.classList.add("search-tag");
tagButton.textContent = value.name;
tagButton.title = value.description;
tagButton.id = "tag-" + id;
tagButton.addEventListener("click", () => this.__updateTagState(id));
this.uiElements.searchDialogTags.appendChild(tagButton);
});
}
__updateTagState(tag) {
const tags = this.mainmanager.getManagerVariable("tag", "allTags");
const tagPos = this.#Search__selectedTags.indexOf(tag);
if (tagPos >= 0) this.#Search__selectedTags.splice(tagPos, 1);
else this.#Search__selectedTags.push(tag);
this.#Search__selectedTags = this.#Search__selectedTags.filter(
(val) =>
val == tag || tags.get(val).radiogroup != tags.get(tag).radiogroup
);
this.__updateSearchUISoft();
this.__updateSearch();
}
__updateSearchUISoft() {
const tags = this.mainmanager.getManagerVariable("tag", "allTags");
tags.forEach((_, id) => {
const child = this.uiElements.searchDialogTags.querySelector(
"#tag-" + id
);
if (this.#Search__selectedTags.includes(id))
child.classList.add("selected");
else child.classList.remove("selected");
});
}
async __initSearch() {
// Runs on first search, as to not make the client wait too long when starting EventMapper (loading the same thing twice)
this.setLoading(true);
this.#Search__allEvents = await this.mainmanager.callManagerFunction(
"event",
"getAllEvents"
);
this.setLoading(false);
}
async __updateSearch() {
const currentLocalization = this.mainmanager.getCurrentLocalization();
if (this.#Search__allEvents == null) await this.__initSearch();
const searchTransform = (x) =>
x.toLowerCase().replace(currentLocalization.regexAnyWordCharacter, "");
const searchValue = searchTransform(
this.uiElements.searchDialogBarInput.value
);
let whatFilter = [];
let filteredEvents = this.#Search__allEvents.filter((event) => {
if (searchValue.length < 1) {
whatFilter.push(null);
return true;
}
// First, filter by text
if (searchTransform(event.name).includes(searchValue)) {
whatFilter.push({
type: "name",
src: event.name,
});
return true;
} else if (searchTransform(event.description).includes(searchValue)) {
whatFilter.push({
type: "description",
src: event.description,
});
return true;
} else if (searchTransform(event.url).includes(searchValue)) {
whatFilter.push({
type: "url",
src: event.url,
});
return true;
} else {
// Filter by room/floor
const eventFloorId = this.mainmanager.convertIdFocus(event.id, "floor");
const eventFloor = this.mainmanager
.getAllFocusObject("floor")
.get(eventFloorId);
if (eventFloor != null) {
if (searchTransform(eventFloor.name).includes(searchValue)) {
whatFilter.push({
type: "floor",
src: eventFloor.name,
});
return true;
}
} else {
if (searchTransform(eventFloorId).includes(searchValue)) {
whatFilter.push({
type: "floor",
src: eventFloorId,
});
return true;
}
}
const eventRoomId = this.mainmanager.convertIdFocus(event.id, "room");
const eventRoom = this.mainmanager
.getAllFocusObject("room")
.get(eventRoomId);
if (eventRoom != null) {
if (searchTransform(eventRoom.name).includes(searchValue)) {
whatFilter.push({
type: "room",
src: eventRoom.name,
});
return true;
}
} else {
if (searchTransform(eventRoomId).includes(searchValue)) {
whatFilter.push({
type: "room",
src: eventRoomId,
});
return true;
}
}
}
});
if (this.#Search__selectedTags.length > 0)
filteredEvents = filteredEvents.filter((event, idx) => {
return this.#Search__selectedTags.every((tag) =>
event.tags.includes(tag)
);
});
this.uiElements.searchDialogList.replaceChildren();
const currentDate = new Date();
filteredEvents.forEach((event, idx) => {
const eventFloorId = this.mainmanager.convertIdFocus(event.id, "floor");
const eventDateStart = new Date(event.when.start);
const eventDateEnd = new Date(event.when.end);
const filterReason = whatFilter[idx];
const eventStarted = currentDate > eventDateStart;
const eventEnded = currentDate > eventDateEnd;
let datePrefix;
if (eventEnded) {
return; // Don't display this event
} else if (eventStarted) {
datePrefix = currentLocalization.date_started;
} else {
datePrefix = currentLocalization.date_starting;
}
const eventListContainer = document.createElement("div");
eventListContainer.classList.add("event-list-container");
eventListContainer.id = "event-" + event.id;
const eventListContainerSummary = document.createElement("p");
if (filterReason != null) {
const eventListContainerSummaryReason = document.createElement("small");
eventListContainerSummaryReason.textContent =
currentLocalization.search_filters[filterReason.type](filterReason);
eventListContainerSummaryReason.classList.add("reason");
eventListContainerSummary.appendChild(eventListContainerSummaryReason);
eventListContainerSummary.appendChild(document.createElement("br"));
}
const eventListContainerSummaryName = document.createElement("a");
eventListContainerSummaryName.href = "#" + event.id;
eventListContainerSummaryName.textContent = event.name;
eventListContainerSummary.appendChild(eventListContainerSummaryName);
eventListContainerSummary.appendChild(document.createTextNode(" "));
if (this.mainmanager.getCurrentFocus("floor") != eventFloorId) {
const eventFloor = this.mainmanager
.getAllFocusObject("floor")
.get(eventFloorId);
const eventListContainerSummaryFloor = document.createElement("a");
eventListContainerSummaryFloor.href = "#" + eventFloorId;
eventListContainerSummaryFloor.textContent =
currentLocalization.parenthesis(eventFloor?.name ?? eventFloorId);
eventListContainerSummary.appendChild(eventListContainerSummaryFloor);
} else {
const eventRoomId = this.mainmanager.convertIdFocus(event.id, "room");
const eventRoom = this.mainmanager
.getAllFocusObject("room")
.get(eventRoomId);
const eventListContainerSummaryRoom = document.createElement("a");
eventListContainerSummaryRoom.href = "#" + eventRoomId;
eventListContainerSummaryRoom.textContent =
currentLocalization.parenthesis(eventRoom?.name ?? eventRoomId);
eventListContainerSummary.appendChild(eventListContainerSummaryRoom);
}
eventListContainerSummary.appendChild(document.createElement("br"));
const eventListContainerSummarySmall = document.createElement("small");
const eventListContainerSummaryTime = document.createElement("span");
eventListContainerSummaryTime.classList.add("clarification");
eventListContainerSummaryTime.title = eventDateStart.toLocaleString(
this.mainmanager.getCurrentLangCode(),
{
dateStyle: "short",
timeStyle: "short",
}
);
eventListContainerSummaryTime.textContent =
currentLocalization.date_summary(
datePrefix,
getRelativeTime(
eventDateStart,
currentDate,
this.mainmanager.getCurrentLangCode()
)
);
eventListContainerSummarySmall.appendChild(eventListContainerSummaryTime);
eventListContainerSummarySmall.appendChild(document.createTextNode(" "));
const eventListContainerSummaryLength = document.createElement("span");
eventListContainerSummaryLength.textContent =
currentLocalization.parenthesis(
currentLocalization.minutes_long(
getMinutesDifference(eventDateStart, eventDateEnd)
)
);
eventListContainerSummarySmall.appendChild(
eventListContainerSummaryLength
);
eventListContainerSummary.appendChild(eventListContainerSummarySmall);
eventListContainer.appendChild(eventListContainerSummary);
this.uiElements.searchDialogList.appendChild(eventListContainer);
});
}
__initInspector() {
@ -213,8 +573,7 @@ export class UIManager {
""
);
const eventWhere = document.createElement("a");
eventWhere.href = "#" + eventRoom.id;
const eventWhere = document.createElement("span");
eventWhere.textContent = eventRoom.name;
this.uiElements.eventsInspectorEventLength.appendChild(eventWhere);
@ -416,9 +775,10 @@ export class UIManager {
this.uiElements.floorButtons.replaceChildren();
// Put them back
currentFloors.forEach(({ name }, id) => {
currentFloors.forEach(({ name, shortName }, id) => {
const floorButton = document.createElement("button");
floorButton.textContent = name;
floorButton.textContent = shortName;
floorButton.title = name;
floorButton.classList.add("floor-button");
floorButton.id = "floor-" + id;
if (id === mainFocus) floorButton.classList.add("selected");
@ -430,16 +790,15 @@ export class UIManager {
});
if (currentFloors.size < 1) {
this.uiElements.floors.classList.add("empty");
this.uiElements.controls.classList.add("empty");
this.floorsEmpty = true;
} else {
this.uiElements.floors.classList.remove("empty");
this.uiElements.controls.classList.remove("empty");
this.floorsEmpty = false;
}
}
__updateFloorsSoft() {
const currentLocalization = this.mainmanager.getCurrentLocalization();
const currentFloors = this.mainmanager.getAllFocusObject("floor");
const mainFocus = this.mainmanager.getCurrentFocus("floor");

90
assets/styles/dialog.css Normal file
View file

@ -0,0 +1,90 @@
/* I fell into a burning ring of fire / Went down down down / And the flames went higher */
#search-dialog-scrim {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100%;
background-color: #0008;
opacity: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.125s;
}
#search-dialog-scrim.open {
opacity: 1;
pointer-events: all;
user-select: text;
}
#search-dialog {
box-sizing: border-box;
display: inline-flex;
gap: 8px;
padding: 8px;
border-radius: 4px;
background-color: #202020;
color: #fff;
box-shadow: 0 0 8px 0 #0008;
flex-direction: column;
width: calc(100% - 16px);
max-width: 624px;
}
#search-dialog #search-dialog-header {
display: grid;
align-items: center;
grid-template-columns: auto 36px;
gap: 8px;
}
#search-dialog #search-dialog-bar {
display: grid;
align-items: center;
grid-template-columns: auto;
gap: 8px;
}
#search-dialog #search-dialog-tags {
display: flex;
align-items: center;
overflow-x: auto;
overflow-y: hidden;
gap: 8px;
width: 100%;
padding: 6px 0;
}
#search-dialog #search-dialog-tags .search-tag {
white-space: nowrap;
height: 24px;
padding: 0 8px;
}
#search-dialog #search-dialog-tags .search-tag.selected {
border: 1px solid #fff;
}
#search-dialog #search-list {
width: 100%;
overflow-y: auto;
overflow-x: hidden;
box-sizing: border-box;
padding: 8px;
background-color: #0002;
display: flex;
flex-direction: column;
gap: 4px;
display: inline-block;
}
#search-dialog #search-list .event-list-container p {
margin: 0;
}
#search-dialog #search-list .event-list-container p .reason {
opacity: 0.5;
}

View file

@ -4,7 +4,7 @@
body, html {
margin: 0;
height: 100vh;
height: 100%;
width: 100vw;
box-sizing: border-box;
overflow: hidden;
@ -42,6 +42,7 @@ button:not(.link) {
border-radius: 4px;
height: 36px;
border: 1px solid #0008;
user-select: none;
}
button:not(.link):hover {
@ -68,6 +69,23 @@ button.link {
cursor: pointer;
}
button:not(.link):focus,
input:focus {
outline: 1px solid #808080;
}
input {
background-color: #0004;
border: none;
color: inherit;
font: inherit;
box-sizing: border-box;
padding: 6px;
border-radius: 4px;
height: 36px;
border: 1px solid #0008;
}
a {
color: currentColor;
}
@ -85,25 +103,39 @@ a {
left: 0;
top: 0;
width: 100vw;
height: 100vh;
height: 100%;
pointer-events: none;
user-select: none;
}
#map-ui #controls {
display: flex;
flex-direction: column;
gap: 8px;
position: absolute;
top: 0;
right: 0;
padding: 8px;
background-color: #fff4;
border-radius: 0 0 0 4px;
}
#map-ui #controls.empty {
right: -100%;
}
#map-ui #zoom {
pointer-events: all;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 8px;
position: absolute;
padding: 8px;
top: 16px;
left: 16px;
border-radius: 4px;
background-color: #202020;
color: #fff;
box-shadow: 0 0 8px 0 #0008;
transition: left 0.5s;
}
#map-ui #zoom .zoom-button {
@ -114,19 +146,12 @@ a {
pointer-events: all;
display: inline-block;
box-sizing: border-box;
position: absolute;
padding: 8px;
top: 16px;
right: 16px;
border-radius: 4px;
background-color: #202020;
color: #fff;
box-shadow: 0 0 8px 0 #0008;
transition: right 0.5s;
}
#map-ui #floors.empty {
right: -100%;
transition: left 0.5s;
}
#map-ui #floors #floor-buttons {
@ -145,6 +170,27 @@ a {
border: 1px solid #fff;
}
#map-ui #menu-bar {
position: absolute;
left: 8px;
top: 8px;
pointer-events: all;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px;
border-radius: 4px;
background-color: #202020;
color: #fff;
box-shadow: 0 0 8px 0 #0008;
transition: left 0.5s;
}
#map-ui #menu-bar .menu-button {
width: 36px;
}
#map-ui #events-container {
position: absolute;
left: 0;
@ -156,13 +202,14 @@ a {
#map-ui #events-container #events,
#map-ui #events-container #events-inspector {
pointer-events: all;
/* user-select: text; */
box-sizing: border-box;
display: inline-flex;
position: absolute;
gap: 8px;
padding: 8px;
left: 16px;
bottom: 16px;
left: 8px;
bottom: 8px;
border-radius: 4px;
translate: 0 calc(100% + 32px);
background-color: #202020;
@ -172,7 +219,7 @@ a {
max-height: calc(50vh - 16px);
flex-direction: column;
width: 400px;
max-width: calc(100% - 32px);
max-width: calc(100% - 16px);
z-index: 9;
}
@ -269,6 +316,7 @@ a {
background-color: #2228;
border-radius: 8px;
opacity: 0;
z-index: 1000;
transition: opacity 0.25s;
}

View file

@ -10,19 +10,30 @@
rel="stylesheet"
href="/assets/styles/style.css"
/>
<link
rel="stylesheet"
href="/assets/styles/dialog.css"
/>
</head>
<body>
<canvas id="map"></canvas>
<div id="map-ui" class="loading">
<div id="controls" class="empty">
<div id="zoom">
<button class="zoom-button" id="zoom-in">+</button>
<button class="zoom-button" id="zoom-out" disabled>-</button>
</div>
<div id="floors" class="empty">
<div id="floors">
<div id="floor-buttons">
</div>
</div>
</div>
<div id="menu-bar">
<button class="menu-button" id="menu-search">Sc</button>
<button class="menu-button" id="menu-lang">Ln</button>
<button class="menu-button" id="menu-about">(i)</button>
</div>
<div id="events-container" class="empty">
<div id="events">
<div id="events-header">
@ -51,6 +62,24 @@
</div>
</div>
</div>
<div id="search-dialog-scrim">
<div id="search-dialog">
<div id="search-dialog-header">
<div>
<h1 class="widget-heading" id="search-dialog-title"></h1>
</div>
<button id="close">X</button>
</div>
<div id="search-dialog-bar">
<input type="text" id="search-dialog-input" />
</div>
<div id="search-dialog-tags">
</div>
<div id="search-list">
</div>
</div>
</div>
<div id="loading">
<div id="loading-circle">

View file

@ -22,9 +22,48 @@ app.get('/data/:lang/events/:floor', async (req, res) => {
// Merge events and localization
const lang = req.params.lang;
const merged = events.map(event => {
event.name = event.lang[lang]?.name ?? "";
event.description = event.lang[lang]?.description ?? "";
event.url = event.lang[lang]?.url ?? "";
const curLang = event.lang[lang] ?? event.lang[config.default_language];
event.name = curLang.name ?? "";
event.description = curLang.description ?? "";
event.url = curLang.url ?? "";
delete event.lang;
return event;
});
return res.send(merged);
})
app.get('/data/:lang/events/', async (req, res) => {
// Get floors
let floorsReq = await fetch(new URL("floors.json", config.data_url));
let floors;
if (floorsReq.ok)
floors = await floorsReq.json();
else
return res.status(400).send("Bad Request");
let allEvents = [];
await Promise.allSettled(floors.map(async (curFloor) => {
let eventsReq = await fetch(new URL(`events/${curFloor.id}.json`, config.data_url));
let events;
if (eventsReq.ok)
events = await eventsReq.json();
else
return null;
allEvents = allEvents.concat(events);
}));
// Merge events and localization
const lang = req.params.lang;
const merged = allEvents.map(event => {
const curLang = event.lang[lang] ?? event.lang[config.default_language];
event.name = curLang.name ?? "";
event.description = curLang.description ?? "";
event.url = curLang.url ?? "";
delete event.lang;
return event;
});
@ -33,21 +72,48 @@ app.get('/data/:lang/events/:floor', async (req, res) => {
})
app.get('/data/:lang/floors', async (req, res) => {
// Get layers
let layersReq = await fetch(new URL("floors.json", config.data_url));
let layers;
if (layersReq.ok)
layers = await layersReq.json();
// Get floors
let floorsReq = await fetch(new URL("floors.json", config.data_url));
let floors;
if (floorsReq.ok)
floors = await floorsReq.json();
else
return res.status(400).send("Bad Request");
// Merge layers and localization
// Merge floors and localization
const lang = req.params.lang;
const merged = layers.map(layer => {
layer.name = layer.lang[lang]?.name ?? "";
layer.description = layer.lang[lang]?.description ?? "";
delete layer.lang;
return layer;
const merged = floors.map(floor => {
const curLang = floor.lang[lang] ?? floor.lang[config.default_language];
floor.name = curLang.name ?? "";
floor.shortName = curLang.shortName ?? "";
floor.description = curLang.description ?? "";
delete floor.lang;
return floor;
});
return res.send(merged);
})
app.get('/data/:lang/tags', async (req, res) => {
// Get tags
let tagsReq = await fetch(new URL("tags.json", config.data_url));
let tags;
if (tagsReq.ok)
tags = await tagsReq.json();
else
return res.status(400).send("Bad Request");
// Merge floors and localization
const lang = req.params.lang;
const merged = tags.map(tag => {
const curLang = tag.lang[lang] ?? tag.lang[config.default_language];
tag.name = curLang.name ?? "";
tag.shortName = curLang.shortName ?? "";
tag.description = curLang.description ?? "";
delete tag.lang;
return tag;
});
return res.send(merged);
@ -65,9 +131,11 @@ app.get('/data/:lang/rooms/:floor', async (req, res) => {
// Merge rooms and localization
const lang = req.params.lang;
const merged = rooms.map(room => {
room.name = room.lang[lang]?.name ?? "";
room.shortName = room.lang[lang]?.shortName ?? "";
room.description = room.lang[lang]?.description ?? "";
const curLang = room.lang[lang] ?? room.lang[config.default_language];
room.name = curLang.name ?? "";
room.shortName = curLang.shortName ?? "";
room.description = curLang.description ?? "";
delete room.lang;
return room;
});