Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load Entity into Block functionality #480

Merged
merged 51 commits into from
Apr 15, 2022
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
31410b2
add useAccountEntities hook
teenoh Apr 1, 2022
0adb1d5
tmp
teenoh Apr 1, 2022
d9d58b0
Merge branch 'main' into vu/instantiate-block-with-entity
teenoh Apr 1, 2022
0c98bbf
tmp
teenoh Apr 5, 2022
975d632
block context menu ui updates
teenoh Apr 5, 2022
3d43a20
update block suggester submenu ui
teenoh Apr 5, 2022
145aee2
Merge branch 'vu/instantiate-block-with-entity'
teenoh Apr 5, 2022
54e54d7
pull accountId from useRouteAccountInfo
teenoh Apr 5, 2022
6c584dc
Merge branch 'main' into vu/instantiate-block-with-entity
teenoh Apr 7, 2022
1ef9b4b
add xs size to textfield
teenoh Apr 7, 2022
0749e29
update load entity menu
teenoh Apr 7, 2022
d3dd7e7
tmp
teenoh Apr 8, 2022
93f7550
create separate component for block list submenu
teenoh Apr 11, 2022
c9c389a
add listeners for set keys to open submenus
teenoh Apr 11, 2022
8eb9592
add action for updating block properties with temp fix to bug
teenoh Apr 11, 2022
d9513cf
add feature flag
teenoh Apr 11, 2022
9aca93b
fix lint issues
teenoh Apr 11, 2022
48614a7
Merge branch 'main' into vu/instantiate-block-with-entity
teenoh Apr 11, 2022
3d115ff
fix tests
teenoh Apr 11, 2022
5f08195
fix playwright tests
teenoh Apr 12, 2022
7d78bd1
fix lint issues
teenoh Apr 12, 2022
dadc0d3
fix bug causing maximum callstack error
teenoh Apr 12, 2022
85496d2
add requested changes
teenoh Apr 12, 2022
1d42631
move block context menu to blocks folder
teenoh Apr 12, 2022
005a503
updates
teenoh Apr 12, 2022
ddaacab
fix lint issues
teenoh Apr 12, 2022
d2cccb1
Merge branch 'main' into vu/instantiate-block-with-entity
teenoh Apr 12, 2022
d67d1f3
Merge branch 'main' into vu/instantiate-block-with-entity
teenoh Apr 12, 2022
4b10782
reuse createLocalBlock in createRemoteBlock
teenoh Apr 14, 2022
7aa9a5e
add prefixEntityIdWithDraft helper
teenoh Apr 14, 2022
f947b97
fix type issue and update comments
teenoh Apr 14, 2022
30ed083
switch to useQuery and update entityFieldsFragment
teenoh Apr 14, 2022
30555bc
fix issue in createLocalBlock
teenoh Apr 14, 2022
c1991f6
calculate submenu top offset in useLayoutEffect
teenoh Apr 14, 2022
0922ad9
add todo comment to remove feature flag
teenoh Apr 14, 2022
416001b
abstract prosemirror code to schema manager
teenoh Apr 14, 2022
a685464
updates
teenoh Apr 14, 2022
191cac3
add todo comment to properly handle refs in block context menu item
teenoh Apr 14, 2022
c4ed9f6
add requested change
teenoh Apr 14, 2022
8ba064a
updates
teenoh Apr 14, 2022
650ae59
Merge branch 'main' into vu/instantiate-block-with-entity
teenoh Apr 14, 2022
1f0bc14
add todo comment to update createLocalBlock
teenoh Apr 14, 2022
9f75c83
Merge branch 'main' into vu/instantiate-block-with-entity
teenoh Apr 14, 2022
c6a9055
fix lint issues
teenoh Apr 14, 2022
43d4501
revert changes to text block section of createRemoteBlock
teenoh Apr 14, 2022
f0d37d3
Merge branch 'vu/instantiate-block-with-entity' of github.com:hashint…
teenoh Apr 14, 2022
c867f5c
comment out code that autofocuses block list search input
teenoh Apr 14, 2022
6ce3d8e
update comments
teenoh Apr 14, 2022
a6d09c6
fix prettier
teenoh Apr 14, 2022
22f7792
rename refixEntityIdWithDraft to draftIdForEntity
teenoh Apr 15, 2022
99f1d29
cleanups
teenoh Apr 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import React, { useRef, forwardRef, useMemo } from "react";

import { useKey } from "rooks";
import { EntityStore, isBlockEntity } from "@hashintel/hash-shared/entityStore";

