implement ContextMenu core

This commit is contained in:
srgooglo 2022-10-12 00:00:38 +02:00
parent f39952baa6
commit 8e81116ba1
4 changed files with 259 additions and 0 deletions

View File

@ -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 <div>
<p>No items</p>
</div>
}
return items.map((item, index) => {
if (item.type === "separator") {
return <div key={index} className="context-menu-separator" />
}
return <div
key={index}
onClick={() => handleItemClick(item)}
className="item"
>
<p className="label">
{item.label}
</p>
{item.description && <p className="description">
{item.description}
</p>}
{createIconRender(item.icon)}
</div>
})
}
return <div
className="contextMenu"
style={{
top: cords.y,
left: cords.x,
}}
>
{renderItems()}
</div>
}

View File

@ -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);
}
}

View File

@ -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()
}
}

View File

@ -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,
]