diff --git a/packages/app/src/cores/contextMenu/components/contextMenu/index.jsx b/packages/app/src/cores/contextMenu/components/contextMenu/index.jsx
index 53fdff78..6c99f413 100755
--- a/packages/app/src/cores/contextMenu/components/contextMenu/index.jsx
+++ b/packages/app/src/cores/contextMenu/components/contextMenu/index.jsx
@@ -1,12 +1,23 @@
import React from "react"
import { createIconRender } from "@components/Icons"
+import { AnimatePresence, motion } from "framer-motion"
import "./index.less"
-export default (props) => {
+const ContextMenu = (props) => {
+ const [visible, setVisible] = React.useState(true)
const { items = [], cords, clickedComponent, ctx } = props
+ async function onClose() {
+ setVisible(false)
+ props.unregisterOnClose(onClose)
+ }
+
+ React.useEffect(() => {
+ props.registerOnClose(onClose)
+ }, [])
+
const handleItemClick = async (item) => {
if (typeof item.action === "function") {
await item.action(clickedComponent, ctx)
@@ -33,21 +44,43 @@ export default (props) => {
{item.label}
- {item.description &&
- {item.description}
-
}
- {createIconRender(item.icon)}
+
+ {
+ item.description &&
+ {item.description}
+
+ }
+
+ {
+ createIconRender(item.icon)
+ }
})
}
- return
- {renderItems()}
-
-}
\ No newline at end of file
+ return
+ {
+ visible &&
+
+ {
+ renderItems()
+ }
+
+
+ }
+
+}
+
+export default ContextMenu
\ 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
index eab8f613..d4c04735 100755
--- a/packages/app/src/cores/contextMenu/components/contextMenu/index.less
+++ b/packages/app/src/cores/contextMenu/components/contextMenu/index.less
@@ -1,15 +1,21 @@
@import "@styles/vars.less";
-.contextMenu {
+.context-menu-wrapper {
position: fixed;
z-index: 100000;
- display: flex;
- flex-direction: column;
-
top: 0;
left: 0;
+ transition: all 150ms ease-in-out;
+}
+
+.context-menu {
+ position: relative;
+
+ display: flex;
+ flex-direction: column;
+
width: 230px;
height: fit-content;
diff --git a/packages/app/src/cores/contextMenu/context_menu.core.js b/packages/app/src/cores/contextMenu/context_menu.core.js
index f618a295..9b160e63 100755
--- a/packages/app/src/cores/contextMenu/context_menu.core.js
+++ b/packages/app/src/cores/contextMenu/context_menu.core.js
@@ -1,5 +1,6 @@
import React from "react"
import Core from "evite/src/core"
+import EventEmitter from "evite/src/internals/EventEmitter"
import ContextMenu from "./components/contextMenu"
@@ -9,17 +10,13 @@ import PostCardContext from "@config/context-menu/post"
export default class ContextMenuCore extends Core {
static namespace = "contextMenu"
- public = {
- show: this.show.bind(this),
- hide: this.hide.bind(this),
- registerContext: this.registerContext.bind(this),
- }
-
contexts = {
...DefaultContenxt,
...PostCardContext,
}
+ eventBus = new EventEmitter()
+
async onInitialize() {
if (app.isMobile) {
this.console.warn("Context menu is not available on mobile")
@@ -29,11 +26,48 @@ export default class ContextMenuCore extends Core {
document.addEventListener("contextmenu", this.handleEvent.bind(this))
}
- registerContext(element, context) {
+ async 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
+ }
+
+ const items = await this.generateItems(component)
+
+ if (!items) {
+ this.console.warn("No context menu items found, aborting")
+ return false
+ }
+
+ this.show({
+ registerOnClose: (cb) => { this.eventBus.on("close", cb) },
+ unregisterOnClose: (cb) => { this.eventBus.off("close", cb) },
+ cords: {
+ x,
+ y,
+ },
+ clickedComponent: component,
+ items: items,
+ ctx: {
+ close: this.onClose,
+ }
+ })
+ }
+
+ registerContext = async (element, context) => {
this.contexts[element] = context
}
- async generateItems(element) {
+ generateItems = async (element) => {
let items = []
// find the closest context with attribute (context-menu)
@@ -72,7 +106,7 @@ export default class ContextMenuCore extends Core {
if (typeof contextObject === "function") {
contextObject = await contextObject(items, parentElement, element, {
- close: this.hide,
+ close: this.onClose,
})
}
@@ -102,49 +136,23 @@ export default class ContextMenuCore extends Core {
return items
}
- async 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
- }
-
- const items = await this.generateItems(component)
-
- if (!items) {
- this.console.warn("No context menu items found, aborting")
- return false
- }
-
- this.show({
- cords: {
- x,
- y,
+ show = async (payload) => {
+ app.cores.window_mng.render(
+ "context-menu-portal",
+ React.createElement(ContextMenu, payload),
+ {
+ onClose: this.onClose,
+ createOrUpdate: true,
+ closeOnClickOutside: true,
},
- clickedComponent: component,
- items: items,
- ctx: {
- close: this.hide.bind(this),
- }
- })
+ )
}
- show(payload) {
- app.cores.window_mng.render("context-menu", React.createElement(ContextMenu, payload), {
- createOrUpdate: true,
- closeOnClickOutside: true,
- })
- }
+ onClose = async (delay = 200) => {
+ this.eventBus.emit("close", delay)
- hide() {
- app.cores.window_mng.close("context-menu")
+ await new Promise((resolve) => {
+ setTimeout(resolve, delay)
+ })
}
}
\ No newline at end of file
diff --git a/packages/app/src/cores/windows/windows.core.jsx b/packages/app/src/cores/windows/windows.core.jsx
index 5512a974..b9d2a1f8 100644
--- a/packages/app/src/cores/windows/windows.core.jsx
+++ b/packages/app/src/cores/windows/windows.core.jsx
@@ -5,8 +5,6 @@ import { createRoot } from "react-dom/client"
import DefaultWindow from "./components/defaultWindow"
-import DefaultWindowContext from "./components/defaultWindow/context"
-
import "./index.less"
export default class WindowManager extends Core {
@@ -15,7 +13,6 @@ export default class WindowManager extends Core {
static idMount = "windows"
root = null
-
windows = []
public = {
@@ -54,11 +51,12 @@ export default class WindowManager extends Core {
* @param {boolean} options.createOrUpdate - Specifies whether to create a new element or update an existing one.
* @return {HTMLElement} The created or updated element.
*/
- render(
+ async render(
id,
fragment,
{
useFrame = false,
+ onClose = null,
createOrUpdate = false,
closeOnClickOutside = false,
} = {}
@@ -68,6 +66,7 @@ export default class WindowManager extends Core {
let win = null
// check if window already exist
+ // if exist, try to automatically generate a new id
if (this.root.querySelector(`#${id}`) && !createOrUpdate) {
const newId = `${id}_${Date.now()}`
@@ -76,6 +75,17 @@ export default class WindowManager extends Core {
id = newId
}
+ // if closeOnClickOutside is true, add click event listener
+ if (closeOnClickOutside === true) {
+ document.addEventListener(
+ "click",
+ (e) => this.handleWrapperClick(id, e),
+ { once: true },
+ )
+ }
+
+ // check if window already exist, if exist and createOrUpdate is true, update the element
+ // if not exist, create a new element
if (this.root.querySelector(`#${id}`) && createOrUpdate) {
element = document.getElementById(id)
@@ -96,24 +106,23 @@ export default class WindowManager extends Core {
win = {
id: id,
node: node,
+ onClose: onClose,
+ closeOnClickOutside: closeOnClickOutside,
}
this.windows.push(win)
}
+ // if useFrame is true, wrap the fragment with a DefaultWindow component
if (useFrame) {
fragment =
{fragment}
}
- if (closeOnClickOutside) {
- document.addEventListener("click", (e) => this.handleWrapperClick(id, e))
- }
-
node.render(React.cloneElement(fragment, {
close: () => {
- this.close(id)
+ this.close(id, onClose)
}
}))
@@ -129,26 +138,38 @@ export default class WindowManager extends Core {
* @param {string} id - The ID of the window to be closed.
* @return {boolean} Returns true if the window was successfully closed, false otherwise.
*/
- close(id) {
+ async close(id) {
const element = document.getElementById(id)
const win = this.windows.find((node) => {
return node.id === id
})
- if (!win) {
- this.console.warn(`Window ${id} not found`)
-
+ if (!win || !element) {
+ this.console.error(`Window [${id}] not found`)
return false
}
+ this.console.debug(`Closing window ${id}`, win, element)
+
+ // if onClose callback is defined, call it
+ if (typeof win.onClose === "function") {
+ this.console.debug(`Trigging close callback for window ${id}`)
+ await win.onClose()
+ }
+
+ // remove the element from the DOM
+ this.console.debug(`Removing element from DOM for window ${id}`)
win.node.unmount()
this.root.removeChild(element)
+ // remove the window from the list
+ this.console.debug(`Removing window from list for window ${id}`)
this.windows = this.windows.filter((node) => {
return node.id !== id
})
+ this.console.debug(`Window ${id} closed`)
return true
}
}
\ No newline at end of file