import { EditorView } from "prosemirror-view";
import { Schema } from "prosemirror-model";
import { Box, Divider, Menu, Typography } from "@mui/material";
import { bindMenu } from "material-ui-popup-state";
import { PopupState } from "material-ui-popup-state/hooks";
import { format } from "date-fns";
import {
faAdd,
faArrowRight,
faLink,
faRefresh,
} from "@fortawesome/free-solid-svg-icons";
import {
faCopy,
faMessage,
faTrashCan,
} from "@fortawesome/free-regular-svg-icons";
import { getBlockDomId } from "../BlockView";
import { BlockSuggesterProps } from "../createSuggester/BlockSuggester";

import { BlockLoaderInput } from "./BlockLoaderInput";
import { useUsers } from "../../../components/hooks/useUsers";
import { FontAwesomeIcon } from "../../../shared/icons";
import { BlockContextMenuItem } from "./BlockContextMenuItem";
import { LoadEntityMenuContent } from "./LoadEntityMenuContent";
import { BlockListMenuContent } from "./BlockListMenuContent";

type BlockContextMenuProps = {
popupState: PopupState;
blockSuggesterProps: BlockSuggesterProps;
entityId: string | null;
entityStore: EntityStore;
view: EditorView<Schema>;
};

const LOAD_BLOCK_ENTITY_UI = "hash-load-entity-ui";

