From d652568628abd71f542adc07679b626ddf0356b4 Mon Sep 17 00:00:00 2001 From: MeowcaTheoRange Date: Sun, 2 Jun 2024 22:54:02 -0500 Subject: [PATCH] stuff & things, add more UI --- assets/images/minus.png | Bin 208 -> 0 bytes assets/images/plus.png | Bin 248 -> 0 bytes assets/scripts/KaplayMap/floors.js | 44 +++++- assets/scripts/KaplayMap/gameobj.js | 20 ++- assets/scripts/KaplayMap/index.js | 2 +- assets/scripts/KaplayMap/localization.js | 20 +++ assets/scripts/KaplayMap/ui.js | 181 +++++++++++++++++++++-- assets/scripts/modules/relative_time.js | 26 ++++ assets/styles/style.css | 40 ++++- package-lock.json | 8 +- package.json | 3 +- pages/index.html | 11 +- 12 files changed, 328 insertions(+), 27 deletions(-) delete mode 100644 assets/images/minus.png delete mode 100644 assets/images/plus.png create mode 100644 assets/scripts/KaplayMap/localization.js create mode 100644 assets/scripts/modules/relative_time.js diff --git a/assets/images/minus.png b/assets/images/minus.png deleted file mode 100644 index 5dbdee363b74fab1d6eea1609de0aa62a27139ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Et!3HGD8EPYe)O1f5#}JRscbxbX+Rns!d3aWi{3M*!D^S~neu4+05E2?+@nH81T>N}ase z%zI#QLloO2t_0JY^+Dai*3sUpUbwIG@?0BoVRhoBFU$;ICMlO5T`3X@bRvVNtDnm{ Hr-UW|A~#B% diff --git a/assets/images/plus.png b/assets/images/plus.png deleted file mode 100644 index af56f5bbe5651f90fed23f87315cd9eb0ab5ebde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Et!3HGD8EPYe)P7GF#}JRsPa`#Brt?fehS5B@KFhE>i?}Go+Y~Fzngc5TMlZ zj47f^ShCqv`PUcbAV8 o)Hw1pH!HJfu(F%St48ZHY!J7sI?5oo0_YV6Pgg&ebxsLQ09MXZoB#j- diff --git a/assets/scripts/KaplayMap/floors.js b/assets/scripts/KaplayMap/floors.js index ce585b9..20b3c21 100644 --- a/assets/scripts/KaplayMap/floors.js +++ b/assets/scripts/KaplayMap/floors.js @@ -7,7 +7,8 @@ export class FloorManager { floors = new Map([]); rooms = new Map([]); - currentFloor = ""; + #currentFloor = ""; + #currentRoom = ""; constructor(map, ui, gameobj, events, lang) { this.map = map; @@ -20,6 +21,38 @@ export class FloorManager { this.lang = lang; } + set currentFloor(floor) { + this.#currentFloor = floor; + } + + get currentFloor() { + return this.#currentFloor; + } + + get currentFloorItem() { + return this.floors.get(this.#currentFloor); + } + + setCurrentRoom(room, maximize = true) { + this.#currentRoom = room; + this.ui.__updateEvents(); + if (maximize) this.ui.eventsMaximize(); + if (room == "") this.gameobj.zoomToFloor(); + else this.gameobj.zoomToRoom(room); + } + + get currentRoom() { + return this.#currentRoom; + } + + get currentRoomItem() { + return this.currentFloorRooms.get(this.#currentRoom); + } + + get currentFloorRooms() { + return this.rooms.get(this.#currentFloor); + } + async getFloors() { let allFloorsReqSend = fetch(`/data/${this.lang}/map`); @@ -51,7 +84,10 @@ export class FloorManager { let allRooms; if (allRoomsReq.ok) allRooms = await allRoomsReq.json(); - this.rooms.set(id, allRooms); + const roomMap = new Map([]); + allRooms.forEach((room) => roomMap.set(room.id, room)); + + this.rooms.set(id, roomMap); this.ui.setLoading(false); @@ -60,7 +96,7 @@ export class FloorManager { async changeFloor(id) { const floor = this.floors.get(id); - if (floor != null) this.currentFloor = id; + if (floor != null) this.#currentFloor = id; if (this.ui.floorsEmpty) this.ui.__updateFloorsHard(); else this.ui.__updateFloorsSoft(); @@ -77,6 +113,8 @@ export class FloorManager { this.ui.__updateEvents(); + this.setCurrentRoom("", false); + this.ui.setLoading(false); return floor; diff --git a/assets/scripts/KaplayMap/gameobj.js b/assets/scripts/KaplayMap/gameobj.js index e70e30a..4855709 100644 --- a/assets/scripts/KaplayMap/gameobj.js +++ b/assets/scripts/KaplayMap/gameobj.js @@ -4,7 +4,6 @@ export class GameObjManager { floorObject; roomObjects = new Map([]); - currentRoom; constructor(map) { this.map = map; @@ -55,6 +54,21 @@ export class GameObjManager { this.map.zoomToAbs(this.map.opts.minZoomLevel, bounds.center()); } + zoomToFloor() { + const objectBounds = this.floorObject.renderArea().bbox(); + + this.map.zoomToAbs(this.map.opts.minZoomLevel, objectBounds.center()); + } + + zoomToRoom(id) { + const selectedObject = this.roomObjects.get(id); + if (selectedObject == null) return; + + const objectBounds = selectedObject.renderArea().bbox(); + + this.map.zoomToAbs(this.map.opts.minZoomLevel + 1, objectBounds.center()); + } + generateRooms() { if (this.roomObjects.size > 0) { this.roomObjects.forEach((x) => x.destroy()); @@ -78,7 +92,7 @@ export class GameObjManager { this.map.kp.pos(), this.map.kp.z(6), { - clickForgiveness: 1, + clickForgiveness: 5, startClickPosition: null, }, ]); @@ -108,7 +122,7 @@ export class GameObjManager { obj.startClickPosition && obj.startClickPosition.dist(endClickPosition) < obj.clickForgiveness ) { - this.currentRoom = room.id; + this.floormanager.setCurrentRoom(room.id); } obj.color = this.map.kp.Color.fromHex("505050"); this.map.clearMouseMode(); diff --git a/assets/scripts/KaplayMap/index.js b/assets/scripts/KaplayMap/index.js index c2db368..c150049 100644 --- a/assets/scripts/KaplayMap/index.js +++ b/assets/scripts/KaplayMap/index.js @@ -4,7 +4,7 @@ export class KaplayMap { minZoomLevel: 2, maxZoomLevel: 5, dblClickDuration: 0.2, // s - dblClickForgiveness: 30, // px + dblClickForgiveness: 100, // px }; uiLayer; diff --git a/assets/scripts/KaplayMap/localization.js b/assets/scripts/KaplayMap/localization.js new file mode 100644 index 0000000..5e88ae7 --- /dev/null +++ b/assets/scripts/KaplayMap/localization.js @@ -0,0 +1,20 @@ +export default new Map([ + [ + "en_US", + { + date_starting: "Starting", + date_started: "Started", + floors_header: "Floors", + date_summary: (prefix, time) => `${prefix} ${time}`, + parenthesis: (content) => `(${content})`, + hours_long: (hours) => + hours == 1 ? `${hours} hour long` : `${hours} hours long`, + minutes_long: (hours) => + hours == 1 ? `${hours} minute long` : `${hours} minutes long`, + events_in: (name) => `Events (${name})`, + view_events_in: (name) => `Return to events in ${name}`, + minimize: "Hide events panel", + maximize: "Show events panel", + }, + ], +]); diff --git a/assets/scripts/KaplayMap/ui.js b/assets/scripts/KaplayMap/ui.js index 7fbc210..8b003f5 100644 --- a/assets/scripts/KaplayMap/ui.js +++ b/assets/scripts/KaplayMap/ui.js @@ -1,3 +1,9 @@ +import { + getMinutesDifference, + getRelativeTime, +} from "../modules/relative_time.js"; +import localization from "./localization.js"; + export class UIManager { ui; map; @@ -21,6 +27,9 @@ 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"); @@ -29,10 +38,26 @@ export class UIManager { this.ui.querySelector("#events-container"); this.uiElements.events = this.uiElements.eventsContainer.querySelector("#events"); + this.uiElements.eventsBackButton = this.uiElements.events.querySelector( + "#events-header #back" + ); + this.uiElements.eventsRoomName = this.uiElements.events.querySelector( + "#events-header #room-name" + ); + this.uiElements.eventsRoomDescription = + 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.minimizeButton = this.uiElements.events.querySelector( + "#footer #footer-buttons #minimize" + ); + this.uiElements.maximizeButton = + this.uiElements.eventsMinimized.querySelector("#maximize"); // Loading this.uiElements.loading = this.ui.querySelector("#loading"); @@ -64,33 +89,163 @@ export class UIManager { }); } + toggleEventsMinimized() { + this.uiElements.eventsContainer.classList.toggle("minimized"); + } + + eventsMinimize() { + this.uiElements.eventsContainer.classList.add("minimized"); + } + + eventsMaximize() { + this.uiElements.eventsContainer.classList.remove("minimized"); + } + + __updateAll() { + this.__updateEvents(); + this.__updateFloorsHard(); + } + __initEvents() { - const minimizeButton = this.uiElements.events.querySelector( - "#footer #footer-buttons #minimize" - ); - minimizeButton.addEventListener("click", () => { + this.uiElements.eventsBackButton.addEventListener("click", () => { + this.floormanager.setCurrentRoom(""); + }); + this.uiElements.eventsMinimizedBackButton.addEventListener("click", () => { + this.floormanager.setCurrentRoom("", false); + }); + + this.uiElements.minimizeButton.addEventListener("click", () => { this.uiElements.eventsContainer.classList.add("minimized"); }); - const maximizeButton = - this.uiElements.eventsMinimized.querySelector("#maximize"); - maximizeButton.addEventListener("click", () => { + this.uiElements.maximizeButton.addEventListener("click", () => { this.uiElements.eventsContainer.classList.remove("minimized"); }); } __updateEvents() { + const currentLocalization = localization.get(this.floormanager.lang); + + this.uiElements.minimizeButton.title = currentLocalization.minimize; + this.uiElements.maximizeButton.title = currentLocalization.maximize; + // Figure out header + + if (this.floormanager.currentRoomItem != null) { + this.uiElements.eventsRoomName.textContent = + currentLocalization.events_in(this.floormanager.currentRoomItem.name); + this.uiElements.eventsRoomDescription.textContent = + this.floormanager.currentRoomItem.description; + this.uiElements.eventsBackButton.disabled = false; + this.uiElements.eventsBackButton.title = + currentLocalization.view_events_in( + this.floormanager.currentFloorItem.name + ); + this.uiElements.eventsMinimizedBackButton.disabled = false; + this.uiElements.eventsMinimizedBackButton.title = + currentLocalization.view_events_in( + this.floormanager.currentFloorItem.name + ); + } else { + this.uiElements.eventsRoomName.textContent = + currentLocalization.events_in(this.floormanager.currentFloorItem.name); + this.uiElements.eventsRoomDescription.textContent = + this.floormanager.currentFloorItem.description; + this.uiElements.eventsBackButton.disabled = true; + this.uiElements.eventsBackButton.title = ""; + this.uiElements.eventsMinimizedBackButton.disabled = true; + this.uiElements.eventsMinimizedBackButton.title = ""; + } + // Remove all children this.uiElements.eventList.replaceChildren(); - console.log(this.eventmanager.events); // Put them back + + const currentDate = new Date(); this.eventmanager.events.forEach((event, id) => { + if ( + this.floormanager.currentRoomItem && + event.where != this.floormanager.currentRoom + ) + return; // Don't display this event + + const eventFloor = this.floormanager.currentFloorRooms.get(event.where); + const eventDateStart = new Date(event.when.start); + const eventDateEnd = new Date(event.when.end); + + 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("details"); eventListContainer.classList.add("event-list-container"); eventListContainer.id = "event-" + id; const eventListContainerSummary = document.createElement("summary"); - eventListContainerSummary.textContent = event.name; + + const eventListContainerSummaryName = document.createElement("b"); + eventListContainerSummaryName.textContent = event.name; + + eventListContainerSummary.appendChild(eventListContainerSummaryName); + + eventListContainerSummary.appendChild(document.createTextNode(" ")); + + if (this.floormanager.currentRoomItem == null) { + const eventListContainerSummaryRoom = document.createElement("button"); + eventListContainerSummaryRoom.classList.add("link"); + eventListContainerSummaryRoom.addEventListener("click", () => { + this.floormanager.setCurrentRoom(eventFloor.id); + }); + eventListContainerSummaryRoom.textContent = + currentLocalization.parenthesis(eventFloor.name); + + 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.floormanager.lang.replace(/_/g, "-") + ); + eventListContainerSummaryTime.textContent = + currentLocalization.date_summary( + datePrefix, + getRelativeTime( + eventDateStart, + currentDate, + this.floormanager.lang.replace(/_/g, "-") + ) + ); + + 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); @@ -120,8 +275,11 @@ export class UIManager { } __updateFloorsHard() { + const currentLocalization = localization.get(this.floormanager.lang); // Remove all children this.uiElements.floorButtons.replaceChildren(); + this.uiElements.floorsHeading.textContent = + currentLocalization.floors_header; // Put them back this.floormanager.floors.forEach(({ name }, id) => { @@ -148,6 +306,11 @@ export class UIManager { } __updateFloorsSoft() { + const currentLocalization = localization.get(this.floormanager.lang); + + this.uiElements.floorsHeading.textContent = + currentLocalization.floors_header; + this.floormanager.floors.forEach(({ name }, id) => { const child = this.uiElements.floorButtons.querySelector("#floor-" + id); if (id === this.floormanager.currentFloor) diff --git a/assets/scripts/modules/relative_time.js b/assets/scripts/modules/relative_time.js new file mode 100644 index 0000000..5547e04 --- /dev/null +++ b/assets/scripts/modules/relative_time.js @@ -0,0 +1,26 @@ +// in miliseconds +export const units = { + year: 24 * 60 * 60 * 1000 * 365, + month: (24 * 60 * 60 * 1000 * 365) / 12, + day: 24 * 60 * 60 * 1000, + hour: 60 * 60 * 1000, + minute: 60 * 1000, + second: 1000, +}; + +export function getRelativeTime(d1, d2, locale) { + let rtf = new Intl.RelativeTimeFormat(locale, { + numeric: "auto", + }); + + let elapsed = d1 - d2; + + // "Math.abs" accounts for both "past" & "future" scenarios + for (var u in units) + if (Math.abs(elapsed) > units[u] || u == "second") + return rtf.format(Math.round(elapsed / units[u]), u); +} + +export function getMinutesDifference(d1, d2) { + return (d2.getTime() - d1.getTime()) / units.minute; +} diff --git a/assets/styles/style.css b/assets/styles/style.css index 384d07a..b916532 100644 --- a/assets/styles/style.css +++ b/assets/styles/style.css @@ -28,7 +28,7 @@ p.widget-description { padding-bottom: 2px; */ } -button { +button:not(.link) { background-color: transparent; border: none; color: inherit; @@ -39,16 +39,31 @@ button { border: 1px solid #0008; } -button:hover { +button:not(.link):hover { background-color: #0004; } -button:active { +button:not(.link):active { background-color: #0008; } -button:disabled { +button:not(.link):disabled { background-color: #8884; + color: #808080; +} + +button.link { + background: none !important; + border: none; + padding: 0; + font: inherit; + color: currentColor; + text-decoration: underline; + cursor: pointer; +} + +.clarification { + text-decoration: dotted underline; } #map { @@ -147,7 +162,7 @@ button:disabled { #map-ui #events-container #events { left: 16px; - width: 300px; + width: 400px; } #map-ui #events-container.minimized #events { @@ -168,8 +183,20 @@ button:disabled { gap: 8px; } +#map-ui #events-container #events #events-header p { + margin-bottom: 0; +} + +#map-ui #events-container #events #events-header { + display: grid; + grid-template-columns: 36px auto; + gap: 8px; + margin-bottom: 8px; + align-items: center; +} + #map-ui #events-container #events #event-list { - min-height: 100px; + min-height: 200px; max-height: calc(50% - 16px); margin: 8px 0; padding: 8px; @@ -184,6 +211,7 @@ button:disabled { white-space: pre-line; } +#map-ui #events-container #events #events-header button, #map-ui #events-container #footer #footer-buttons button, #map-ui #events-container #events-minimized button { width: 36px; diff --git a/package-lock.json b/package-lock.json index 9bbe217..0b6b594 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "express": "^4.19.2" + "express": "^4.19.2", + "https": "^1.0.0" }, "devDependencies": { "nodemon": "^3.1.1" @@ -499,6 +500,11 @@ "node": ">= 0.8" } }, + "node_modules/https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", + "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==" + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", diff --git a/package.json b/package.json index 7e5273f..2540068 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "type": "module", "license": "MIT", "dependencies": { - "express": "^4.19.2" + "express": "^4.19.2", + "https": "^1.0.0" }, "devDependencies": { "nodemon": "^3.1.1" diff --git a/pages/index.html b/pages/index.html index aff9e44..c59a0ea 100644 --- a/pages/index.html +++ b/pages/index.html @@ -26,20 +26,25 @@
-

Events

-

Lorem ipsum dolor sit amet.

+
+ +
+

Events

+

Lorem ipsum dolor sit amet.

+
+
+