From 8e81116ba159161d6a986b0ad89dcbf9c7346b3b Mon Sep 17 00:00:00 2001 From: srgooglo Date: Wed, 12 Oct 2022 00:00:38 +0200 Subject: [PATCH] implement `ContextMenu` core --- .../components/contextMenu/index.jsx | 53 ++++++++ .../components/contextMenu/index.less | 80 +++++++++++ packages/app/src/cores/contextMenu/index.js | 124 ++++++++++++++++++ packages/app/src/cores/index.js | 2 + 4 files changed, 259 insertions(+) create mode 100644 packages/app/src/cores/contextMenu/components/contextMenu/index.jsx create mode 100644 packages/app/src/cores/contextMenu/components/contextMenu/index.less create mode 100644 packages/app/src/cores/contextMenu/index.js diff --git a/packages/app/src/cores/contextMenu/components/contextMenu/index.jsx b/packages/app/src/cores/contextMenu/components/contextMenu/index.jsx new file mode 100644 index 00000000..5f64d9a7 --- /dev/null +++ b/packages/app/src/cores/contextMenu/components/contextMenu/index.jsx @@ -0,0 +1,53 @@ +import React from "react" + +import { createIconRender } from "components/Icons" + +import "./index.less" + +export default (props) => { + const { items = [], cords, clickedComponent } = props + + const handleItemClick = async (item) => { + if (typeof item.action === "function") { + await item.action(clickedComponent) + } + } + + const renderItems = () => { + if (items.length === 0) { + return
+

No items

+
+ } + + return items.map((item, index) => { + if (item.type === "separator") { + return
+ } + + return
handleItemClick(item)} + className="item" + > +

+ {item.label} +

+ {item.description &&

+ {item.description} +

} + {createIconRender(item.icon)} +
+ }) + } + + return
+ {renderItems()} +
+} \ No newline at end of file diff --git a/packages/app/src/cores/contextMenu/components/contextMenu/index.less b/packages/app/src/cores/contextMenu/components/contextMenu/index.less new file mode 100644 index 00000000..21607e0f --- /dev/null +++ b/packages/app/src/cores/contextMenu/components/contextMenu/index.less @@ -0,0 +1,80 @@ +.contextMenu { + position: absolute; + z-index: 100000; + + display: flex; + flex-direction: column; + + top: 0; + left: 0; + + width: 200px; + height: fit-content; + + background-color: var(--background-color-primary); + border: 1px solid var(--border-color); + + border-radius: 8px; + + padding: 10px; + + font-family: "Recursive", sans-serif; + font-size: 0.8rem; + font-weight: 500; + color: var(--text-color); + + h1, + h2, + h3, + h4, + h5, + h6, + p, + span { + margin: 0; + word-break: break-all; + } + + .item { + position: relative; + display: inline-flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + + width: 100%; + + padding: 5px 10px 5px 20px; + + transition: all 50ms ease-in-out; + + border-radius: 8px; + + overflow: hidden; + + &:hover { + background-color: var(--background-color-accent); + padding-left: 25px; + } + + &:active { + background-color: var(--background-color-primary2); + transform: scale(0.95); + } + } + + .context-menu-separator { + position: relative; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + width: 100%; + height: 1px; + + margin: 10px 0; + + background-color: var(--border-color); + } +} \ No newline at end of file diff --git a/packages/app/src/cores/contextMenu/index.js b/packages/app/src/cores/contextMenu/index.js new file mode 100644 index 00000000..dcb336d9 --- /dev/null +++ b/packages/app/src/cores/contextMenu/index.js @@ -0,0 +1,124 @@ +import React from "react" +import Core from "evite/src/core" + +import { DOMWindow } from "components/RenderWindow" +import ContextMenu from "./components/contextMenu" + +import Contexts from "schemas/menu-contexts" + +export default class ContextMenuCore extends Core { + static namespace = "ContextMenu" + static public = ["show", "hide", "registerContext"] + + contexts = Object() + defaultContext = [ + { + label: "Report a bug", + icon: "AlertTriangle", + action: (parent, element) => { + app.eventBus.emit("app.reportBug", { + parent, + element + }) + } + } + ] + + DOMWindow = new DOMWindow({ + id: "contextMenu", + className: "contextMenuWrapper", + clickOutsideToClose: true, + }) + + async initialize() { + document.addEventListener("contextmenu", this.handleEvent) + } + + registerContext = (element, context) => { + this.contexts[element] = context + } + + generateItems = (element) => { + let items = [] + + // find the closest context with attribute (context-menu) + // if not found, use default context + const parentElement = element.closest("[context-menu]") + + if (parentElement) { + const context = parentElement.getAttribute("context-menu") + + // if context is not registered, try to fetch it from the constants contexts object + if (!this.contexts[context]) { + items = Contexts[context] || [] + } else { + items = this.contexts[context] ?? [] + } + + if (typeof items === "function") { + items = items( + parentElement, + element, + { + close: this.hide + } + ) + } + } + + // fullfill each item with a correspondent index if missing declared + items = items.map((item, index) => { + if (!item.index) { + item.index = index + } + + return item + }) + + // short items (if has declared index) + items = items.sort((a, b) => a.index - b.index) + + if (items.length > 0) { + items.push({ + type: "separator" + }) + } + + items.push(...this.defaultContext) + + return items + } + + handleEvent = (event) => { + event.preventDefault() + + // get the cords of the mouse + const x = event.clientX + const y = event.clientY + + // get the component that was clicked + const component = document.elementFromPoint(x, y) + + // check if is clicking inside a context menu or a children inside a context menu + if (component.classList.contains("contextMenu") || component.closest(".contextMenu")) { + return + } + + this.show({ + cords: { + x, + y, + }, + clickedComponent: component, + items: this.generateItems(component), + }) + } + + show = (payload) => { + this.DOMWindow.render(React.createElement(ContextMenu, payload)) + } + + hide = () => { + this.DOMWindow.remove() + } +} \ No newline at end of file diff --git a/packages/app/src/cores/index.js b/packages/app/src/cores/index.js index eb90b750..493b78be 100644 --- a/packages/app/src/cores/index.js +++ b/packages/app/src/cores/index.js @@ -3,6 +3,7 @@ import APICore from "./api" import StyleCore from "./style" import PermissionsCore from "./permissions" import SearchCore from "./search" +import ContextMenuCore from "./contextMenu" import I18nCore from "./i18n" import NotificationsCore from "./notifications" @@ -24,4 +25,5 @@ export default [ ShortcutsCore, AudioPlayer, + ContextMenuCore, ] \ No newline at end of file