plane/web/components/pages/editor/header/options-dropdown.tsx
Aaryan Khandelwal ff03c0b718 [WEB-1322] dev: conflict free pages collaboration (#4463)
* chore: pages realtime

* chore: empty binary response

* chore: added a ypy package

* feat: pages collaboration

* chore: update fetching logic

* chore: degrade ypy version

* chore: replace useEffect fetch logic with useSWR

* chore: move all the update logic to the page store

* refactor: remove react-hook-form

* chore: save description_html as well

* chore: migrate old data logic

* fix: added description_binary as field name

* fix: code cleanup

* refactor: create separate hook to handle page description

* fix: build errors

* chore: combine updates instead of using the whole document

* chore: removed ypy package

* chore: added conflict resolving logic to the client side

* chore: add a save changes button

* chore: add read-only validation

* chore: remove saving state information

* chore: added permission class

* chore: removed the migration file

* chore: corrected the model field

* chore: rename pageStore to page

* chore: update collaboration provider

* chore: add try catch to handle error

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-05-28 13:10:03 +05:30

160 lines
4.5 KiB
TypeScript

import { observer } from "mobx-react";
import { ArchiveRestoreIcon, Clipboard, Copy, Link, Lock, LockOpen } from "lucide-react";
// document editor
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/document-editor";
// ui
import { ArchiveIcon, CustomMenu, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui";
// helpers
import { copyTextToClipboard, copyUrlToClipboard } from "@/helpers/string.helper";
// hooks
import { useAppRouter } from "@/hooks/store";
import { usePageFilters } from "@/hooks/use-page-filters";
// store
import { IPageStore } from "@/store/pages/page.store";
type Props = {
editorRef: EditorRefApi | EditorReadOnlyRefApi | null;
handleDuplicatePage: () => void;
page: IPageStore;
};
export const PageOptionsDropdown: React.FC<Props> = observer((props) => {
const { editorRef, handleDuplicatePage, page } = props;
// store values
const {
archived_at,
is_locked,
id,
archive,
lock,
unlock,
canCurrentUserArchivePage,
canCurrentUserDuplicatePage,
canCurrentUserLockPage,
restore,
} = page;
// store hooks
const { workspaceSlug, projectId } = useAppRouter();
// page filters
const { isFullWidth, handleFullWidth } = usePageFilters();
const handleArchivePage = async () =>
await archive().catch(() =>
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Page could not be archived. Please try again later.",
})
);
const handleRestorePage = async () =>
await restore().catch(() =>
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Page could not be restored. Please try again later.",
})
);
const handleLockPage = async () =>
await lock().catch(() =>
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Page could not be locked. Please try again later.",
})
);
const handleUnlockPage = async () =>
await unlock().catch(() =>
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Page could not be unlocked. Please try again later.",
})
);
// menu items list
const MENU_ITEMS: {
key: string;
action: () => void;
label: string;
icon: React.FC<any>;
shouldRender: boolean;
}[] = [
{
key: "copy-markdown",
action: () => {
if (!editorRef) return;
copyTextToClipboard(editorRef.getMarkDown()).then(() =>
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Markdown copied to clipboard.",
})
);
},
label: "Copy markdown",
icon: Clipboard,
shouldRender: true,
},
{
key: "copy-page-link",
action: () => {
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/pages/${id}`).then(() =>
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Page link copied to clipboard.",
})
);
},
label: "Copy page link",
icon: Link,
shouldRender: true,
},
{
key: "make-a-copy",
action: handleDuplicatePage,
label: "Make a copy",
icon: Copy,
shouldRender: canCurrentUserDuplicatePage,
},
{
key: "lock-unlock-page",
action: is_locked ? handleUnlockPage : handleLockPage,
label: is_locked ? "Unlock page" : "Lock page",
icon: is_locked ? LockOpen : Lock,
shouldRender: canCurrentUserLockPage,
},
{
key: "archive-restore-page",
action: archived_at ? handleRestorePage : handleArchivePage,
label: archived_at ? "Restore page" : "Archive page",
icon: archived_at ? ArchiveRestoreIcon : ArchiveIcon,
shouldRender: canCurrentUserArchivePage,
},
];
return (
<CustomMenu maxHeight="md" placement="bottom-start" verticalEllipsis closeOnSelect>
<CustomMenu.MenuItem
className="hidden md:flex w-full items-center justify-between gap-2"
onClick={() => handleFullWidth(!isFullWidth)}
>
Full width
<ToggleSwitch value={isFullWidth} onChange={() => {}} />
</CustomMenu.MenuItem>
{MENU_ITEMS.map((item) => {
if (!item.shouldRender) return null;
return (
<CustomMenu.MenuItem key={item.key} onClick={item.action} className="flex items-center gap-2">
<item.icon className="h-3 w-3" />
{item.label}
</CustomMenu.MenuItem>
);
})}
</CustomMenu>
);
});