Add a shared cache for popovers
authorAlexander Ebert <ebert@woltlab.com>
Wed, 24 Jan 2024 18:34:51 +0000 (19:34 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 25 Jan 2024 15:29:59 +0000 (16:29 +0100)
ts/WoltLabSuite/Core/Component/Popover.ts
ts/WoltLabSuite/Core/Component/Popover/SharedCache.ts [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Component/Popover.js
wcfsetup/install/files/js/WoltLabSuite/Core/Component/Popover/SharedCache.js [new file with mode: 0644]

index 659307e0ab59d591e3d531e8a4829fb38d830b95..85436257050bffd612acc0f542a6d9b5ce5c208a 100644 (file)
@@ -1,9 +1,9 @@
-import { prepareRequest } from "../Ajax/Backend";
 import DomUtil from "../Dom/Util";
 import { getPageOverlayContainer } from "../Helper/PageOverlay";
 import { wheneverFirstSeen } from "../Helper/Selector";
 import RepeatingTimer from "../Timer/Repeating";
 import * as UiAlignment from "../Ui/Alignment";
+import SharedCache from "./Popover/SharedCache";
 
 const enum Delay {
   Hide = 500,
@@ -11,27 +11,24 @@ const enum Delay {
 }
 
 class Popover {
-  readonly #cache = new Map<number, string>();
-  #currentElement: HTMLElement | undefined = undefined;
+  readonly #cache: SharedCache;
   #container: HTMLElement | undefined = undefined;
-  readonly #endpoint: URL;
   #enabled = true;
   readonly #identifier: string;
-  #pendingElement: HTMLElement | undefined = undefined;
   #pendingObjectId: number | undefined = undefined;
   #timerStart: RepeatingTimer | undefined = undefined;
   #timerHide: RepeatingTimer | undefined = undefined;
 
-  constructor(selector: string, endpoint: string, identifier: string) {
+  constructor(cache: SharedCache, selector: string, identifier: string) {
+    this.#cache = cache;
     this.#identifier = identifier;
-    this.#endpoint = new URL(endpoint);
 
     wheneverFirstSeen(selector, (element) => {
       element.addEventListener("mouseenter", () => {
-        this.#hoverStart(element);
+        this.#showPopover(element);
       });
       element.addEventListener("mouseleave", () => {
-        this.#hoverEnd(element);
+        this.#hidePopover();
       });
     });
 
@@ -47,7 +44,7 @@ class Popover {
     });
   }
 
-  #hoverStart(element: HTMLElement): void {
+  #showPopover(element: HTMLElement): void {
     const objectId = this.#getObjectId(element);
 
     this.#pendingObjectId = objectId;
@@ -56,7 +53,7 @@ class Popover {
         timer.stop();
 
         const objectId = this.#pendingObjectId!;
-        void this.#getContent(objectId).then((content) => {
+        void this.#cache.get(objectId).then((content) => {
           if (objectId !== this.#pendingObjectId) {
             return;
           }
@@ -74,38 +71,19 @@ class Popover {
     }
   }
 
-  #hoverEnd(element: HTMLElement): void {
-    this.#timerStart?.stop();
-    this.#pendingObjectId = undefined;
-
+  #hidePopover(): void {
     if (this.#timerHide === undefined) {
-      this.#timerHide = new RepeatingTimer(() => {
-        // do something
+      this.#timerHide = new RepeatingTimer((timer) => {
+        timer.stop();
+
+        this.#timerStart?.stop();
+        this.#container?.setAttribute("aria-hidden", "true");
       }, Delay.Hide);
     } else {
       this.#timerHide.restart();
     }
   }
 
-  async #getContent(objectId: number): Promise<string> {
-    let content = this.#cache.get(objectId);
-    if (content !== undefined) {
-      return content;
-    }
-
-    this.#endpoint.searchParams.set("id", objectId.toString());
-
-    const response = await prepareRequest(this.#endpoint).get().fetchAsResponse();
-    if (!response?.ok) {
-      return "";
-    }
-
-    content = await response.text();
-    this.#cache.set(objectId, content);
-
-    return content;
-  }
-
   #setEnabled(enabled: boolean): void {
     this.#enabled = enabled;
   }