export const BlockContextMenu = forwardRef<
HTMLDivElement,
BlockContextMenuProps
>(({ popupState, blockSuggesterProps, entityId, entityStore }, ref) => {
const blockData = entityId ? entityStore.saved[entityId] : null;
const { data: users } = useUsers();
const setEntityMenuItemRef = useRef<HTMLLIElement>(null);
const swapBlocksMenuItemRef = useRef<HTMLLIElement>(null);

if (blockData && !isBlockEntity(blockData)) {
throw new Error("BlockContextMenu linked to non-block entity");
}

const menuItems = useMemo(() => {
const hasChildEntity =
Object.keys(blockData?.properties.entity?.properties ?? {}).length > 0;
const items = [
{
key: "set-entity",
title: hasChildEntity ? "Swap Entity" : "Add an entity",
icon: <FontAwesomeIcon icon={faAdd} />,
// @todo fix this TS issue. all subMenu's require
// popupState and that's passed in through cloneElement
// in BlockContextMenuItem
// For now the temporary fix is to make popupState optional
// in LoadEntityMenuContent
subMenu: <LoadEntityMenuContent entityId={entityId} />,
subMenuWidth: 280,
},
{
key: "copy-link",
title: "Copy Link",
icon: <FontAwesomeIcon icon={faLink} />,
onClick: () => {
const url = new URL(document.location.href);
url.hash = getBlockDomId(entityId!);
void navigator.clipboard.writeText(url.toString());
},
},
{
key: "duplicate",
title: "Duplicate",
icon: <FontAwesomeIcon icon={faCopy} />,
},
{
key: "delete",
title: "Delete",
icon: <FontAwesomeIcon icon={faTrashCan} />,
},
{
key: "swap-block",
title: "Swap block type",
icon: <FontAwesomeIcon icon={faRefresh} />,
subMenu: (
<BlockListMenuContent blockSuggesterProps={blockSuggesterProps} />
),
subMenuWidth: 228,
},
{
key: "move-to-page",
title: "Move to page",
icon: <FontAwesomeIcon icon={faArrowRight} />,
},
{
key: "comment",
title: "Comment",
icon: <FontAwesomeIcon icon={faMessage} />,
},
];

// @todo this flag wouldn't be need once
// https://app.asana.com/0/1201959586244685/1202106892392942 has been addressed
if (!localStorage.getItem(LOAD_BLOCK_ENTITY_UI)) {
items.shift();
}

return items;
}, [blockData, entityId, blockSuggesterProps]);

useKey(["Escape"], () => {
popupState.close();
});

useKey(["@"], () => {
if (popupState.isOpen && localStorage.getItem(LOAD_BLOCK_ENTITY_UI)) {
setEntityMenuItemRef.current?.focus();
}
});

useKey(["/"], () => {
if (popupState.isOpen) {
swapBlocksMenuItemRef.current?.focus();
}
});

return (
<Menu
{...bindMenu(popupState)}
ref={ref}
anchorOrigin={{
horizontal: "left",
vertical: "bottom",
}}
transformOrigin={{
horizontal: "right",
vertical: "top",
}}
PaperProps={{
sx: {
width: 228,
},
}}
data-testid="block-context-menu"
>
<Box component="li" px={2} pt={1.5} mb={1}>
<BlockLoaderInput />
</Box>

{menuItems.map(({ key, title, icon, onClick, subMenu, subMenuWidth }) => {
if (key === "copy-link" && !entityId) {
return null;
}

let menuItemRef;
if (key === "set-entity") {
menuItemRef = setEntityMenuItemRef;
}
if (key === "swap-block") {
menuItemRef = swapBlocksMenuItemRef;
}

return (
<BlockContextMenuItem
key={key}
title={title}
itemKey={key}
icon={icon}
onClick={onClick}
subMenu={subMenu}
subMenuWidth={subMenuWidth}
{...(menuItemRef && { ref: menuItemRef })}
/>
);
})}

<Divider />
<Box px={1.75} pt={1.25} pb={1.5}>
<Typography
variant="microText"
sx={({ palette }) => ({
color: palette.gray[60],
display: "block",
})}
>
Last edited by {/* @todo use lastedited value when available */}
{
users.find(
(account) =>
account.entityId ===
blockData?.properties.entity.createdByAccountId,
)?.name
}
</Typography>

{typeof blockData?.properties.entity.updatedAt === "string" && (
<Typography
variant="microText"
sx={({ palette }) => ({
color: palette.gray[60],
})}
>
{format(new Date(blockData.properties.entity.updatedAt), "hh.mm a")}
{", "}
{format(
new Date(blockData.properties.entity.updatedAt),
"dd/MM/yyyy",
)}
</Typography>
)}
</Box>
</Menu>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
import { ListItemIcon, ListItemText, MenuItem } from "@mui/material";
import {
bindPopover,
bindHover,
bindFocus,
usePopupState,
} from "material-ui-popup-state/hooks";
import HoverPopover from "material-ui-popup-state/HoverPopover";
import {
cloneElement,
forwardRef,
RefObject,
useLayoutEffect,
useRef,
useState,
} from "react";
import { FontAwesomeIcon } from "../../../shared/icons";

type BlockContextMenuItemProps = {
itemKey: string;
onClick?: () => void;
icon: JSX.Element;
title: JSX.Element | string;
subMenu?: JSX.Element;
subMenuWidth?: number;
};

export const BlockContextMenuItem = forwardRef<
HTMLLIElement,
BlockContextMenuItemProps
>(({ onClick, icon, title, itemKey, subMenu, subMenuWidth }, ref) => {
const subMenuPopupState = usePopupState({
variant: "popper",
popupId: `${itemKey}-submenu`,
});
const [subMenuOffsetTop, setSubmenuOffsetTop] = useState<
number | undefined
>();
const localRef = useRef<HTMLLIElement>(null);
// @todo this doesn't handle when ref is a function and can break when trying to
// access offsetTop in the useLayoutEffect. Consider using an library to handle merging refs
// @see https://github.com/gregberge/react-merge-refs, or better still, use the
// useForkRef exported by MUI
const menuItemRef = (ref ?? localRef) as RefObject<HTMLLIElement>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ref here could be a function in which case your attempt to access it in your effect breaks. I would use a tool to combine refs instead. https://github.com/gregberge/react-merge-refs

Copy link
Contributor Author

@teenoh teenoh Apr 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. Didn't consider that. Added a todo comment in 191cac3 to handle that when working on nested menus


useLayoutEffect(() => {
if (subMenu && !subMenuOffsetTop && menuItemRef.current) {
setSubmenuOffsetTop(menuItemRef.current.offsetTop);
}
}, [subMenu, subMenuOffsetTop, menuItemRef]);

return (
<MenuItem
ref={menuItemRef}
{...(subMenu
? {
...bindHover(subMenuPopupState),
...bindFocus(subMenuPopupState),
}
: {
onClick,
})}
sx={{
position: "relative",
}}
>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={title} />
{subMenu ? (
<>
<FontAwesomeIcon
icon={faChevronRight}
sx={({ palette }) => ({
ml: "auto",
color: palette.gray[50],
fontSize: 12,
})}
/>
<HoverPopover
{...bindPopover(subMenuPopupState)}
elevation={4}
anchorOrigin={{
horizontal: "right",
vertical: subMenuOffsetTop ? -subMenuOffsetTop : "top",
}}
transformOrigin={{
horizontal: "left",
vertical: "top",
}}
PaperProps={{
sx: {
height: 300,
width: subMenuWidth,
ml: 1,
py: 0.5,
},
}}
>
{cloneElement(subMenu, { popupState: subMenuPopupState })}
</HoverPopover>
</>
) : null}
</MenuItem>
);
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BlockVariant } from "blockprotocol";
import { tw } from "twind";
import { RemoteBlockMetadata } from "../../blocks/userBlocks";
import { RemoteBlockMetadata } from "../../userBlocks";

export type MenuState = {
currentView: "normal" | "search";
Expand Down
Loading