diff --git a/live/src/core/hocuspocus-server.ts b/live/src/core/hocuspocus-server.ts index 1e327e5e58e..ca2e4df0e6d 100644 --- a/live/src/core/hocuspocus-server.ts +++ b/live/src/core/hocuspocus-server.ts @@ -5,8 +5,8 @@ import { handleAuthentication } from "@/core/lib/authentication.js"; // extensions import { getExtensions } from "@/core/extensions/index.js"; import { + DocumentEventResponses, DocumentEventsServer, - documentEventResponses, } from "@plane/editor/lib"; export const getHocusPocusServer = async () => { @@ -42,7 +42,7 @@ export const getHocusPocusServer = async () => { } }, async onStateless({ payload, document }) { - const response = documentEventResponses[payload as DocumentEventsServer]; + const response = DocumentEventResponses[payload as DocumentEventsServer]; if (response) { document.broadcastStateless(response); } diff --git a/packages/editor/src/core/helpers/document-events.ts b/packages/editor/src/core/helpers/document-events.ts index 5746106f822..5ef356b8ac6 100644 --- a/packages/editor/src/core/helpers/document-events.ts +++ b/packages/editor/src/core/helpers/document-events.ts @@ -1,9 +1,9 @@ -export const documentEventResponses = { - lock: "locked", - unlock: "unlocked", - archive: "archived", - unarchive: "unarchived", -} as const; +export enum DocumentEventResponses { + Lock = "locked", + Unlock = "unlocked", + Archive = "archived", + Unarchive = "unarchived", +} -export type DocumentEventsServer = keyof typeof documentEventResponses; -export type DocumentEventsClient = (typeof documentEventResponses)[DocumentEventsServer]; +export type DocumentEventsServer = keyof typeof DocumentEventResponses; +export type DocumentEventsClient = (typeof DocumentEventResponses)[DocumentEventsServer]; diff --git a/packages/ui/src/icons/index.ts b/packages/ui/src/icons/index.ts index 660768845b3..584a6c3ddf5 100644 --- a/packages/ui/src/icons/index.ts +++ b/packages/ui/src/icons/index.ts @@ -28,3 +28,5 @@ export * from "./dropdown-icon"; export * from "./intake"; export * from "./user-activity-icon"; export * from "./favorite-folder-icon"; +// types +export type { ISvgIcons } from "./type"; diff --git a/web/core/components/pages/editor/header/options-dropdown.tsx b/web/core/components/pages/editor/header/options-dropdown.tsx index 79e7ede49d4..abd6815a3ec 100644 --- a/web/core/components/pages/editor/header/options-dropdown.tsx +++ b/web/core/components/pages/editor/header/options-dropdown.tsx @@ -1,14 +1,14 @@ "use client"; +import { useState, useEffect, useCallback } from "react"; import { observer } from "mobx-react"; import { useParams, useRouter } from "next/navigation"; -import { useState, useEffect } from "react"; -import { ArchiveRestoreIcon, Clipboard, Copy, History, Link, Lock, LockOpen } from "lucide-react"; +import { ArchiveRestoreIcon, Clipboard, Copy, History, Link, Lock, LockOpen, LucideIcon } from "lucide-react"; // document editor import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor"; import { DocumentEventsClient } from "@plane/editor/lib"; // ui -import { ArchiveIcon, CustomMenu, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui"; +import { ArchiveIcon, CustomMenu, ISvgIcons, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui"; // helpers import { copyTextToClipboard, copyUrlToClipboard } from "@/helpers/string.helper"; // hooks @@ -28,39 +28,6 @@ export const PageOptionsDropdown: React.FC = observer((props) => { // create a local state to track if the current action is being processed const [localAction, setLocalAction] = useState(null); - // listen to real time updates from the live server - useEffect(() => { - const provider = editorRef?.listenToRealTimeUpdate(); - - const handleStatelessMessage = (message: { payload: DocumentEventsClient }) => { - if (localAction === message.payload) { - setLocalAction(null); - return; - } - - switch (message.payload) { - case "locked": - handleLockPage(false); - break; - case "unlocked": - handleUnlockPage(false); - break; - case "archived": - handleArchivePage(false); - break; - case "unarchived": - handleRestorePage(false); - break; - } - }; - - provider?.on("stateless", handleStatelessMessage); - - return () => { - provider?.off("stateless", handleStatelessMessage); - }; - }, [editorRef, localAction]); - // router const router = useRouter(); // store values @@ -83,92 +50,137 @@ export const PageOptionsDropdown: React.FC = observer((props) => { // update query params const { updateQueryParams } = useQueryParams(); - const handleArchivePage = async (isLocal: boolean = true) => { - await archive() - .then(() => { - if (isLocal) { - setLocalAction("archived"); - } - }) - .catch(() => { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Page could not be archived. Please try again later.", + const handleArchivePage = useCallback( + async (isLocal: boolean = true) => { + await archive() + .then(() => { + if (isLocal) { + setLocalAction("archived"); + } + }) + .catch(() => { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Page could not be archived. Please try again later.", + }); }); - }); - }; + }, + [archive] + ); // watch for changes in localAction useEffect(() => { if (localAction === "archived") { - editorRef?.emitRealTimeUpdate("archive"); + editorRef?.emitRealTimeUpdate("Archive"); } if (localAction === "unarchived") { - editorRef?.emitRealTimeUpdate("unarchive"); + editorRef?.emitRealTimeUpdate("Unarchive"); } if (localAction === "locked") { - editorRef?.emitRealTimeUpdate("lock"); + editorRef?.emitRealTimeUpdate("Lock"); } if (localAction === "unlocked") { - editorRef?.emitRealTimeUpdate("unlock"); + editorRef?.emitRealTimeUpdate("Unlock"); } }, [localAction, editorRef]); - const handleRestorePage = async (isLocal: boolean = true) => { - await restore() - .then(() => { - if (isLocal) { - setLocalAction("unarchived"); - } - }) - .catch(() => - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Page could not be restored. Please try again later.", + const handleRestorePage = useCallback( + async (isLocal: boolean = true) => { + await restore() + .then(() => { + if (isLocal) { + setLocalAction("unarchived"); + } }) - ); - }; + .catch(() => + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Page could not be restored. Please try again later.", + }) + ); + }, + [restore] + ); - const handleLockPage = async (isLocal: boolean = true) => { - await lock() - .then(() => { - if (isLocal) { - setLocalAction("locked"); - } - }) - .catch(() => - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Page could not be locked. Please try again later.", + const handleLockPage = useCallback( + async (isLocal: boolean = true) => { + await lock() + .then(() => { + if (isLocal) { + setLocalAction("locked"); + } }) - ); - }; + .catch(() => + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Page could not be locked. Please try again later.", + }) + ); + }, + [lock] + ); - const handleUnlockPage = async (isLocal: boolean = true) => { - await unlock() - .then(() => { - if (isLocal) { - setLocalAction("unlocked"); - } - }) - .catch(() => - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Page could not be unlocked. Please try again later.", + const handleUnlockPage = useCallback( + async (isLocal: boolean = true) => { + await unlock() + .then(() => { + if (isLocal) { + setLocalAction("unlocked"); + } }) - ); - }; + .catch(() => + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Page could not be unlocked. Please try again later.", + }) + ); + }, + [unlock] + ); + + // listen to real time updates from the live server + useEffect(() => { + const provider = editorRef?.listenToRealTimeUpdate(); + + const handleStatelessMessage = (message: { payload: DocumentEventsClient }) => { + if (localAction === message.payload) { + setLocalAction(null); + return; + } + + switch (message.payload) { + case "locked": + handleLockPage(false); + break; + case "unlocked": + handleUnlockPage(false); + break; + case "archived": + handleArchivePage(false); + break; + case "unarchived": + handleRestorePage(false); + break; + } + }; + + provider?.on("stateless", handleStatelessMessage); + + return () => { + provider?.off("stateless", handleStatelessMessage); + }; + }, [editorRef, localAction, handleArchivePage, handleRestorePage, handleLockPage, handleUnlockPage]); // menu items list const MENU_ITEMS: { key: string; action: () => void; label: string; - icon: React.FC; + icon: LucideIcon | React.FC; shouldRender: boolean; }[] = [ {