Skip to content

Commit

Permalink
feat(web): add function list context menu (#1825)
Browse files Browse the repository at this point in the history
* feat(web): add function list context menu

* fix popover trigger click

* fix delete with new function render & fix contextmenu hide after delete
  • Loading branch information
newfish-cmyk authored Feb 5, 2024
1 parent 3e17727 commit bcde132
Show file tree
Hide file tree
Showing 13 changed files with 222 additions and 49 deletions.
21 changes: 21 additions & 0 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"pako": "^2.1.0",
"qrcode.react": "^3.1.0",
"react": "18.2.0",
"react-contexify": "^6.0.0",
"react-datepicker": "^4.11.0",
"react-day-picker": "^8.8.0",
"react-dom": "18.2.0",
Expand Down Expand Up @@ -101,4 +102,4 @@
"*.{ts,tsx,js}": "eslint --fix",
"*.{css,scss}": "stylelint --fix"
}
}
}
3 changes: 3 additions & 0 deletions web/src/components/ConfirmButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface ConfirmButtonProps {
headerText: string;
bodyText: string | React.ReactElement | any;
confirmButtonText?: string;
hideContextMenu?: () => void;
children: React.ReactElement;
}

Expand All @@ -26,6 +27,7 @@ const ConfirmButton = ({
headerText,
bodyText,
confirmButtonText,
hideContextMenu,
children,
}: ConfirmButtonProps) => {
const { isOpen, onOpen, onClose } = useDisclosure();
Expand All @@ -34,6 +36,7 @@ const ConfirmButton = ({
const onSubmit: React.MouseEventHandler<HTMLButtonElement> = (event) => {
onSuccessAction(event);
onClose();
hideContextMenu && hideContextMenu();
};

return (
Expand Down
7 changes: 4 additions & 3 deletions web/src/components/DateRangePicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DateRange, DayPicker, SelectRangeEventHandler } from "react-day-picker"
import { useTranslation } from "react-i18next";
import {
Box,
Button,
Input,
Popover,
PopoverContent,
Expand Down Expand Up @@ -99,9 +100,9 @@ export default function DateRangePicker(props: {
/>
<Popover onClose={onClose}>
<PopoverTrigger>
<div>
<CalendarIcon className="mr-3 cursor-pointer !text-grayModern-500" fontSize="16" />
</div>
<Button variant="none" px={0} mr={2} minW={4}>
<CalendarIcon className="!text-grayModern-500 pb-[2px]" fontSize="18" />
</Button>
</PopoverTrigger>
<PopoverContent zIndex={99}>
<DayPicker
Expand Down
11 changes: 5 additions & 6 deletions web/src/components/MoreButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Box,
Button,
Popover,
PopoverContent,
PopoverTrigger,
Expand All @@ -17,28 +18,26 @@ export default function MoreButton(props: {
label: string;
maxWidth?: string;
className?: string;
refItem?: React.RefObject<HTMLDivElement>;
}) {
const { children, isHidden, maxWidth, label = t("openPopover"), className } = props;
const { children, isHidden, maxWidth, label = t("openPopover"), className, refItem } = props;
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<div className={clsx("flex group-hover:visible ", isHidden ? "invisible" : "visible")}>
<Popover
isOpen={isOpen}
onOpen={onOpen}
onClose={onClose}
closeOnBlur={true}
placement="bottom"
>
<Tooltip aria-label="tooltip" placement="bottom" label={label}>
<Box display="inline-block">
<PopoverTrigger>
<div className="px-1">
<MoreIcon className="cursor-pointer align-middle" fontSize={12} />
</div>
<Button variant="none" p={0} minW={0} h={0} w={5}><MoreIcon className="cursor-pointer align-middle" fontSize={12} /></Button>
</PopoverTrigger>
</Box>
</Tooltip>
<PopoverContent p="2" maxWidth={maxWidth ? maxWidth : "100px"} className={className}>
<PopoverContent p="2" maxWidth={maxWidth ? maxWidth : "120px"} className={className}>
<div className="flex justify-around">{children}</div>
</PopoverContent>
</Popover>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.contexify {
box-shadow: none !important;
border: 1px solid #e2e8f0 !important;
min-width: 0 !important;
justify-content: center;
width: 120px;
padding: 2px !important;
}

.contexify_theme-dark {
background-color: #212630 !important;
border: 1px solid rgba(255, 255, 255, 0.16) !important;
}

.contexify_itemContent {
background: none !important;
width: 100% !important;
height: 100% !important;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Item, Menu } from "react-contexify";
import "./index.scss"
import "react-contexify/dist/ReactContexify.css";
import CreateModal from "../CreateModal";
import { TFunction } from "@/apis/typing";
import IconText from "@/components/IconText";
import { EditIconLine, LinkIcon, RecycleDeleteIcon } from "@/components/CommonIcon";
import { useTranslation } from "react-i18next";
import ConfirmButton from "@/components/ConfirmButton";
import { useDeleteFunctionMutation } from "../../../service";
import useGlobalStore from "@/pages/globalStore";
import CopyText from "@/components/CopyText";
import { COLOR_MODE } from "@/constants";
import { useColorMode } from "@chakra-ui/react";


export default function ContextMenu(props: { functionItem: TFunction, tagsList: string[], hideAll: () => void }) {
const { functionItem, tagsList, hideAll } = props;
const { t } = useTranslation();
const deleteFunctionMutation = useDeleteFunctionMutation();
const { showSuccess, currentApp } = useGlobalStore();
const darkMode = useColorMode().colorMode === COLOR_MODE.dark;

return (
<Menu id={functionItem._id} animation="fade" className="flex" theme={darkMode ? "dark" : "light"}>
<Item closeOnClick={false}>
<CreateModal
functionItem={functionItem} tagList={tagsList} hideContextMenu={hideAll}>
<IconText
icon={
<div className="flex h-5 items-center">
<EditIconLine />
</div>
}
text={t("Edit")}
/>
</CreateModal>
</Item>
<Item>
<ConfirmButton
onSuccessAction={async () => {
const res = await deleteFunctionMutation.mutateAsync(functionItem);
if (!res.error) {
showSuccess(t("DeleteSuccess"));
}
}}
headerText={String(t("Delete"))}
bodyText={String(t("FunctionPanel.DeleteConfirm"))}
hideContextMenu={hideAll}
>
<IconText
icon={<div className="h-5 flex items-center"><RecycleDeleteIcon fontSize={16} /></div>}
text={t("Delete")}
className="hover:!text-error-600"
/>
</ConfirmButton>
</Item>
<Item>
<CopyText
text={`${currentApp.origin}/${functionItem.name}`}
hideToolTip
>
<IconText
icon={<div className="h-5 flex items-center"><LinkIcon fontSize={22} /></div>}
text={t("Copy")}
className="hover:!text-primary-400"
/>
</CopyText>
</Item>
</Menu>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ const CreateModal = (props: {
children?: React.ReactElement;
tagList?: any;
aiCode?: string;
hideContextMenu?: () => void;
}) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const { showSuccess, currentApp } = useGlobalStore();

const { functionItem, children = null, tagList, aiCode } = props;
const { functionItem, children = null, tagList, aiCode, hideContextMenu } = props;
const isEdit = !!functionItem;
const navigate = useNavigate();
const [searchKey, setSearchKey] = useState("");
Expand Down Expand Up @@ -129,6 +130,7 @@ const CreateModal = (props: {
onClose();
reset(defaultValues);
navigate(`/app/${currentApp.appid}/function/${res.data.name}`, { replace: true });
hideContextMenu && hideContextMenu();
}
};

Expand Down
65 changes: 51 additions & 14 deletions web/src/pages/app/functions/mods/FunctionPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { cloneDeep } from "lodash";

import {
EditIconLine,
LinkIcon,
RecycleBinIcon,
RecycleDeleteIcon,
TriggerIcon,
Expand Down Expand Up @@ -53,6 +54,10 @@ import RecycleBinModal from "@/pages/app/functions/mods/RecycleBinModal";
import useCustomSettingStore from "@/pages/customSetting";
import useGlobalStore from "@/pages/globalStore";

import { useContextMenu } from "react-contexify";
import ContextMenu from "./ContextMenu";
import CopyText from "@/components/CopyText";

type TagItem = {
tagName: string;
selected: boolean;
Expand Down Expand Up @@ -114,6 +119,8 @@ export default function FunctionList() {

const [currentTag, setCurrentTag] = useState<TagItem | null>(null);

const { show, hideAll } = useContextMenu();

const generateRoot = useCallback(
(data: TFunction[]) => {
const root = cloneDeep(functionRoot);
Expand Down Expand Up @@ -185,9 +192,9 @@ export default function FunctionList() {
return oldTag.length > 0
? oldTag[0]
: {
tagName: tagName,
selected: false,
};
tagName: tagName,
selected: false,
};
});
setTagsList(newTags);

Expand Down Expand Up @@ -277,9 +284,8 @@ export default function FunctionList() {
return (
<span className="flex select-none items-center">
<span>{item.desc}</span>
<div className="ml-1 translate-y-[1px] scale-[.85] opacity-75">{` ${
nameParts[nameParts.length - 1]
}`}</div>
<div className="ml-1 translate-y-[1px] scale-[.85] opacity-75">{` ${nameParts[nameParts.length - 1]
}`}</div>
</span>
);
} else {
Expand Down Expand Up @@ -321,14 +327,14 @@ export default function FunctionList() {
darkMode ? "text-grayIron-200" : " text-grayIron-700",
item.name === currentFunction?.name && !item.children?.length && "!text-primary-700",
dragOverFuncDir !== null &&
((item.children?.length && (item.name + "/").startsWith(dragOverFuncDir)) ||
(!item.children?.length && item.name.startsWith(dragOverFuncDir))) &&
"!text-primary-700",
((item.children?.length && (item.name + "/").startsWith(dragOverFuncDir)) ||
(!item.children?.length && item.name.startsWith(dragOverFuncDir))) &&
"!text-primary-700",
"!mb-0 pb-[2px]",
dragOverFuncDir !== null &&
((item.children?.length && (item.name + "/").startsWith(dragOverFuncDir)) ||
(!item.children?.length && item.name.startsWith(dragOverFuncDir))) &&
"bg-[#f9f9f9]",
((item.children?.length && (item.name + "/").startsWith(dragOverFuncDir)) ||
(!item.children?.length && item.name.startsWith(dragOverFuncDir))) &&
"bg-[#f9f9f9]",
)}
onClick={() => {
if (!item.children?.length) {
Expand All @@ -348,6 +354,22 @@ export default function FunctionList() {
onDragStart={onDragStart}
data-func={item.name}
data-is-func-dir={!!item.children?.length}
onContextMenu={(e) => {
const sidebarWidth = JSON.parse(localStorage.getItem("laf_custom_setting") || "").state.layoutInfo.functionPage.SideBar.style.width || 0
if (!!item.children?.length) return;
if (e.clientX > sidebarWidth - 120) {
show({
event: e,
id: item._id,
position: {
x: e.clientX - 120,
y: e.clientY,
}
})
} else {
show({ event: e, id: item._id })
}
}}
>
<div
className="flex items-center overflow-hidden text-ellipsis whitespace-nowrap font-medium"
Expand All @@ -364,8 +386,8 @@ export default function FunctionList() {
<HStack spacing={1}>
{functionCache.getCache(item?._id, (item as any)?.source?.code) !==
(item as any)?.source?.code && (
<span className="mt-[1px] inline-block h-1 w-1 flex-none rounded-full bg-rose-500"></span>
)}
<span className="mt-[1px] inline-block h-1 w-1 flex-none rounded-full bg-rose-500"></span>
)}
<MoreButton isHidden={item.name !== currentFunction?.name} label={t("Operation")}>
<>
<CreateModal functionItem={item} tagList={tagsList}>
Expand Down Expand Up @@ -394,11 +416,26 @@ export default function FunctionList() {
className="hover:!text-error-600"
/>
</ConfirmButton>
<CopyText
text={`${currentApp.origin}/${item.name}`}
hideToolTip
>
<IconText
icon={<div className="h-5 flex items-center"><LinkIcon fontSize={22} /></div>}
text={t("Copy")}
className="hover:!text-primary-400"
/>
</CopyText>
</>
</MoreButton>
</HStack>
)}
</SectionList.Item>
{item._id && <ContextMenu
functionItem={item as unknown as TFunction}
tagsList={tagsList.map((item) => item.tagName)}
hideAll={hideAll}
/>}
{item.isExpanded && item?.children?.length && renderSectionItems(item.children)}
</React.Fragment>
);
Expand Down
Loading

0 comments on commit bcde132

Please sign in to comment.