@@ -138,5 +116,7 @@ type Configuration = {
 export function setupFor(configuration: Configuration): void {
   const { identifier, endpoint, selector } = configuration;
 
-  new Popover(selector, endpoint, identifier);
+  const cache = new SharedCache(endpoint);
+
+  new Popover(cache, selector, identifier);
 }
diff --git a/ts/WoltLabSuite/Core/Component/Popover/SharedCache.ts b/ts/WoltLabSuite/Core/Component/Popover/SharedCache.ts
new file mode 100644 (file)
index 0000000..c39b91d
--- /dev/null
@@ -0,0 +1,33 @@
+import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend";
+
+type ObjectId = number;
+
+export class SharedCache {
+  readonly #data = new Map<ObjectId, string>();
+  readonly #endpoint: URL;
+
+  constructor(endpoint: string) {
+    this.#endpoint = new URL(endpoint);
+  }
+
+  async get(objectId: ObjectId): Promise<string> {
+    let content = this.#data.get(objectId);
+    if (content !== undefined) {
+      return content;
+    }
+
+    this.#endpoint.searchParams.set("id", objectId.toString());
+
+    const response = await prepareRequest(this.#endpoint).get().fetchAsResponse();
+    if (!response?.ok) {
+      return "";
+    }
+
+    content = await response.text();
+    this.#data.set(objectId, content);
+
+    return content;
+  }
+}
+
+export default SharedCache;
index 64917fd0d5bd26e59cec71287fef20d712f286dd..5257c08b1553ee16dcad823e69f0799d5bbbb991 100644 (file)
@@ -1,30 +1,28 @@
-define(["require", "exports", "tslib", "../Ajax/Backend", "../Dom/Util", "../Helper/PageOverlay", "../Helper/Selector", "../Timer/Repeating", "../Ui/Alignment"], function (require, exports, tslib_1, Backend_1, Util_1, PageOverlay_1, Selector_1, Repeating_1, UiAlignment) {
+define(["require", "exports", "tslib", "../Dom/Util", "../Helper/PageOverlay", "../Helper/Selector", "../Timer/Repeating", "../Ui/Alignment", "./Popover/SharedCache"], function (require, exports, tslib_1, Util_1, PageOverlay_1, Selector_1, Repeating_1, UiAlignment, SharedCache_1) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
     exports.setupFor = void 0;
     Util_1 = tslib_1.__importDefault(Util_1);
     Repeating_1 = tslib_1.__importDefault(Repeating_1);
     UiAlignment = tslib_1.__importStar(UiAlignment);
+    SharedCache_1 = tslib_1.__importDefault(SharedCache_1);
     class Popover {
-        #cache = new Map();
-        #currentElement = undefined;
+        #cache;
         #container = undefined;
-        #endpoint;
         #enabled = true;
         #identifier;
-        #pendingElement = undefined;
         #pendingObjectId = undefined;
         #timerStart = undefined;
         #timerHide = undefined;
-        constructor(selector, endpoint, identifier) {
+        constructor(cache, selector, identifier) {
+            this.#cache = cache;
             this.#identifier = identifier;
-            this.#endpoint = new URL(endpoint);
             (0, Selector_1.wheneverFirstSeen)(selector, (element) => {
                 element.addEventListener("mouseenter", () => {
-                    this.#hoverStart(element);
+                    this.#showPopover(element);
                 });
                 element.addEventListener("mouseleave", () => {
-                    this.#hoverEnd(element);
+                    this.#hidePopover();
                 });
             });
             const mq = window.matchMedia("(hover:hover)");
@@ -36,14 +34,14 @@ define(["require", "exports", "tslib", "../Ajax/Backend", "../Dom/Util", "../Hel
                 this.#setEnabled(false);
             });
         }
-        #hoverStart(element) {
+        #showPopover(element) {
             const objectId = this.#getObjectId(element);
             this.#pendingObjectId = objectId;
             if (this.#timerStart === undefined) {
                 this.#timerStart = new Repeating_1.default((timer) => {
                     timer.stop();
                     const objectId = this.#pendingObjectId;
-                    void this.#getContent(objectId).then((content) => {
+                    void this.#cache.get(objectId).then((content) => {
                         if (objectId !== this.#pendingObjectId) {
                             return;
                         }
@@ -58,32 +56,18 @@ define(["require", "exports", "tslib", "../Ajax/Backend", "../Dom/Util", "../Hel
                 this.#timerStart.restart();
             }
         }
-        #hoverEnd(element) {
-            this.#timerStart?.stop();
-            this.#pendingObjectId = undefined;
+        #hidePopover() {
             if (this.#timerHide === undefined) {
-                this.#timerHide = new Repeating_1.default(() => {
-                    // do something
+                this.#timerHide = new Repeating_1.default((timer) => {
+                    timer.stop();
+                    this.#timerStart?.stop();
+                    this.#container?.setAttribute("aria-hidden", "true");
                 }, 500 /* Delay.Hide */);
             }
             else {
                 this.#timerHide.restart();
             }
         }
-        async #getContent(objectId) {
-            let content = this.#cache.get(objectId);
-            if (content !== undefined) {
-                return content;
-            }
-            this.#endpoint.searchParams.set("id", objectId.toString());
-            const response = await (0, Backend_1.prepareRequest)(this.#endpoint).get().fetchAsResponse();
-            if (!response?.ok) {
-                return "";
-            }
-            content = await response.text();
-            this.#cache.set(objectId, content);
-            return content;
-        }
         #setEnabled(enabled) {
             this.#enabled = enabled;
         }
@@ -104,7 +88,8 @@ define(["require", "exports", "tslib", "../Ajax/Backend", "../Dom/Util", "../Hel
     }
     function setupFor(configuration) {
         const { identifier, endpoint, selector } = configuration;
-        new Popover(selector, endpoint, identifier);
+        const cache = new SharedCache_1.default(endpoint);
+        new Popover(cache, selector, identifier);
     }
     exports.setupFor = setupFor;
 });
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Popover/SharedCache.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Popover/SharedCache.js
new file mode 100644 (file)
index 0000000..03ffc15
--- /dev/null
@@ -0,0 +1,28 @@
+define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend"], function (require, exports, Backend_1) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.SharedCache = void 0;
+    class SharedCache {
+        #data = new Map();
+        #endpoint;
+        constructor(endpoint) {
+            this.#endpoint = new URL(endpoint);
+        }
+        async get(objectId) {
+            let content = this.#data.get(objectId);
+            if (content !== undefined) {
+                return content;
+            }
+            this.#endpoint.searchParams.set("id", objectId.toString());
+            const response = await (0, Backend_1.prepareRequest)(this.#endpoint).get().fetchAsResponse();
+            if (!response?.ok) {
+                return "";
+            }
+            content = await response.text();
+            this.#data.set(objectId, content);
+            return content;
+        }
+    }
+    exports.SharedCache = SharedCache;
+    exports.default = SharedCache;
+});