Embed the control panel into the user menu
authorAlexander Ebert <ebert@woltlab.com>
Tue, 14 Dec 2021 16:44:11 +0000 (17:44 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 14 Dec 2021 16:44:11 +0000 (17:44 +0100)
com.woltlab.wcf/templates/pageHeaderUser.tpl
ts/WoltLabSuite/Core/Ui/Page/Menu/User.ts
ts/WoltLabSuite/Core/Ui/User/Menu/ControlPanel.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/User.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Menu/ControlPanel.js
wcfsetup/install/files/style/ui/userMenu.scss

index 6ffc193151078a1b69b987d2815eb1b25cdaba58..68a5c3ff7ed4b721d0ba403b931b9fadfae1fcca 100644 (file)
@@ -60,7 +60,7 @@
                                        </div>
                                        <div class="userMenuContentDivider"></div>
                                        {/if}
-                                       <div class="userMenuContent">
+                                       <div class="userMenuContent userMenuContentScrollable">
                                                {foreach from=$__wcf->getUserMenu()->getUserMenuItems() item=menuItem}
                                                <div class="userMenuItem userMenuItemNarrow" data-category="{$menuItem[category]->menuItem}">
                                                        <div class="userMenuItemImage">
index 61080f9c5ab1741b1895d5450bbe191b4814577a..55a7ee45e0e78d6be634b2a40810ee2170f70246 100644 (file)
@@ -13,6 +13,7 @@ import * as Language from "../../../Language";
 import { getUserMenuProviders } from "../../User/Menu/Manager";
 import { UserMenuProvider } from "../../User/Menu/Data/Provider";
 import DomUtil from "../../../Dom/Util";
+import { getElement as getControlPanelElement } from "../../User/Menu/ControlPanel";
 
 type CallbackOpen = (event: MouseEvent) => void;
 
@@ -20,6 +21,12 @@ type Tab = HTMLAnchorElement;
 type TabPanel = HTMLDivElement;
 type TabComponents = [Tab, TabPanel];
 
+type TabData = {
+  icon: string;
+  label: string;
+  origin: string;
+};
+
 export class PageMenuUser implements PageMenuProvider {
   private readonly callbackOpen: CallbackOpen;
   private readonly container: PageMenuContainer;
@@ -117,15 +124,26 @@ export class PageMenuUser implements PageMenuProvider {
   }
 
   private attachViewToPanel(tab: HTMLAnchorElement): void {
+    const origin = tab.dataset.origin!;
     const tabPanel = this.tabPanels.get(tab)!;
-    if (tabPanel.childElementCount === 0) {
-      const provider = this.userMenuProviders.get(tab);
-      if (provider) {
-        const view = provider.getView();
-        tabPanel.append(view.getElement());
-        void view.open();
-      } else {
-        throw new Error("TODO: Legacy user panel menus");
+
+    if (origin === "userMenu") {
+      const element = getControlPanelElement();
+      element.hidden = false;
+
+      if (tabPanel.childElementCount === 0) {
+        tabPanel.append(element);
+      }
+    } else {
+      if (tabPanel.childElementCount === 0) {
+        const provider = this.userMenuProviders.get(tab);
+        if (provider) {
+          const view = provider.getView();
+          tabPanel.append(view.getElement());
+          void view.open();
+        } else {
+          throw new Error("TODO: Legacy user panel menus");
+        }
       }
     }
   }
@@ -183,7 +201,12 @@ export class PageMenuUser implements PageMenuProvider {
     tabList.setAttribute("aria-label", Language.get("TODO"));
     tabContainer.append(tabList);
 
-    // TODO: Inject the control panel first.
+    const [tab, tabPanel] = this.buildControlPanelTab();
+    tabList.append(tab);
+    tabContainer.append(tabPanel);
+
+    this.tabs.push(tab);
+    this.tabPanels.set(tab, tabPanel);
 
     getUserMenuProviders().forEach((provider) => {
       const [tab, tabPanel] = this.buildTab(provider);
@@ -202,21 +225,47 @@ export class PageMenuUser implements PageMenuProvider {
   }
 
   private buildTab(provider: UserMenuProvider): TabComponents {
+    const panelButton = provider.getPanelButton();
+    const button = panelButton.querySelector("a")!;
+
+    const data: TabData = {
+      icon: button.querySelector(".icon")!.outerHTML,
+      label: button.dataset.title || button.title,
+      origin: panelButton.id,
+    };
+
+    return this.buildTabComponents(data);
+  }
+
+  private buildControlPanelTab(): TabComponents {
+    const panel = document.getElementById("topMenu")!;
+    const userMenu = document.getElementById("userMenu")!;
+    const userMenuButton = userMenu.querySelector("a")!;
+
+    const data: TabData = {
+      icon: panel.querySelector(".userPanelAvatar .userAvatarImage")!.outerHTML,
+      label: userMenuButton.dataset.title || userMenuButton.title,
+      origin: userMenu.id,
+    };
+
+    return this.buildTabComponents(data);
+  }
+
+  private buildTabComponents(data: TabData): TabComponents {
     const tabId = DomUtil.getUniqueId();
     const panelId = DomUtil.getUniqueId();
 
     const tab = document.createElement("a");
     tab.classList.add("pageMenuUserTab");
-    tab.dataset.origin = provider.getPanelButton().id;
+    tab.dataset.origin = data.origin;
     tab.id = tabId;
     tab.setAttribute("aria-controls", panelId);
     tab.setAttribute("aria-selected", "false");
     tab.setAttribute("role", "tab");
     tab.tabIndex = -1;
 
-    const button = provider.getPanelButton().querySelector("a")!;
-    tab.setAttribute("aria-label", button.dataset.title || button.title);
-    tab.innerHTML = button.querySelector(".icon")!.outerHTML;
+    tab.setAttribute("aria-label", data.label);
+    tab.innerHTML = data.icon;
 
     tab.addEventListener("click", (event) => {
       event.preventDefault();
index 45bf6d61a90917cd2d4e3c0f47f168c96c6eb0ca..0bbbbad1742cf5e629291ad3c9d26a083c2962d2 100644 (file)
@@ -54,6 +54,10 @@ function close(): void {
   link.setAttribute("aria-expanded", "false");
 }
 
+export function getElement(): HTMLElement {
+  return element;
+}
+
 let isInitialized = false;
 export function setup(): void {
   if (!isInitialized) {
index 25f85f6cd7b3bcd1bf2a431bec2ed57a418c7bc2..d2280f2d176b94a6d43a1b362610886453b5dc61 100644 (file)
@@ -6,7 +6,7 @@
  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module WoltLabSuite/Core/Ui/Page/Menu/User
  */
-define(["require", "exports", "tslib", "./Container", "../../../Language", "../../User/Menu/Manager", "../../../Dom/Util"], function (require, exports, tslib_1, Container_1, Language, Manager_1, Util_1) {
+define(["require", "exports", "tslib", "./Container", "../../../Language", "../../User/Menu/Manager", "../../../Dom/Util", "../../User/Menu/ControlPanel"], function (require, exports, tslib_1, Container_1, Language, Manager_1, Util_1, ControlPanel_1) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
     exports.hasValidUserMenu = exports.PageMenuUser = void 0;
@@ -87,16 +87,26 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
             this.attachViewToPanel(tab);
         }
         attachViewToPanel(tab) {
+            const origin = tab.dataset.origin;
             const tabPanel = this.tabPanels.get(tab);
-            if (tabPanel.childElementCount === 0) {
-                const provider = this.userMenuProviders.get(tab);
-                if (provider) {
-                    const view = provider.getView();
-                    tabPanel.append(view.getElement());
-                    void view.open();
+            if (origin === "userMenu") {
+                const element = (0, ControlPanel_1.getElement)();
+                element.hidden = false;
+                if (tabPanel.childElementCount === 0) {
+                    tabPanel.append(element);
                 }
-                else {
-                    throw new Error("TODO: Legacy user panel menus");
+            }
+            else {
+                if (tabPanel.childElementCount === 0) {
+                    const provider = this.userMenuProviders.get(tab);
+                    if (provider) {
+                        const view = provider.getView();
+                        tabPanel.append(view.getElement());
+                        void view.open();
+                    }
+                    else {
+                        throw new Error("TODO: Legacy user panel menus");
+                    }
                 }
             }
         }
@@ -147,7 +157,11 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
             tabList.setAttribute("role", "tablist");
             tabList.setAttribute("aria-label", Language.get("TODO"));
             tabContainer.append(tabList);
-            // TODO: Inject the control panel first.
+            const [tab, tabPanel] = this.buildControlPanelTab();
+            tabList.append(tab);
+            tabContainer.append(tabPanel);
+            this.tabs.push(tab);
+            this.tabPanels.set(tab, tabPanel);
             (0, Manager_1.getUserMenuProviders)().forEach((provider) => {
                 const [tab, tabPanel] = this.buildTab(provider);
                 tabList.append(tab);
@@ -160,19 +174,39 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
             return tabContainer;
         }
         buildTab(provider) {
+            const panelButton = provider.getPanelButton();
+            const button = panelButton.querySelector("a");
+            const data = {
+                icon: button.querySelector(".icon").outerHTML,
+                label: button.dataset.title || button.title,
+                origin: panelButton.id,
+            };
+            return this.buildTabComponents(data);
+        }
+        buildControlPanelTab() {
+            const panel = document.getElementById("topMenu");
+            const userMenu = document.getElementById("userMenu");
+            const userMenuButton = userMenu.querySelector("a");
+            const data = {
+                icon: panel.querySelector(".userPanelAvatar .userAvatarImage").outerHTML,
+                label: userMenuButton.dataset.title || userMenuButton.title,
+                origin: userMenu.id,
+            };
+            return this.buildTabComponents(data);
+        }
+        buildTabComponents(data) {
             const tabId = Util_1.default.getUniqueId();
             const panelId = Util_1.default.getUniqueId();
             const tab = document.createElement("a");
             tab.classList.add("pageMenuUserTab");
-            tab.dataset.origin = provider.getPanelButton().id;
+            tab.dataset.origin = data.origin;
             tab.id = tabId;
             tab.setAttribute("aria-controls", panelId);
             tab.setAttribute("aria-selected", "false");
             tab.setAttribute("role", "tab");
             tab.tabIndex = -1;
-            const button = provider.getPanelButton().querySelector("a");
-            tab.setAttribute("aria-label", button.dataset.title || button.title);
-            tab.innerHTML = button.querySelector(".icon").outerHTML;
+            tab.setAttribute("aria-label", data.label);
+            tab.innerHTML = data.icon;
             tab.addEventListener("click", (event) => {
                 event.preventDefault();
                 this.openTab(tab);
index 9646127bf69ed59bc13b79f989818bb8ec1fd935..4d6424041d6e412693dcdd84fd122b2df6b4cad8 100644 (file)
@@ -10,7 +10,7 @@
 define(["require", "exports", "tslib", "../../CloseOverlay", "./Manager", "focus-trap", "../../Alignment", "../../../Dom/Util"], function (require, exports, tslib_1, CloseOverlay_1, Manager_1, focus_trap_1, Alignment, Util_1) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
-    exports.setup = void 0;
+    exports.setup = exports.getElement = void 0;
     CloseOverlay_1 = (0, tslib_1.__importDefault)(CloseOverlay_1);
     Alignment = (0, tslib_1.__importStar)(Alignment);
     Util_1 = (0, tslib_1.__importDefault)(Util_1);
@@ -42,6 +42,10 @@ define(["require", "exports", "tslib", "../../CloseOverlay", "./Manager", "focus
         button.classList.remove("open");
         link.setAttribute("aria-expanded", "false");
     }
+    function getElement() {
+        return element;
+    }
+    exports.getElement = getElement;
     let isInitialized = false;
     function setup() {
         if (!isInitialized) {
index f2ecf1198aca0c83e301dca007dabd1b807b01bd..4fd4d9a753d080c0d4a2afafda8277a063014b06 100644 (file)
@@ -42,7 +42,7 @@
        flex-direction: column;
        height: 100%;
 
-       .userMenuContent {
+       .userMenuContentScrollable {
                flex: 1 auto;
        }
 }