Lots of little QOL style fixes, remove minimized event panels

This commit is contained in:
MeowcaTheoRange 2024-06-08 03:57:38 -05:00
parent 2b9dff2983
commit a6aded4e8e
9 changed files with 203 additions and 198 deletions

View file

@ -1,3 +1,74 @@
# EventMapper
TODO: Readme
EventMapper, an easily-deployable, nearly-static digital guide application made in JavaScript.
## Specifications
### Data Folder Structure
```
/events
[FLOOR].json
/rooms
[FLOOR].json
floors.json
```
### Floor
Used to represent a floor in a building. It is a parent to both its [Rooms](#room) and [Events](#events).
This object gets represented in the app as a map poly, and in a set of buttons among other floors.
```json
{
"id": "[FLOOR ID]",
"poly": [
[x, y], // An array of vertex positions.
...
],
"lang": {
"[IETF LANGUAGE TAG]": {
"name": "[SHORT SINGLE-LINE STRING]",
"description": "[SINGLE-LINE STRING]"
}
}
}
```
This will be stored in an array in `floors.json` to be accessed by clients.
### Room
Used to represent a space in which events can happen. It is a child to a [Floor](#floor), and a parent to [Events](#event).
This object gets represented in the app as a map poly.
```json
{
"id": "[FLOOR]_[ROOM ID]",
"poly": [
[x, y], // An array of vertex positions.
...
],
"lang": {
"[IETF LANGUAGE TAG]": {
"name": "[SINGLE-LINE STRING]",
"description": "[SINGLE-LINE STRING]"
}
}
}
```
This will be stored in an array in `rooms/[FLOOR].json` to be accessed by clients.
### Event
Used to represent a point in time when an activity or festivity happens. It is a child to both a [Floor](#floor) and a [Room](#room), reflected in its ID.
```json
{
"id": "[FLOOR]_[ROOM]_[EVENT ID]",
"when": {
"start": "[ISO 8601 DATE]",
"end": "[ISO 8601 DATE]"
},
"lang": {
"[IETF LANGUAGE TAG]": {
"name": "[SINGLE-LINE STRING]",
"description": "[MULTI-LINE STRING]",
"url": "[URL]"
}
}
}
```
This will be stored in an array in `events/[FLOOR].json` to be accessed by clients.

View file

@ -1,8 +1,4 @@
export class GameObjManager {
opts = {
floorMinZoom: 1,
roomMinZoom: 2,
};
map;
mainmanager;
@ -10,12 +6,8 @@ export class GameObjManager {
floorObject;
roomObjects = new Map([]);
constructor(map, opts) {
constructor(map) {
this.map = map;
this.opts = {
...this.opts,
opts,
};
}
generateFloor() {
@ -58,13 +50,32 @@ export class GameObjManager {
this.map.kp.add(this.floorObject);
}
zoomToBounds(boundingBox) {
// Get screen size
const width = this.map.kp.width();
const height = this.map.kp.height();
// Get bbox size
const bBoxWidth = boundingBox.width * 1.5;
const bBoxHeight = boundingBox.height * 1.5;
// Compare bounds
const scaledWidth = width / bBoxWidth;
const scaledHeight = height / bBoxHeight;
// Whichever one is biggest
const scale = Math.min(scaledWidth, scaledHeight);
this.map.zoomToAbs(
Math.log2(scale), // log scale
boundingBox.center()
);
}
zoomToFloor() {
const objectBounds = this.floorObject.renderArea().bbox();
this.map.zoomToAbs(
this.map.opts.minZoomLevel + this.opts.floorMinZoom,
objectBounds.center()
);
this.zoomToBounds(objectBounds);
}
zoomToRoom(id) {
@ -73,10 +84,7 @@ export class GameObjManager {
const objectBounds = selectedObject.renderArea().bbox();
this.map.zoomToAbs(
this.map.opts.minZoomLevel + this.opts.roomMinZoom,
objectBounds.center()
);
this.zoomToBounds(objectBounds);
}
generateRooms() {

View file

@ -15,12 +15,10 @@ export default new Map([
events_in: (name) => `Events (${name})`,
event_in: (location) => `In ${location}`,
view_events_in: (name) => `Return to events in ${name}`,
minimize: "Hide events panel",
maximize: "Show events panel",
minimize: "Toggle event panel docking",
event_inspector_header: (name) => `Event ${name}`,
event_inspector_back: "Return to events panel",
event_inspector_minimize: "Hide event inspector",
event_inspector_maximize: "Show event inspector",
event_inspector_minimize: "Toggle event inspector docking",
},
],
]);

View file

@ -2,7 +2,7 @@ export class KaplayMap {
kp;
opts = {
minZoomLevel: 1,
maxZoomLevel: 5,
maxZoomLevel: 6,
dblClickDuration: 0.2, // s
dblClickForgiveness: 100, // px
};

View file

@ -29,7 +29,7 @@ export class EventMapperManager {
this.uimanager = new UIManager(mapUi);
this.uimanager.mainmanager = this;
this.gameobjmanager = new GameObjManager(this.map, {});
this.gameobjmanager = new GameObjManager(this.map);
this.gameobjmanager.mainmanager = this;
}
@ -60,17 +60,24 @@ export class EventMapperManager {
let hash = location.hash;
if (hash.includes("#")) hash = hash.replace("#", "");
else return;
const floorFocus = this.convertIdFocus(hash, "floor");
const roomFocus = this.convertIdFocus(hash, "room");
const eventFocus = this.convertIdFocus(hash, "event");
this.uimanager.setLoading(true);
if (floorFocus != this.convertFocus(prevID, "floor"))
await this.selectFloor(floorFocus);
this.uimanager.setLoading(true);
this.uimanager.setLoading(false);
if (roomFocus != this.convertFocus(prevID, "room"))
await this.selectRoom(roomFocus);
this.uimanager.setLoading(true);
this.uimanager.setLoading(false);
if (eventFocus != this.convertFocus(prevID, "event"))
await this.selectEvent(eventFocus);
this.uimanager.setLoading(false);
}
async selectFloor(id) {

View file

@ -49,7 +49,9 @@ export class UIManager {
}
setEventsMinimized(state) {
if (state) {
if (state == null) {
this.uiElements.eventsContainer.classList.toggle("minimized");
} else if (state) {
this.uiElements.eventsContainer.classList.add("minimized");
} else {
this.uiElements.eventsContainer.classList.remove("minimized");
@ -57,6 +59,9 @@ export class UIManager {
}
setEventsInspector(state) {
if (
state != this.uiElements.eventsContainer.classList.contains("inspector")
)
this.setEventsMinimized(false);
if (state) {
this.uiElements.eventsContainer.classList.add("inspector");
@ -81,9 +86,6 @@ export class UIManager {
// Floors
this.uiElements.floors = this.ui.querySelector("#floors");
this.uiElements.floorsHeading = this.ui.querySelector(
"#floors .widget-heading"
);
this.uiElements.floorButtons =
this.uiElements.floors.querySelector("#floor-buttons");
@ -102,10 +104,6 @@ export class UIManager {
this.uiElements.events.querySelector("#events-header #room-description");
this.uiElements.eventList =
this.uiElements.events.querySelector("#event-list");
this.uiElements.eventsMinimized =
this.uiElements.eventsContainer.querySelector("#events-minimized");
this.uiElements.eventsMinimizedBackButton =
this.uiElements.eventsMinimized.querySelector("#back");
this.uiElements.eventsInspector =
this.uiElements.eventsContainer.querySelector("#events-inspector");
@ -117,29 +115,16 @@ export class UIManager {
this.uiElements.eventsInspectorHeader.querySelector("#event-length");
this.uiElements.eventsInspectorRoomTime =
this.uiElements.eventsInspector.querySelector("#room-time");
this.uiElements.eventsInspectorMinimized =
this.uiElements.eventsContainer.querySelector(
"#events-inspector-minimized"
);
this.uiElements.eventsInspectorBackButton =
this.uiElements.eventsInspector.querySelector("#back");
this.uiElements.eventsInspectorBody =
this.uiElements.eventsInspector.querySelector("#event-body");
this.uiElements.eventsInspectorMinimizedBackButton =
this.uiElements.eventsInspectorMinimized.querySelector("#back");
this.uiElements.minimizeButton = this.uiElements.events.querySelector(
"#footer #footer-buttons #minimize"
);
this.uiElements.maximizeButton =
this.uiElements.eventsMinimized.querySelector("#maximize");
this.uiElements.minimizeButton =
this.uiElements.events.querySelector("#minimize");
this.uiElements.eventsInspectorMinimizeButton =
this.uiElements.eventsInspector.querySelector(
"#footer #footer-buttons #minimize"
);
this.uiElements.eventsInspectorMaximizeButton =
this.uiElements.eventsInspectorMinimized.querySelector("#maximize");
this.uiElements.eventsInspector.querySelector("#minimize");
// Loading
this.uiElements.loading = this.ui.querySelector("#loading");
@ -166,21 +151,11 @@ export class UIManager {
__initInspector() {
this.uiElements.eventsInspectorMinimizeButton.addEventListener(
"click",
() => this.setEventsMinimized(true)
);
this.uiElements.eventsInspectorMaximizeButton.addEventListener(
"click",
() => this.setEventsMinimized(false)
() => this.setEventsMinimized()
);
this.uiElements.eventsInspectorBackButton.addEventListener("click", () => {
window.location.hash = this.mainmanager.getCurrentFocus("room");
});
this.uiElements.eventsInspectorMinimizedBackButton.addEventListener(
"click",
() => {
window.location.hash = this.mainmanager.getCurrentFocus("room");
}
);
}
updateInspector() {
@ -191,12 +166,8 @@ export class UIManager {
this.uiElements.eventsInspectorBackButton.title =
currentLocalization.event_inspector_back;
this.uiElements.eventsInspectorMinimizedBackButton.title =
currentLocalization.event_inspector_back;
this.uiElements.eventsInspectorMinimizeButton.title =
currentLocalization.event_inspector_minimize;
this.uiElements.eventsInspectorMaximizeButton.title =
currentLocalization.event_inspector_maximize;
const currentDate = new Date();
const eventDateStart = new Date(currentEvent.when.start);
@ -283,16 +254,10 @@ export class UIManager {
this.uiElements.eventsBackButton.addEventListener("click", () => {
window.location.hash = this.mainmanager.getCurrentFocus("floor");
});
this.uiElements.eventsMinimizedBackButton.addEventListener("click", () => {
window.location.hash = this.mainmanager.getCurrentFocus("floor");
});
this.uiElements.minimizeButton.addEventListener("click", () => {
this.uiElements.eventsContainer.classList.add("minimized");
});
this.uiElements.maximizeButton.addEventListener("click", () => {
this.uiElements.eventsContainer.classList.remove("minimized");
});
this.uiElements.minimizeButton.addEventListener("click", () =>
this.setEventsMinimized()
);
}
__updateEventsSoft() {
@ -314,7 +279,6 @@ export class UIManager {
const focusedRoom = this.mainmanager.getCurrentFocusObject("room");
this.uiElements.minimizeButton.title = currentLocalization.minimize;
this.uiElements.maximizeButton.title = currentLocalization.maximize;
// Figure out header
if (this.mainmanager.getIsFocused("room")) {
@ -329,9 +293,6 @@ export class UIManager {
this.uiElements.eventsBackButton.disabled = false;
this.uiElements.eventsBackButton.title =
currentLocalization.view_events_in(focusedFloor.name);
this.uiElements.eventsMinimizedBackButton.disabled = false;
this.uiElements.eventsMinimizedBackButton.title =
currentLocalization.view_events_in(focusedFloor.name);
} else {
if (currentEvents.size < 1) {
this.uiElements.eventsRoomName.textContent = focusedFloor.name;
@ -343,8 +304,6 @@ export class UIManager {
focusedFloor.description;
this.uiElements.eventsBackButton.disabled = true;
this.uiElements.eventsBackButton.title = "";
this.uiElements.eventsMinimizedBackButton.disabled = true;
this.uiElements.eventsMinimizedBackButton.title = "";
}
// Remove all children
@ -455,8 +414,6 @@ export class UIManager {
const mainFocus = this.mainmanager.getCurrentFocus("floor");
// Remove all children
this.uiElements.floorButtons.replaceChildren();
this.uiElements.floorsHeading.textContent =
currentLocalization.floors_header;
// Put them back
currentFloors.forEach(({ name }, id) => {
@ -486,9 +443,6 @@ export class UIManager {
const currentFloors = this.mainmanager.getAllFocusObject("floor");
const mainFocus = this.mainmanager.getCurrentFocus("floor");
this.uiElements.floorsHeading.textContent =
currentLocalization.floors_header;
currentFloors.forEach((_, id) => {
const child = this.uiElements.floorButtons.querySelector("#floor-" + id);
if (id === mainFocus) child.classList.add("selected");

View file

@ -15,7 +15,7 @@ h1.widget-heading {
margin: 0;
margin-bottom: 4px;
font-size: 14px;
letter-spacing: 0.25em;
/* letter-spacing: 0.25em; */
text-transform: uppercase;
/* border-bottom: 1px solid currentColor;
padding-bottom: 2px; */
@ -136,15 +136,13 @@ a {
}
#map-ui #floors #floor-buttons .floor-button {
text-align: left;
width: 200px;
padding: 12px;
height: unset;
display: inline-block;
width: 36px;
height: 36px;
padding: 4px;
}
#map-ui #floors #floor-buttons .floor-button.selected {
padding-left: 9px;
border: 1px solid #fff;
border-left: 4px solid #fff;
}
#map-ui #events-container {
@ -156,51 +154,57 @@ a {
}
#map-ui #events-container #events,
#map-ui #events-container #events-minimized,
#map-ui #events-container #events-inspector,
#map-ui #events-container #events-inspector-minimized {
#map-ui #events-container #events-inspector {
pointer-events: all;
box-sizing: border-box;
display: inline-block;
display: inline-flex;
position: absolute;
gap: 8px;
padding: 8px;
left: 16px;
bottom: 16px;
border-radius: 4px;
left: -100%;
translate: 0 calc(100% + 32px);
background-color: #202020;
color: #fff;
box-shadow: 0 0 8px 0 #0008;
transition: left 0.5s;
}
#map-ui #events-container #events,
#map-ui #events-container #events-inspector {
transition: translate 0.25s, bottom 0.25s, width 0.25s, border-radius 0.25s;
max-height: calc(50vh - 16px);
flex-direction: column;
width: 400px;
max-width: calc(100% - 32px);
z-index: 9;
}
#map-ui #events-container:not(.empty):not(.inspector):not(.minimized) #events {
left: 16px;
#map-ui #events-container:not(.inspector) #events,
#map-ui #events-container.inspector #events-inspector {
translate: 0;
}
#map-ui #events-container:not(.empty):not(.inspector).minimized #events-minimized {
left: 16px;
#map-ui #events-container:not(.inspector) #events,
#map-ui #events-container.inspector #events-inspector {
translate: 0;
}
#map-ui #events-container:not(.empty):not(.minimized).inspector #events-inspector {
left: 16px;
#map-ui #events-container:not(.inspector).minimized #events,
#map-ui #events-container.inspector.minimized #events-inspector {
bottom: 0;
translate: 0 calc(100% - 52px);
}
#map-ui #events-container:not(.empty).minimized.inspector #events-inspector-minimized {
left: 16px;
#map-ui #events-container.empty #events,
#map-ui #events-container.empty #events-inspector {
translate: 0 calc(100% + 32px);
}
/* #map-ui #floors.empty ~ #events-container #events,
#map-ui #floors.empty ~ #events-container #events-inspector,
#map-ui #floors.empty ~ #events-container #events-minimized,
#map-ui #floors.empty ~ #events-container #events-inspector-minimized {
left: -100% !important;
} */
#map-ui #events-container.minimized #events #room-description,
#map-ui #events-container.minimized #events-inspector #event-length {
display: none;
}
#map-ui #events-container.minimized #room-name {
margin: 0;
}
#map-ui #events-container #events #footer #footer-buttons,
#map-ui #events-container #events-inspector #footer #footer-buttons {
@ -214,28 +218,15 @@ a {
margin-bottom: 0;
}
#map-ui #events-container #events #events-header,
#map-ui #events-container #events-inspector #events-header {
#map-ui #events-container #events-header {
display: grid;
grid-template-columns: 36px auto;
align-items: start;
grid-template-columns: 36px auto 36px;
gap: 8px;
margin-bottom: 8px;
/* align-items: center; */
}
#map-ui #events-container #events #event-list {
/* min-height: 100px; */
max-height: calc(50% - 16px);
margin: 8px 0;
padding: 8px;
background-color: #0002;
display: flex;
flex-direction: column;
gap: 4px;
/* transition: height 0.25s,
min-height 0.25s,
padding 0.25s,
margin 0.25s; */
#map-ui #events-container.minimized #events-header {
align-items: center;
}
#map-ui #events-container #events.empty #event-list {
@ -248,19 +239,23 @@ a {
margin: 0;
}
#map-ui #events-container #events #event-list,
#map-ui #events-container #events-inspector #event-body {
/* min-height: 100px; */
max-height: calc(50% - 16px);
margin: 8px 0;
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;
}
#map-ui #events-container #events #events-header button,
#map-ui #events-container #footer #footer-buttons button,
#map-ui #events-container #events-inspector #events-header button:not(.link),
#map-ui #events-container #events-minimized button,
#map-ui #events-container #events-inspector-minimized button {
#map-ui #events-container #events-inspector #events-header button:not(.link) {
width: 36px;
}
@ -293,3 +288,13 @@ a {
mask-image: radial-gradient(circle, #fff0 0%, #fff0 33%, #ffff 33%, #ffff 66%, #fff0 66%, #fff0 100%);
animation: load 0.5s linear infinite;
}
@media (max-width: 640px) {
#map-ui #events-container #events,
#map-ui #events-container #events-inspector {
bottom: 0;
border-radius: 4px 4px 0 0;
width: 100%;
max-height: 33vh;
}
}

View file

@ -19,7 +19,6 @@
<button class="zoom-button" id="zoom-out" disabled>-</button>
</div>
<div id="floors" class="empty">
<h1 class="widget-heading"></h1>
<div id="floor-buttons">
</div>
@ -32,19 +31,11 @@
<h1 class="widget-heading" id="room-name"></h1>
<p class="widget-description" id="room-description"></p>
</div>
<button id="minimize">_</button>
</div>
<div id="event-list">
</div>
<div id="footer">
<div id="footer-buttons">
<button id="minimize">_</button>
</div>
</div>
</div>
<div id="events-minimized">
<button id="maximize"></button>
<button id="back" disabled></button>
</div>
<div id="events-inspector">
<div id="events-header">
@ -53,19 +44,11 @@
<h1 class="widget-heading" id="room-name"></h1>
<p class="widget-description subtitle" id="event-length"></p>
</div>
<button id="minimize">_</button>
</div>
<p class="widget-description" id="room-time"></p>
<div id="event-body">
</div>
<div id="footer">
<div id="footer-buttons">
<button id="minimize">_</button>
</div>
</div>
</div>
<div id="events-inspector-minimized">
<button id="maximize"></button>
<button id="back"></button>
</div>
</div>
<div id="loading">

View file

@ -19,21 +19,14 @@ app.get('/data/:lang/events/:floor', async (req, res) => {
else
return res.status(400).send("Bad Request");
// Get localization
let l10nReq = await fetch(new URL(`localization/${req.params.lang}.json`, config.data_url));
let l10n;
if (l10nReq.ok)
l10n = await l10nReq.json();
else
return res.status(404).send("Localization not found!");
// Merge events and localization
const lang = req.params.lang;
const merged = events.map(event => {
const localizationKey = l10n.__events[event.id];
return {
...event,
...localizationKey
};
event.name = event.lang[lang]?.name ?? "";
event.description = event.lang[lang]?.description ?? "";
event.url = event.lang[lang]?.url ?? "";
delete event.lang;
return event;
});
return res.send(merged);
@ -48,21 +41,13 @@ app.get('/data/:lang/floors', async (req, res) => {
else
return res.status(400).send("Bad Request");
// Get localization
let l10nReq = await fetch(new URL(`localization/${req.params.lang}.json`, config.data_url));
let l10n;
if (l10nReq.ok)
l10n = await l10nReq.json();
else
return res.status(404).send("Localization not found!");
// Merge layers and localization
const lang = req.params.lang;
const merged = layers.map(layer => {
const localizationKey = l10n.__layers[layer.id];
return {
...layer,
...localizationKey
};
layer.name = layer.lang[lang]?.name ?? "";
layer.description = layer.lang[lang]?.description ?? "";
delete layer.lang;
return layer;
});
return res.send(merged);
@ -77,21 +62,13 @@ app.get('/data/:lang/rooms/:floor', async (req, res) => {
else
return res.status(400).send("Bad Request");
// Get localization
let l10nReq = await fetch(new URL(`localization/${req.params.lang}.json`, config.data_url));
let l10n;
if (l10nReq.ok)
l10n = await l10nReq.json();
else
return res.status(404).send("Localization not found!");
// Merge rooms and localization
const lang = req.params.lang;
const merged = rooms.map(room => {
const localizationKey = l10n.__rooms[room.id];
return {
...room,
...localizationKey
};
room.name = room.lang[lang]?.name ?? "";
room.description = room.lang[lang]?.description ?? "";
delete room.lang;
return room;
});
return res.send(merged);
@ -102,3 +79,5 @@ app.listen(config.port, () => {
listeningURL.port = config.port;
console.log(`Example app listening on ${listeningURL.toString()}`)
})
export default app;