From: Alexander Ebert Date: Fri, 31 Dec 2021 14:21:45 +0000 (+0100) Subject: Removal of unused components from the previous menu implementation X-Git-Tag: 5.5.0_Alpha_1~242^2~11 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=16a746b27d35dc8d7693c29de6505b79125ffdd3;p=GitHub%2FWoltLab%2FWCF.git Removal of unused components from the previous menu implementation --- diff --git a/com.woltlab.wcf/acpTemplateDelete.xml b/com.woltlab.wcf/acpTemplateDelete.xml index ff858ff644..43542e97cc 100644 --- a/com.woltlab.wcf/acpTemplateDelete.xml +++ b/com.woltlab.wcf/acpTemplateDelete.xml @@ -32,6 +32,7 @@ + diff --git a/com.woltlab.wcf/fileDelete.xml b/com.woltlab.wcf/fileDelete.xml index 8556feed1a..fc6b6a2a5a 100644 --- a/com.woltlab.wcf/fileDelete.xml +++ b/com.woltlab.wcf/fileDelete.xml @@ -88,6 +88,7 @@ acp/templates/pageLogo.tpl acp/templates/pageMenuItemAdd.tpl acp/templates/pageMenuItemList.tpl + acp/templates/pageMenuMobile.tpl acp/templates/pageMenuUser.tpl acp/templates/pageSearchArea.tpl acp/templates/reactionTypeIcon.tpl @@ -1371,6 +1372,7 @@ js/WoltLabSuite/Core/Media/Search.js js/WoltLabSuite/Core/Ui/InlineEditor.js js/WoltLabSuite/Core/Ui/Page/JumpToTop.js + js/WoltLabSuite/Core/Ui/Page/Menu/Abstract.js js/WoltLabSuite/Core/Ui/Reaction/ReputationButtons.js js/WoltLabSuite/Core/Ui/Redactor/RuntimeStyle.js js/closest.js @@ -2495,6 +2497,7 @@ style/ui/dialog.less style/ui/dropdown.less style/ui/dropdownInteractive.less + style/ui/menuMobile.scss style/ui/message.less style/ui/poll.less style/ui/popover.less diff --git a/com.woltlab.wcf/templateDelete.xml b/com.woltlab.wcf/templateDelete.xml index f00d6da3d0..b38fa7214c 100644 --- a/com.woltlab.wcf/templateDelete.xml +++ b/com.woltlab.wcf/templateDelete.xml @@ -37,6 +37,7 @@ + diff --git a/com.woltlab.wcf/templates/footer.tpl b/com.woltlab.wcf/templates/footer.tpl index 0e9a4e229e..30b416fb8d 100644 --- a/com.woltlab.wcf/templates/footer.tpl +++ b/com.woltlab.wcf/templates/footer.tpl @@ -108,8 +108,6 @@ {include file='pageFooter'} -{include file='pageMenuMobile'} - {event name='footer'}
diff --git a/com.woltlab.wcf/templates/pageMenuMobile.tpl b/com.woltlab.wcf/templates/pageMenuMobile.tpl deleted file mode 100644 index 7dc604ebb5..0000000000 --- a/com.woltlab.wcf/templates/pageMenuMobile.tpl +++ /dev/null @@ -1,193 +0,0 @@ -{* main menu / page options / breadcrumbs *} - - -{* user menu *} - diff --git a/ts/WoltLabSuite/Core/Ui/Page/Menu/Abstract.ts b/ts/WoltLabSuite/Core/Ui/Page/Menu/Abstract.ts deleted file mode 100644 index 8721682331..0000000000 --- a/ts/WoltLabSuite/Core/Ui/Page/Menu/Abstract.ts +++ /dev/null @@ -1,586 +0,0 @@ -/** - * Provides a touch-friendly fullscreen menu. - * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Ui/Page/Menu/Abstract - */ - -import * as Core from "../../../Core"; -import * as Environment from "../../../Environment"; -import * as EventHandler from "../../../Event/Handler"; -import * as Language from "../../../Language"; -import * as DomTraverse from "../../../Dom/Traverse"; -import * as UiScreen from "../../Screen"; - -const _pageContainer = document.getElementById("pageContainer")!; - -const enum TouchPosition { - AtEdge = 20, - MovedHorizontally = 5, - MovedVertically = 20, -} - -/** - * Which edge of the menu is touched? Empty string - * if no menu is currently touched. - * - * One 'left', 'right' or ''. - */ -let _androidTouching = ""; - -interface ItemData { - itemList: HTMLOListElement; - parentItemList: HTMLOListElement; -} - -abstract class UiPageMenuAbstract { - private readonly activeList: HTMLOListElement[] = []; - protected readonly button: HTMLElement; - private depth = 0; - private enabled = true; - private readonly eventIdentifier: string; - private readonly items = new Map(); - protected readonly menu: HTMLElement; - private removeActiveList = false; - - protected constructor(eventIdentifier: string, elementId: string, buttonSelector: string) { - if (document.body.dataset.template === "packageInstallationSetup") { - // work-around for WCFSetup on mobile - return; - } - - this.eventIdentifier = eventIdentifier; - this.menu = document.getElementById(elementId)!; - - const callbackOpen = this.open.bind(this); - this.button = document.querySelector(buttonSelector) as HTMLElement; - this.button.addEventListener("click", callbackOpen); - - this.initItems(); - this.initHeader(); - - EventHandler.add(this.eventIdentifier, "open", callbackOpen); - EventHandler.add(this.eventIdentifier, "close", this.close.bind(this)); - EventHandler.add(this.eventIdentifier, "updateButtonState", this.updateButtonState.bind(this)); - - this.menu.addEventListener("animationend", () => { - if (!this.menu.classList.contains("open")) { - this.menu.querySelectorAll(".menuOverlayItemList").forEach((itemList) => { - // force the main list to be displayed - itemList.classList.remove("active", "hidden"); - }); - } - }); - - this.menu.children[0].addEventListener("transitionend", () => { - this.menu.classList.add("allowScroll"); - - if (this.removeActiveList) { - this.removeActiveList = false; - - const list = this.activeList.pop(); - if (list) { - list.classList.remove("activeList"); - } - } - }); - - const backdrop = document.createElement("div"); - backdrop.className = "menuOverlayMobileBackdrop"; - backdrop.addEventListener("click", this.close.bind(this)); - - this.menu.insertAdjacentElement("afterend", backdrop); - - this.menu.parentElement!.insertBefore(backdrop, this.menu.nextSibling); - - this.updateButtonState(); - - if (Environment.platform() === "android") { - this.initializeAndroid(); - } - } - - /** - * Opens the menu. - */ - open(event?: MouseEvent): boolean { - if (!this.enabled) { - return false; - } - - if (event instanceof Event) { - event.preventDefault(); - } - - this.menu.classList.add("open"); - this.menu.classList.add("allowScroll"); - this.menu.children[0].classList.add("activeList"); - - UiScreen.scrollDisable(); - - _pageContainer.classList.add("menuOverlay-" + this.menu.id); - - UiScreen.pageOverlayOpen(); - - return true; - } - - /** - * Closes the menu. - */ - close(event?: Event): boolean { - if (event instanceof Event) { - event.preventDefault(); - } - - if (this.menu.classList.contains("open")) { - this.menu.classList.remove("open"); - - UiScreen.scrollEnable(); - UiScreen.pageOverlayClose(); - - _pageContainer.classList.remove("menuOverlay-" + this.menu.id); - - return true; - } - - return false; - } - - /** - * Enables the touch menu. - */ - enable(): void { - this.enabled = true; - } - - /** - * Disables the touch menu. - */ - disable(): void { - this.enabled = false; - - this.close(); - } - - /** - * Initializes the Android Touch Menu. - */ - private initializeAndroid(): void { - // specify on which side of the page the menu appears - let appearsAt: "left" | "right"; - switch (this.menu.id) { - case "pageUserMenuMobile": - appearsAt = "right"; - break; - case "pageMainMenuMobile": - appearsAt = "left"; - break; - default: - return; - } - - const backdrop = this.menu.nextElementSibling as HTMLElement; - - // horizontal position of the touch start - let touchStart: { x: number; y: number } | undefined = undefined; - - document.addEventListener("touchstart", (event) => { - const touches = event.touches; - - let isLeftEdge: boolean; - let isRightEdge: boolean; - - const isOpen = this.menu.classList.contains("open"); - - // check whether we touch the edges of the menu - if (appearsAt === "left") { - isLeftEdge = !isOpen && touches[0].clientX < TouchPosition.AtEdge; - isRightEdge = isOpen && Math.abs(this.menu.offsetWidth - touches[0].clientX) < TouchPosition.AtEdge; - } else { - isLeftEdge = - isOpen && - Math.abs(document.body.clientWidth - this.menu.offsetWidth - touches[0].clientX) < TouchPosition.AtEdge; - isRightEdge = !isOpen && document.body.clientWidth - touches[0].clientX < TouchPosition.AtEdge; - } - - // abort if more than one touch - if (touches.length > 1) { - if (_androidTouching) { - Core.triggerEvent(document, "touchend"); - } - return; - } - - // break if a touch is in progress - if (_androidTouching) { - return; - } - - // break if no edge has been touched - if (!isLeftEdge && !isRightEdge) { - return; - } - - // break if a different menu is open - if (UiScreen.pageOverlayIsActive()) { - const found = _pageContainer.classList.contains(`menuOverlay-${this.menu.id}`); - if (!found) { - return; - } - } - // break if redactor is in use - if (document.documentElement.classList.contains("redactorActive")) { - return; - } - - touchStart = { - x: touches[0].clientX, - y: touches[0].clientY, - }; - - if (isLeftEdge) { - _androidTouching = "left"; - } - if (isRightEdge) { - _androidTouching = "right"; - } - }); - - document.addEventListener("touchend", (event) => { - // break if we did not start a touch - if (!_androidTouching || !touchStart) { - return; - } - - // break if the menu did not even start opening - if (!this.menu.classList.contains("open")) { - // reset - touchStart = undefined; - _androidTouching = ""; - return; - } - - // last known position of the finger - let position: number; - if (event) { - position = event.changedTouches[0].clientX; - } else { - position = touchStart.x; - } - - // clean up touch styles - this.menu.classList.add("androidMenuTouchEnd"); - this.menu.style.removeProperty("transform"); - backdrop.style.removeProperty(appearsAt); - this.menu.addEventListener( - "transitionend", - () => { - this.menu.classList.remove("androidMenuTouchEnd"); - }, - { once: true }, - ); - - // check whether the user moved the finger far enough - if (appearsAt === "left") { - if (_androidTouching === "left" && position < touchStart.x + 100) { - this.close(); - } - if (_androidTouching === "right" && position < touchStart.x - 100) { - this.close(); - } - } else { - if (_androidTouching === "left" && position > touchStart.x + 100) { - this.close(); - } - if (_androidTouching === "right" && position > touchStart.x - 100) { - this.close(); - } - } - - // reset - touchStart = undefined; - _androidTouching = ""; - }); - - document.addEventListener("touchmove", (event) => { - // break if we did not start a touch - if (!_androidTouching || !touchStart) { - return; - } - - const touches = event.touches; - - // check whether the user started moving in the correct direction - // this avoids false positives, in case the user just wanted to tap - let movedFromEdge = false; - if (_androidTouching === "left") { - movedFromEdge = touches[0].clientX > touchStart.x + TouchPosition.MovedHorizontally; - } - if (_androidTouching === "right") { - movedFromEdge = touches[0].clientX < touchStart.x - TouchPosition.MovedHorizontally; - } - - const movedVertically = Math.abs(touches[0].clientY - touchStart.y) > TouchPosition.MovedVertically; - - let isOpen = this.menu.classList.contains("open"); - if (!isOpen && movedFromEdge && !movedVertically) { - // the menu is not yet open, but the user moved into the right direction - this.open(); - isOpen = true; - } - - if (isOpen) { - // update CSS to the new finger position - let position = touches[0].clientX; - if (appearsAt === "right") { - position = document.body.clientWidth - position; - } - if (position > this.menu.offsetWidth) { - position = this.menu.offsetWidth; - } - if (position < 0) { - position = 0; - } - - const offset = (appearsAt === "left" ? 1 : -1) * (position - this.menu.offsetWidth); - this.menu.style.setProperty("transform", `translateX(${offset}px)`); - backdrop.style.setProperty(appearsAt, Math.min(this.menu.offsetWidth, position).toString() + "px"); - } - }); - } - - /** - * Initializes all menu items. - */ - private initItems(): void { - this.menu.querySelectorAll(".menuOverlayItemLink").forEach((element: HTMLAnchorElement) => { - this.initItem(element); - }); - } - - /** - * Initializes a single menu item. - */ - private initItem(item: HTMLAnchorElement): void { - // check if it should contain a 'more' link w/ an external callback - const parent = item.parentElement!; - const more = parent.dataset.more; - if (more) { - item.addEventListener("click", (event) => { - event.preventDefault(); - event.stopPropagation(); - - EventHandler.fire(this.eventIdentifier, "more", { - handler: this, - identifier: more, - item: item, - parent: parent, - }); - }); - - return; - } - - const itemList = item.nextElementSibling as HTMLOListElement; - if (itemList === null) { - return; - } - - // handle static items with an icon-type button next to it (acp menu) - if (itemList.nodeName !== "OL" && itemList.classList.contains("menuOverlayItemLinkIcon")) { - // add wrapper - const wrapper = document.createElement("span"); - wrapper.className = "menuOverlayItemWrapper"; - parent.insertBefore(wrapper, item); - wrapper.appendChild(item); - - while (wrapper.nextElementSibling) { - wrapper.appendChild(wrapper.nextElementSibling); - } - - return; - } - - const isLink = item.getAttribute("href") !== "#"; - const parentItemList = parent.parentElement as HTMLOListElement; - let itemTitle = itemList.dataset.title; - - this.items.set(item, { - itemList: itemList, - parentItemList: parentItemList, - }); - - if (!itemTitle) { - itemTitle = DomTraverse.childByClass(item, "menuOverlayItemTitle")!.textContent!; - itemList.dataset.title = itemTitle; - } - - const callbackLink = this.showItemList.bind(this, item); - if (isLink) { - const wrapper = document.createElement("span"); - wrapper.className = "menuOverlayItemWrapper"; - parent.insertBefore(wrapper, item); - wrapper.appendChild(item); - - const moreLink = document.createElement("a"); - moreLink.href = "#"; - moreLink.className = "menuOverlayItemLinkIcon" + (item.classList.contains("active") ? " active" : ""); - moreLink.innerHTML = ''; - moreLink.addEventListener("click", callbackLink); - wrapper.appendChild(moreLink); - } else { - item.classList.add("menuOverlayItemLinkMore"); - item.addEventListener("click", callbackLink); - } - - const backLinkItem = document.createElement("li"); - backLinkItem.className = "menuOverlayHeader"; - - const wrapper = document.createElement("span"); - wrapper.className = "menuOverlayItemWrapper"; - - const backLink = document.createElement("a"); - backLink.href = "#"; - backLink.className = "menuOverlayItemLink menuOverlayBackLink"; - backLink.textContent = parentItemList.dataset.title || ""; - backLink.addEventListener("click", this.hideItemList.bind(this, item)); - - const closeLink = document.createElement("a"); - closeLink.href = "#"; - closeLink.className = "menuOverlayItemLinkIcon"; - closeLink.innerHTML = ''; - closeLink.addEventListener("click", this.close.bind(this)); - - wrapper.appendChild(backLink); - wrapper.appendChild(closeLink); - backLinkItem.appendChild(wrapper); - - itemList.insertBefore(backLinkItem, itemList.firstElementChild); - - if (!backLinkItem.nextElementSibling!.classList.contains("menuOverlayTitle")) { - const titleItem = document.createElement("li"); - titleItem.className = "menuOverlayTitle"; - const title = document.createElement("span"); - title.textContent = itemTitle; - titleItem.appendChild(title); - - itemList.insertBefore(titleItem, backLinkItem.nextElementSibling); - } - } - - /** - * Renders the menu item list header. - */ - private initHeader(): void { - const listItem = document.createElement("li"); - listItem.className = "menuOverlayHeader"; - - const wrapper = document.createElement("span"); - wrapper.className = "menuOverlayItemWrapper"; - listItem.appendChild(wrapper); - - const logoWrapper = document.createElement("span"); - logoWrapper.className = "menuOverlayLogoWrapper"; - wrapper.appendChild(logoWrapper); - - const logo = document.createElement("span"); - logo.className = "menuOverlayLogo"; - const pageLogo = this.menu.dataset.pageLogo!; - logo.style.setProperty("background-image", `url("${pageLogo}")`, ""); - logoWrapper.appendChild(logo); - - const closeLink = document.createElement("a"); - closeLink.href = "#"; - closeLink.className = "menuOverlayItemLinkIcon"; - closeLink.innerHTML = ''; - closeLink.addEventListener("click", this.close.bind(this)); - wrapper.appendChild(closeLink); - - const list = DomTraverse.childByClass(this.menu, "menuOverlayItemList")!; - list.insertBefore(listItem, list.firstElementChild); - } - - /** - * Hides an item list, return to the parent item list. - */ - private hideItemList(item: HTMLAnchorElement, event: MouseEvent): void { - if (event instanceof Event) { - event.preventDefault(); - } - - this.menu.classList.remove("allowScroll"); - this.removeActiveList = true; - - const data = this.items.get(item)!; - data.parentItemList.classList.remove("hidden"); - - this.updateDepth(false); - } - - /** - * Shows the child item list. - */ - private showItemList(item: HTMLAnchorElement, event: MouseEvent): void { - event.preventDefault(); - - const data = this.items.get(item)!; - - const load = data.itemList.dataset.load; - if (load) { - if (!Core.stringToBool(item.dataset.loaded || "")) { - const target = event.currentTarget as HTMLElement; - const icon = target.firstElementChild!; - if (icon.classList.contains("fa-angle-right")) { - icon.classList.remove("fa-angle-right"); - icon.classList.add("fa-spinner"); - } - - EventHandler.fire(this.eventIdentifier, "load_" + load); - - return; - } - } - - this.menu.classList.remove("allowScroll"); - - data.itemList.classList.add("activeList"); - data.parentItemList.classList.add("hidden"); - - this.activeList.push(data.itemList); - - this.updateDepth(true); - } - - private updateDepth(increase: boolean): void { - this.depth += increase ? 1 : -1; - - let offset = this.depth * -100; - if (Language.get("wcf.global.pageDirection") === "rtl") { - // reverse logic for RTL - offset *= -1; - } - - const child = this.menu.children[0] as HTMLElement; - child.style.setProperty("transform", `translateX(${offset}%)`, ""); - } - - protected updateButtonState(): void { - let hasNewContent = false; - const itemList = this.menu.querySelector(".menuOverlayItemList"); - this.menu.querySelectorAll(".badgeUpdate").forEach((badge) => { - const value = badge.textContent!; - if (~~value > 0 && badge.closest(".menuOverlayItemList") === itemList) { - hasNewContent = true; - } - }); - - this.button.classList[hasNewContent ? "add" : "remove"]("pageMenuMobileButtonHasContent"); - } -} - -Core.enableLegacyInheritance(UiPageMenuAbstract); - -export = UiPageMenuAbstract; diff --git a/wcfsetup/install/files/acp/templates/footer.tpl b/wcfsetup/install/files/acp/templates/footer.tpl index ca15232394..9ddc8ba807 100644 --- a/wcfsetup/install/files/acp/templates/footer.tpl +++ b/wcfsetup/install/files/acp/templates/footer.tpl @@ -6,8 +6,6 @@ {include file='pageFooter'}
-{if $__isRescueMode|empty}{include file='pageMenuMobile'}{/if} - {event name='footer'} diff --git a/wcfsetup/install/files/acp/templates/pageMenuMobile.tpl b/wcfsetup/install/files/acp/templates/pageMenuMobile.tpl deleted file mode 100644 index 1f352acbb3..0000000000 --- a/wcfsetup/install/files/acp/templates/pageMenuMobile.tpl +++ /dev/null @@ -1,87 +0,0 @@ -{if PACKAGE_ID && $__wcf->user->userID} - {* main menu *} - - - {* user menu *} - -{/if} diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Abstract.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Abstract.js deleted file mode 100644 index c6bc0424b6..0000000000 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Abstract.js +++ /dev/null @@ -1,468 +0,0 @@ -/** - * Provides a touch-friendly fullscreen menu. - * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Ui/Page/Menu/Abstract - */ -define(["require", "exports", "tslib", "../../../Core", "../../../Environment", "../../../Event/Handler", "../../../Language", "../../../Dom/Traverse", "../../Screen"], function (require, exports, tslib_1, Core, Environment, EventHandler, Language, DomTraverse, UiScreen) { - "use strict"; - Core = (0, tslib_1.__importStar)(Core); - Environment = (0, tslib_1.__importStar)(Environment); - EventHandler = (0, tslib_1.__importStar)(EventHandler); - Language = (0, tslib_1.__importStar)(Language); - DomTraverse = (0, tslib_1.__importStar)(DomTraverse); - UiScreen = (0, tslib_1.__importStar)(UiScreen); - const _pageContainer = document.getElementById("pageContainer"); - /** - * Which edge of the menu is touched? Empty string - * if no menu is currently touched. - * - * One 'left', 'right' or ''. - */ - let _androidTouching = ""; - class UiPageMenuAbstract { - constructor(eventIdentifier, elementId, buttonSelector) { - this.activeList = []; - this.depth = 0; - this.enabled = true; - this.items = new Map(); - this.removeActiveList = false; - if (document.body.dataset.template === "packageInstallationSetup") { - // work-around for WCFSetup on mobile - return; - } - this.eventIdentifier = eventIdentifier; - this.menu = document.getElementById(elementId); - const callbackOpen = this.open.bind(this); - this.button = document.querySelector(buttonSelector); - this.button.addEventListener("click", callbackOpen); - this.initItems(); - this.initHeader(); - EventHandler.add(this.eventIdentifier, "open", callbackOpen); - EventHandler.add(this.eventIdentifier, "close", this.close.bind(this)); - EventHandler.add(this.eventIdentifier, "updateButtonState", this.updateButtonState.bind(this)); - this.menu.addEventListener("animationend", () => { - if (!this.menu.classList.contains("open")) { - this.menu.querySelectorAll(".menuOverlayItemList").forEach((itemList) => { - // force the main list to be displayed - itemList.classList.remove("active", "hidden"); - }); - } - }); - this.menu.children[0].addEventListener("transitionend", () => { - this.menu.classList.add("allowScroll"); - if (this.removeActiveList) { - this.removeActiveList = false; - const list = this.activeList.pop(); - if (list) { - list.classList.remove("activeList"); - } - } - }); - const backdrop = document.createElement("div"); - backdrop.className = "menuOverlayMobileBackdrop"; - backdrop.addEventListener("click", this.close.bind(this)); - this.menu.insertAdjacentElement("afterend", backdrop); - this.menu.parentElement.insertBefore(backdrop, this.menu.nextSibling); - this.updateButtonState(); - if (Environment.platform() === "android") { - this.initializeAndroid(); - } - } - /** - * Opens the menu. - */ - open(event) { - if (!this.enabled) { - return false; - } - if (event instanceof Event) { - event.preventDefault(); - } - this.menu.classList.add("open"); - this.menu.classList.add("allowScroll"); - this.menu.children[0].classList.add("activeList"); - UiScreen.scrollDisable(); - _pageContainer.classList.add("menuOverlay-" + this.menu.id); - UiScreen.pageOverlayOpen(); - return true; - } - /** - * Closes the menu. - */ - close(event) { - if (event instanceof Event) { - event.preventDefault(); - } - if (this.menu.classList.contains("open")) { - this.menu.classList.remove("open"); - UiScreen.scrollEnable(); - UiScreen.pageOverlayClose(); - _pageContainer.classList.remove("menuOverlay-" + this.menu.id); - return true; - } - return false; - } - /** - * Enables the touch menu. - */ - enable() { - this.enabled = true; - } - /** - * Disables the touch menu. - */ - disable() { - this.enabled = false; - this.close(); - } - /** - * Initializes the Android Touch Menu. - */ - initializeAndroid() { - // specify on which side of the page the menu appears - let appearsAt; - switch (this.menu.id) { - case "pageUserMenuMobile": - appearsAt = "right"; - break; - case "pageMainMenuMobile": - appearsAt = "left"; - break; - default: - return; - } - const backdrop = this.menu.nextElementSibling; - // horizontal position of the touch start - let touchStart = undefined; - document.addEventListener("touchstart", (event) => { - const touches = event.touches; - let isLeftEdge; - let isRightEdge; - const isOpen = this.menu.classList.contains("open"); - // check whether we touch the edges of the menu - if (appearsAt === "left") { - isLeftEdge = !isOpen && touches[0].clientX < 20 /* AtEdge */; - isRightEdge = isOpen && Math.abs(this.menu.offsetWidth - touches[0].clientX) < 20 /* AtEdge */; - } - else { - isLeftEdge = - isOpen && - Math.abs(document.body.clientWidth - this.menu.offsetWidth - touches[0].clientX) < 20 /* AtEdge */; - isRightEdge = !isOpen && document.body.clientWidth - touches[0].clientX < 20 /* AtEdge */; - } - // abort if more than one touch - if (touches.length > 1) { - if (_androidTouching) { - Core.triggerEvent(document, "touchend"); - } - return; - } - // break if a touch is in progress - if (_androidTouching) { - return; - } - // break if no edge has been touched - if (!isLeftEdge && !isRightEdge) { - return; - } - // break if a different menu is open - if (UiScreen.pageOverlayIsActive()) { - const found = _pageContainer.classList.contains(`menuOverlay-${this.menu.id}`); - if (!found) { - return; - } - } - // break if redactor is in use - if (document.documentElement.classList.contains("redactorActive")) { - return; - } - touchStart = { - x: touches[0].clientX, - y: touches[0].clientY, - }; - if (isLeftEdge) { - _androidTouching = "left"; - } - if (isRightEdge) { - _androidTouching = "right"; - } - }); - document.addEventListener("touchend", (event) => { - // break if we did not start a touch - if (!_androidTouching || !touchStart) { - return; - } - // break if the menu did not even start opening - if (!this.menu.classList.contains("open")) { - // reset - touchStart = undefined; - _androidTouching = ""; - return; - } - // last known position of the finger - let position; - if (event) { - position = event.changedTouches[0].clientX; - } - else { - position = touchStart.x; - } - // clean up touch styles - this.menu.classList.add("androidMenuTouchEnd"); - this.menu.style.removeProperty("transform"); - backdrop.style.removeProperty(appearsAt); - this.menu.addEventListener("transitionend", () => { - this.menu.classList.remove("androidMenuTouchEnd"); - }, { once: true }); - // check whether the user moved the finger far enough - if (appearsAt === "left") { - if (_androidTouching === "left" && position < touchStart.x + 100) { - this.close(); - } - if (_androidTouching === "right" && position < touchStart.x - 100) { - this.close(); - } - } - else { - if (_androidTouching === "left" && position > touchStart.x + 100) { - this.close(); - } - if (_androidTouching === "right" && position > touchStart.x - 100) { - this.close(); - } - } - // reset - touchStart = undefined; - _androidTouching = ""; - }); - document.addEventListener("touchmove", (event) => { - // break if we did not start a touch - if (!_androidTouching || !touchStart) { - return; - } - const touches = event.touches; - // check whether the user started moving in the correct direction - // this avoids false positives, in case the user just wanted to tap - let movedFromEdge = false; - if (_androidTouching === "left") { - movedFromEdge = touches[0].clientX > touchStart.x + 5 /* MovedHorizontally */; - } - if (_androidTouching === "right") { - movedFromEdge = touches[0].clientX < touchStart.x - 5 /* MovedHorizontally */; - } - const movedVertically = Math.abs(touches[0].clientY - touchStart.y) > 20 /* MovedVertically */; - let isOpen = this.menu.classList.contains("open"); - if (!isOpen && movedFromEdge && !movedVertically) { - // the menu is not yet open, but the user moved into the right direction - this.open(); - isOpen = true; - } - if (isOpen) { - // update CSS to the new finger position - let position = touches[0].clientX; - if (appearsAt === "right") { - position = document.body.clientWidth - position; - } - if (position > this.menu.offsetWidth) { - position = this.menu.offsetWidth; - } - if (position < 0) { - position = 0; - } - const offset = (appearsAt === "left" ? 1 : -1) * (position - this.menu.offsetWidth); - this.menu.style.setProperty("transform", `translateX(${offset}px)`); - backdrop.style.setProperty(appearsAt, Math.min(this.menu.offsetWidth, position).toString() + "px"); - } - }); - } - /** - * Initializes all menu items. - */ - initItems() { - this.menu.querySelectorAll(".menuOverlayItemLink").forEach((element) => { - this.initItem(element); - }); - } - /** - * Initializes a single menu item. - */ - initItem(item) { - // check if it should contain a 'more' link w/ an external callback - const parent = item.parentElement; - const more = parent.dataset.more; - if (more) { - item.addEventListener("click", (event) => { - event.preventDefault(); - event.stopPropagation(); - EventHandler.fire(this.eventIdentifier, "more", { - handler: this, - identifier: more, - item: item, - parent: parent, - }); - }); - return; - } - const itemList = item.nextElementSibling; - if (itemList === null) { - return; - } - // handle static items with an icon-type button next to it (acp menu) - if (itemList.nodeName !== "OL" && itemList.classList.contains("menuOverlayItemLinkIcon")) { - // add wrapper - const wrapper = document.createElement("span"); - wrapper.className = "menuOverlayItemWrapper"; - parent.insertBefore(wrapper, item); - wrapper.appendChild(item); - while (wrapper.nextElementSibling) { - wrapper.appendChild(wrapper.nextElementSibling); - } - return; - } - const isLink = item.getAttribute("href") !== "#"; - const parentItemList = parent.parentElement; - let itemTitle = itemList.dataset.title; - this.items.set(item, { - itemList: itemList, - parentItemList: parentItemList, - }); - if (!itemTitle) { - itemTitle = DomTraverse.childByClass(item, "menuOverlayItemTitle").textContent; - itemList.dataset.title = itemTitle; - } - const callbackLink = this.showItemList.bind(this, item); - if (isLink) { - const wrapper = document.createElement("span"); - wrapper.className = "menuOverlayItemWrapper"; - parent.insertBefore(wrapper, item); - wrapper.appendChild(item); - const moreLink = document.createElement("a"); - moreLink.href = "#"; - moreLink.className = "menuOverlayItemLinkIcon" + (item.classList.contains("active") ? " active" : ""); - moreLink.innerHTML = ''; - moreLink.addEventListener("click", callbackLink); - wrapper.appendChild(moreLink); - } - else { - item.classList.add("menuOverlayItemLinkMore"); - item.addEventListener("click", callbackLink); - } - const backLinkItem = document.createElement("li"); - backLinkItem.className = "menuOverlayHeader"; - const wrapper = document.createElement("span"); - wrapper.className = "menuOverlayItemWrapper"; - const backLink = document.createElement("a"); - backLink.href = "#"; - backLink.className = "menuOverlayItemLink menuOverlayBackLink"; - backLink.textContent = parentItemList.dataset.title || ""; - backLink.addEventListener("click", this.hideItemList.bind(this, item)); - const closeLink = document.createElement("a"); - closeLink.href = "#"; - closeLink.className = "menuOverlayItemLinkIcon"; - closeLink.innerHTML = ''; - closeLink.addEventListener("click", this.close.bind(this)); - wrapper.appendChild(backLink); - wrapper.appendChild(closeLink); - backLinkItem.appendChild(wrapper); - itemList.insertBefore(backLinkItem, itemList.firstElementChild); - if (!backLinkItem.nextElementSibling.classList.contains("menuOverlayTitle")) { - const titleItem = document.createElement("li"); - titleItem.className = "menuOverlayTitle"; - const title = document.createElement("span"); - title.textContent = itemTitle; - titleItem.appendChild(title); - itemList.insertBefore(titleItem, backLinkItem.nextElementSibling); - } - } - /** - * Renders the menu item list header. - */ - initHeader() { - const listItem = document.createElement("li"); - listItem.className = "menuOverlayHeader"; - const wrapper = document.createElement("span"); - wrapper.className = "menuOverlayItemWrapper"; - listItem.appendChild(wrapper); - const logoWrapper = document.createElement("span"); - logoWrapper.className = "menuOverlayLogoWrapper"; - wrapper.appendChild(logoWrapper); - const logo = document.createElement("span"); - logo.className = "menuOverlayLogo"; - const pageLogo = this.menu.dataset.pageLogo; - logo.style.setProperty("background-image", `url("${pageLogo}")`, ""); - logoWrapper.appendChild(logo); - const closeLink = document.createElement("a"); - closeLink.href = "#"; - closeLink.className = "menuOverlayItemLinkIcon"; - closeLink.innerHTML = ''; - closeLink.addEventListener("click", this.close.bind(this)); - wrapper.appendChild(closeLink); - const list = DomTraverse.childByClass(this.menu, "menuOverlayItemList"); - list.insertBefore(listItem, list.firstElementChild); - } - /** - * Hides an item list, return to the parent item list. - */ - hideItemList(item, event) { - if (event instanceof Event) { - event.preventDefault(); - } - this.menu.classList.remove("allowScroll"); - this.removeActiveList = true; - const data = this.items.get(item); - data.parentItemList.classList.remove("hidden"); - this.updateDepth(false); - } - /** - * Shows the child item list. - */ - showItemList(item, event) { - event.preventDefault(); - const data = this.items.get(item); - const load = data.itemList.dataset.load; - if (load) { - if (!Core.stringToBool(item.dataset.loaded || "")) { - const target = event.currentTarget; - const icon = target.firstElementChild; - if (icon.classList.contains("fa-angle-right")) { - icon.classList.remove("fa-angle-right"); - icon.classList.add("fa-spinner"); - } - EventHandler.fire(this.eventIdentifier, "load_" + load); - return; - } - } - this.menu.classList.remove("allowScroll"); - data.itemList.classList.add("activeList"); - data.parentItemList.classList.add("hidden"); - this.activeList.push(data.itemList); - this.updateDepth(true); - } - updateDepth(increase) { - this.depth += increase ? 1 : -1; - let offset = this.depth * -100; - if (Language.get("wcf.global.pageDirection") === "rtl") { - // reverse logic for RTL - offset *= -1; - } - const child = this.menu.children[0]; - child.style.setProperty("transform", `translateX(${offset}%)`, ""); - } - updateButtonState() { - let hasNewContent = false; - const itemList = this.menu.querySelector(".menuOverlayItemList"); - this.menu.querySelectorAll(".badgeUpdate").forEach((badge) => { - const value = badge.textContent; - if (~~value > 0 && badge.closest(".menuOverlayItemList") === itemList) { - hasNewContent = true; - } - }); - this.button.classList[hasNewContent ? "add" : "remove"]("pageMenuMobileButtonHasContent"); - } - } - Core.enableLegacyInheritance(UiPageMenuAbstract); - return UiPageMenuAbstract; -}); diff --git a/wcfsetup/install/files/style/ui/menuMobile.scss b/wcfsetup/install/files/style/ui/menuMobile.scss deleted file mode 100644 index 85f4de50e3..0000000000 --- a/wcfsetup/install/files/style/ui/menuMobile.scss +++ /dev/null @@ -1,317 +0,0 @@ -/* menu container */ -.menuOverlayMobile { - background-color: $wcfHeaderMenuBackground; - bottom: 0; - display: none; - overflow: hidden; - position: absolute; - top: 0; - z-index: 320; - - &.open { - display: block; - - @include screen-sm-up { - + .menuOverlayMobileBackdrop { - display: block; - } - } - } - - &.androidMenuTouchEnd { - display: block; - position: fixed; - transition: transform 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94); - - &.pageMainMenuMobile:not(.open) { - transform: translateX(-100vw); - } - &.pageUserMenuMobile:not(.open) { - transform: translateX(100vw); - } - - @include screen-sm-up { - &.pageMainMenuMobile:not(.open) { - transform: translateX(-350px); - } - &.pageUserMenuMobile:not(.open) { - transform: translateX(350px); - } - - + .menuOverlayMobileBackdrop { - transition: left 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94), - right 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94); - } - } - } - - > .menuOverlayItemList { - // we use `transform: translateX()` for performance reasons - transition: transform 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94); - - /* work-around to avoid setting explicit visibility */ - visibility: visible; - } - - &.allowScroll { - .menuOverlayItemList:not(.hidden) { - overflow: auto; - -webkit-overflow-scrolling: touch; - } - } - - &:not(.allowScroll) { - // block UI while switching between menus - &::before { - bottom: 0; - content: ""; - left: 0; - position: absolute; - right: 0; - top: 0; - z-index: 500; - } - } - - @include screen-xs { - left: 0; - max-width: 100vw; - right: 0; - - .menuOverlayItemList { - right: 0; - } - } - - @include screen-sm-up { - width: 350px; - - &.pageMainMenuMobile { - left: 0; - - & + .menuOverlayMobileBackdrop { - box-shadow: inset 5px 0 10px -5px rgba(0, 0, 0, 0.6); - left: 350px; - } - - .menuOverlayItemList { - left: 0; - } - } - - &.pageUserMenuMobile { - right: 0; - - & + .menuOverlayMobileBackdrop { - box-shadow: inset -5px 0 10px -5px rgba(0, 0, 0, 0.6); - right: 350px; - } - - .menuOverlayItemList { - right: 0; - } - } - } -} - -.menuOverlayMobileBackdrop { - background-color: rgba(0, 0, 0, 0.4); - bottom: 0; - display: none; - left: 0; - position: fixed; - right: 0; - top: 0; - z-index: 395; -} - -.menuOverlayItemWrapper { - display: flex; - justify-content: flex-end; - - > .menuOverlayItemLink { - flex: 1 1 auto; - } -} - -.menuOverlayItemList { - background-color: $wcfHeaderMenuBackground; - height: 100%; - list-style-type: none; - margin: 0; - padding: 5px 0; - position: absolute; - top: 0; - left: -1px; - bottom: 0; - width: calc(100vw + 1px); - z-index: 450; - - @include screen-sm-up { - width: 350px; - } - - &:not(.activeList) { - display: none; - } -} - -.menuOverlayItemSpacer { - margin-top: 25px; - - /* avoid successive spacers piling up */ - & + .menuOverlayItemSpacer { - display: none; - } -} - -.menuOverlayItem { - &:not(:last-child) { - margin-bottom: 1px; - } - - /* nested item list */ - > .menuOverlayItemList { - margin-left: 100%; - z-index: 500; - } -} - -.menuOverlayItemLink, -.menuOverlayTitle, -.menuOverlayBackLink { - color: $wcfHeaderMenuLink; - display: block; - font-size: 14px; - padding: 10px 30px; - position: relative; -} - -.menuOverlayItemLink { - align-items: center; - background-color: $wcfHeaderMenuLinkBackground; - display: flex; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - - .icon::before { - color: $wcfHeaderMenuLink; - } - - &:hover { - color: $wcfHeaderMenuLinkActive; - } - - /* wrapper class for links containing an additional badge */ - &.menuOverlayItemBadge { - align-items: center; - display: flex; - padding-right: 10px; - - /* different padding if there is no additional icon after the link, - ensures proper alignment for links with badges containing a child - item list */ - &:last-child { - /* 55px = 10px padding + 1px margin + icon */ - /* icon = 2x 10px padding + 16px width */ - padding-right: 55px; - } - - > .menuOverlayItemTitle { - flex: 1 1 auto; - } - - > .badge { - flex: 0 0 auto; - } - } - - &.menuOverlayItemLinkMore::after { - color: $wcfHeaderMenuLink; - content: $fa-var-angle-right; - display: block; - font-family: FontAwesome; - font-size: 18px; - position: absolute; - right: 18px; - top: 50%; - transform: translateY(-50%); - } - - .menuOverlayItemTitle { - overflow: hidden; - text-overflow: ellipsis; - } -} - -/* fix icons in rtl design */ -html[dir="rtl"] .menuOverlayItemLink.menuOverlayItemLinkMore::after { - content: $fa-var-angle-left; -} - -.pageUserMenuMobile .menuOverlayItemBadge:last-child { - padding-right: 10px !important; -} - -.menuOverlayItemLink.active, -.menuOverlayItemLinkIcon.active { - background-color: $wcfHeaderMenuLinkBackgroundActive; - color: $wcfHeaderMenuLinkActive; -} - -.menuOverlayItemLinkIcon.active > .icon::before { - color: $wcfHeaderMenuLinkActive; -} - -.menuOverlayTitle { - color: rgba($wcfHeaderMenuLink, 0.7); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - - &:not(:first-child) { - margin-top: 10px; - } -} - -/* icon link sharing the space with a link or (header only) the logo */ -.menuOverlayItemLinkIcon { - background-color: $wcfHeaderMenuLinkBackground; - flex: 0 0 auto; - margin-left: 1px; - padding: 10px; - - /* force explicit dimensions because no each .icon24 is of equal height/width */ - height: 44px; - width: 44px; - - > .icon::before { - color: $wcfHeaderMenuLink; - } -} - -.menuOverlayBackLink::before { - color: rgba($wcfHeaderMenuLink, 0.7); - content: $fa-var-angle-left; - display: block; - font-family: FontAwesome; - font-size: 18px; - position: absolute; - left: 12px; - top: 50%; - transform: translateY(-50%); -} - -.menuOverlayLogoWrapper { - flex: 1 1 auto; - padding: 5px; - display: flex; - - .menuOverlayLogo { - flex: 1 1 auto; - background-size: contain; - background-repeat: no-repeat; - background-position: center; - } -}