From 31410b251bb0d890f26a18aee68be7b81fd058b1 Mon Sep 17 00:00:00 2001 From: teenoh Date: Fri, 1 Apr 2022 15:40:54 +0100 Subject: [PATCH 01/42] add useAccountEntities hook --- .../BlockContextMenu/BlockContextMenu.tsx | 12 +++++- .../components/hooks/useAccountEntities.ts | 43 +++++++++++++++++++ .../src/graphql/queries/entity.queries.ts | 13 ++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 packages/hash/frontend/src/components/hooks/useAccountEntities.ts create mode 100644 packages/hash/frontend/src/graphql/queries/entity.queries.ts diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx index ab7dd204d27..ad8855c92f5 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { tw } from "twind"; import DeleteIcon from "@mui/icons-material/DeleteOutline"; @@ -24,6 +24,8 @@ import { import { BlockLoaderInput } from "./BlockLoaderInput"; import { useUserBlocks } from "../../blocks/userBlocks"; import { useFilteredBlocks } from "../../blocks/page/createSuggester/useFilteredBlocks"; +import { useAccountEntities } from "../hooks/useAccountEntities"; +import { useCurrentWorkspaceContext } from "../../contexts/CurrentWorkspaceContext"; type BlockContextMenuProps = { blockSuggesterProps: BlockSuggesterProps; @@ -62,6 +64,14 @@ export const BlockContextMenu: React.VFC = ({ entityStore, }) => { const blockData = entityId ? entityStore.saved[entityId] : null; + const { accountId } = useCurrentWorkspaceContext(); + const { fetchEntities } = useAccountEntities(); + + useEffect(() => { + if (entityId) { + fetchEntities(accountId, { entityId }); + } + }, [entityId]); if (blockData && !isBlockEntity(blockData)) { throw new Error("BlockContextMenu linked to non-block entity"); diff --git a/packages/hash/frontend/src/components/hooks/useAccountEntities.ts b/packages/hash/frontend/src/components/hooks/useAccountEntities.ts new file mode 100644 index 00000000000..8230e9d507e --- /dev/null +++ b/packages/hash/frontend/src/components/hooks/useAccountEntities.ts @@ -0,0 +1,43 @@ +import { useCallback, useState } from "react"; +import { useApolloClient } from "@apollo/client"; +import { getEntities } from "../../graphql/queries/entity.queries"; +import { + GetEntitiesQuery, + GetEntitiesQueryVariables, + EntityTypeChoice, +} from "../../graphql/apiTypes.gen"; + +// @todo properly type this +export const useAccountEntities = () => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(); + const client = useApolloClient(); + + const fetchEntities = useCallback( + async (accountId: string, entityTypeFilter: EntityTypeChoice) => { + const response = await client.query< + GetEntitiesQuery, + GetEntitiesQueryVariables + >({ + query: getEntities, + variables: { + accountId, + filter: { + entityType: entityTypeFilter, + }, + }, + }); + + console.log(response.data.entities); + + return response.data.entities; + }, + [client], + ); + + return { + fetchEntities, + loading, + error, + }; +}; diff --git a/packages/hash/frontend/src/graphql/queries/entity.queries.ts b/packages/hash/frontend/src/graphql/queries/entity.queries.ts new file mode 100644 index 00000000000..63921240f05 --- /dev/null +++ b/packages/hash/frontend/src/graphql/queries/entity.queries.ts @@ -0,0 +1,13 @@ +import { gql } from "@apollo/client"; + +export const getEntities = gql` + query getEntities($accountId: ID!, $filter: EntityFilter) { + entities(accountId: $accountId, filter: $filter) { + entityId + accountId + entityTypeId + entityTypeName + properties + } + } +`; From 0adb1d5485df7e927b8b01e185037cd78f251ac7 Mon Sep 17 00:00:00 2001 From: teenoh Date: Fri, 1 Apr 2022 15:51:57 +0100 Subject: [PATCH 02/42] tmp --- .../src/components/BlockContextMenu/BlockContextMenu.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx index ad8855c92f5..7ffd11eb1a7 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx @@ -68,10 +68,10 @@ export const BlockContextMenu: React.VFC = ({ const { fetchEntities } = useAccountEntities(); useEffect(() => { - if (entityId) { - fetchEntities(accountId, { entityId }); + if (entityId && accountId) { + fetchEntities(accountId, { entityTypeId: entityId }); } - }, [entityId]); + }, [entityId, accountId]); if (blockData && !isBlockEntity(blockData)) { throw new Error("BlockContextMenu linked to non-block entity"); From 0c98bbff2fdbf73a8066a91679c9f4b4e9cdeca0 Mon Sep 17 00:00:00 2001 From: teenoh Date: Tue, 5 Apr 2022 01:08:03 +0100 Subject: [PATCH 03/42] tmp --- .../frontend/src/blocks/page/BlockView.tsx | 30 +++- .../BlockContextMenu/BlockContextMenu.tsx | 134 +++++++++++++++++- .../BlockContextMenu/BlockContextMenuItem.tsx | 38 ++++- .../components/hooks/useAccountEntities.ts | 2 +- .../components/layout/MainContentWrapper.tsx | 8 +- 5 files changed, 193 insertions(+), 19 deletions(-) diff --git a/packages/hash/frontend/src/blocks/page/BlockView.tsx b/packages/hash/frontend/src/blocks/page/BlockView.tsx index 0f971c1de5d..d69e4975f7c 100644 --- a/packages/hash/frontend/src/blocks/page/BlockView.tsx +++ b/packages/hash/frontend/src/blocks/page/BlockView.tsx @@ -6,6 +6,7 @@ import { import { isEntityNode } from "@hashintel/hash-shared/prosemirror"; import { ProsemirrorSchemaManager } from "@hashintel/hash-shared/ProsemirrorSchemaManager"; import { BlockVariant } from "blockprotocol"; +import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; import { ProsemirrorNode, Schema } from "prosemirror-model"; import { NodeSelection } from "prosemirror-state"; import { EditorView, NodeView } from "prosemirror-view"; @@ -25,11 +26,16 @@ type BlockHandleProps = { entityId: string | null; onTypeChange: BlockSuggesterProps["onChange"]; entityStore: EntityStore; + view: EditorView; }; export const BlockHandle = forwardRef( - ({ entityId, onTypeChange, entityStore }, ref) => { + ({ entityId, onTypeChange, entityStore, view }, ref) => { const [isPopoverVisible, setPopoverVisible] = useState(false); + const popupState = usePopupState({ + variant: "popover", + popupId: "block-context-menu", + }); useOutsideClick(ref as RefObject, () => setPopoverVisible(false), @@ -51,15 +57,30 @@ export const BlockHandle = forwardRef( className={tw`relative cursor-pointer`} data-testid="block-changer" > - setPopoverVisible(true)} /> - {isPopoverVisible && ( + setPopoverVisible(true)} + /> + + setPopoverVisible(false)} + entityStore={entityStore} + view={view} + popupState={popupState} + /> + + {/* {isPopoverVisible && ( setPopoverVisible(false)} entityStore={entityStore} + view={view} + popupState={popupState} /> - )} + )} */} ); }, @@ -263,6 +284,7 @@ export class BlockView implements NodeView { entityId={blockEntityId} onTypeChange={this.onBlockChange} entityStore={this.store} + view={this.view} /> , this.selectContainer, diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx index 7ffd11eb1a7..386e2b70733 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useRef } from "react"; import { tw } from "twind"; import DeleteIcon from "@mui/icons-material/DeleteOutline"; @@ -10,6 +10,14 @@ import { unstable_batchedUpdates } from "react-dom"; import { EntityStore, isBlockEntity } from "@hashintel/hash-shared/entityStore"; +import { + addEntityStoreAction, + entityStorePluginState, + entityStorePluginStateFromTransaction, + newDraftId, +} from "@hashintel/hash-shared/entityStorePlugin"; +import { EditorView } from "prosemirror-view"; +import { Schema } from "prosemirror-model"; import { getBlockDomId } from "../../blocks/page/BlockView"; import { BlockSuggesterProps } from "../../blocks/page/createSuggester/BlockSuggester"; import { NormalView } from "./NormalView"; @@ -26,12 +34,17 @@ import { useUserBlocks } from "../../blocks/userBlocks"; import { useFilteredBlocks } from "../../blocks/page/createSuggester/useFilteredBlocks"; import { useAccountEntities } from "../hooks/useAccountEntities"; import { useCurrentWorkspaceContext } from "../../contexts/CurrentWorkspaceContext"; +import { useBlockView } from "../../blocks/page/BlockViewContext"; +import PopupState, { bindMenu } from "material-ui-popup-state"; +import { Menu } from "@mui/material"; type BlockContextMenuProps = { + popupState: PopupState; blockSuggesterProps: BlockSuggesterProps; closeMenu: () => void; entityId: string | null; entityStore: EntityStore; + view: EditorView; }; const MENU_ITEMS: Array = [ @@ -58,20 +71,115 @@ const MENU_ITEMS: Array = [ ]; export const BlockContextMenu: React.VFC = ({ + popupState, blockSuggesterProps, closeMenu, entityId, entityStore, + view, }) => { const blockData = entityId ? entityStore.saved[entityId] : null; const { accountId } = useCurrentWorkspaceContext(); const { fetchEntities } = useAccountEntities(); + const blockView = useBlockView(); + + // console.log("blockView => ", blockView); + + const entityStoreRef = useRef(entityStore); + useEffect(() => { + entityStoreRef.current = entityStore; + }); + + // const blockEntity = isBlockEntity(blockData) + // ? blockData.properties.entity + // : null; + + console.log("blockdata => ", blockData); useEffect(() => { - if (entityId && accountId) { - fetchEntities(accountId, { entityTypeId: entityId }); + if (isBlockEntity(blockData) && accountId) { + const blockEntity = blockData.properties.entity; + + fetchEntities(accountId, { + componentId: blockData.properties.componentId, + }) + .then((entities) => { + // debugger; + if (entities.length === 0) return; + + const currentEntityStore = entityStoreRef.current; + // @todo UI for picking the entity + const targetEntity = entities[0]!; + + if (targetEntity.entityId === blockEntity.entityId) return; + + const tr = blockView.view.state.tr; + + const draftEntity = Object.values(currentEntityStore.draft).find( + (entity) => entity.entityId === targetEntity.entityId, + ); + + if (!draftEntity) { + const draftId = newDraftId(); + addEntityStoreAction(blockView.view.state, tr, { + type: "newDraftEntity", + payload: { + accountId: targetEntity.accountId, + draftId, + entityId: targetEntity.entityId, + }, + }); + addEntityStoreAction(blockView.view.state, tr, { + type: "updateEntityProperties", + payload: { + draftId, + merge: false, + properties: targetEntity.properties, + }, + }); + + blockView.view.dispatch(tr); + + const updatedStore = entityStorePluginStateFromTransaction( + tr, + blockView.view.state, + ); + + console.log("updatedStore ==> ", updatedStore); + + blockView.manager + .createRemoteBlock( + blockData.properties.componentId, + updatedStore.store, + `draft-${blockEntity.entityId}`, + ) + .then(() => {}) + .catch(() => {}); + + // 3. If it is not, put it in the entity store + } + + /** + * 4. Update the block entity in the entity store to point to this entity + */ + + // addEntityStoreAction(blockView.view.state, tr, { + // type: "updateEntityProperties", + // payload: { + // draftId: `draft-${blockEntity.entityId}`, + // merge: false, + // properties: targetEntity.properties, + // }, + // }); + + /** + * 5. Update the prosemirror tree to reflect this + */ + blockView.view.dispatch(tr); + }) + .catch(() => {}); } - }, [entityId, accountId]); + }, [blockData, accountId, blockView, fetchEntities]); if (blockData && !isBlockEntity(blockData)) { throw new Error("BlockContextMenu linked to non-block entity"); @@ -218,9 +326,20 @@ export const BlockContextMenu: React.VFC = ({ }; return ( -
+ {/*
*/}
= ({ updateMenuState={updateMenuState} /> )} -
+ {/*
*/} + ); }; diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx index 71d31d80b0e..2050b324a50 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx @@ -1,5 +1,8 @@ +import { ListItemIcon, ListItemText, MenuItem } from "@mui/material"; +import { bindPopover, usePopupState } from "material-ui-popup-state/hooks"; import { VFC } from "react"; import { tw } from "twind"; +import { Popover } from "../../shared/ui"; export const BlockContextMenuItem: VFC< { @@ -15,9 +18,31 @@ export const BlockContextMenuItem: VFC< } | { subMenuVisible: boolean; subMenu: JSX.Element | null } ) -> = ({ selected, onClick, onSelect, icon, title, subMenuVisible, subMenu }) => ( -
  • - -
  • -); + */} + + ); +}; diff --git a/packages/hash/frontend/src/components/hooks/useAccountEntities.ts b/packages/hash/frontend/src/components/hooks/useAccountEntities.ts index 8230e9d507e..ad3968aa8aa 100644 --- a/packages/hash/frontend/src/components/hooks/useAccountEntities.ts +++ b/packages/hash/frontend/src/components/hooks/useAccountEntities.ts @@ -28,7 +28,7 @@ export const useAccountEntities = () => { }, }); - console.log(response.data.entities); + console.log("res => ", response.data.entities); return response.data.entities; }, diff --git a/packages/hash/frontend/src/components/layout/MainContentWrapper.tsx b/packages/hash/frontend/src/components/layout/MainContentWrapper.tsx index 310c667cbc3..a447569f0ec 100644 --- a/packages/hash/frontend/src/components/layout/MainContentWrapper.tsx +++ b/packages/hash/frontend/src/components/layout/MainContentWrapper.tsx @@ -15,7 +15,7 @@ const Main = styled("main", { height: `calc(100vh - ${HEADER_HEIGHT}px)`, overflowY: "auto", flexGrow: 1, - padding: "60px 120px 0 120px", + paddingTop: "60px", transition: theme.transitions.create("margin", { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, @@ -28,6 +28,12 @@ const Main = styled("main", { }), marginLeft: 0, }), + + "& > *": { + maxWidth: 700, + marginLeft: "auto", + marginRight: "auto", + }, })); export const MainContentWrapper: FunctionComponent = ({ children }) => { From 975d6327ee0f7bb859b797f34524d42d160671b3 Mon Sep 17 00:00:00 2001 From: teenoh Date: Tue, 5 Apr 2022 12:24:38 +0100 Subject: [PATCH 04/42] block context menu ui updates --- .../frontend/src/blocks/page/BlockView.tsx | 61 +- .../BlockContextMenu/BlockContextMenu.tsx | 599 +++++++++--------- .../BlockContextMenu/BlockContextMenuItem.tsx | 88 +-- .../BlockContextMenu/BlockLoaderInput.tsx | 8 +- .../BlockContextMenu/NormalView.tsx | 100 ++- .../components/layout/MainContentWrapper.tsx | 10 +- .../src/graphql/queries/entity.queries.ts | 20 +- .../pages/[accountId]/[pageEntityId].page.tsx | 85 +-- 8 files changed, 480 insertions(+), 491 deletions(-) diff --git a/packages/hash/frontend/src/blocks/page/BlockView.tsx b/packages/hash/frontend/src/blocks/page/BlockView.tsx index d69e4975f7c..62432ec434a 100644 --- a/packages/hash/frontend/src/blocks/page/BlockView.tsx +++ b/packages/hash/frontend/src/blocks/page/BlockView.tsx @@ -5,14 +5,22 @@ import { } from "@hashintel/hash-shared/entityStorePlugin"; import { isEntityNode } from "@hashintel/hash-shared/prosemirror"; import { ProsemirrorSchemaManager } from "@hashintel/hash-shared/ProsemirrorSchemaManager"; +import { Box, Menu } from "@mui/material"; import { BlockVariant } from "blockprotocol"; import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; import { ProsemirrorNode, Schema } from "prosemirror-model"; import { NodeSelection } from "prosemirror-state"; import { EditorView, NodeView } from "prosemirror-view"; -import { createRef, forwardRef, RefObject, useMemo, useState } from "react"; +import { + createRef, + forwardRef, + RefObject, + RefObject, + useMemo, + useRef, + useState, +} from "react"; import { useOutsideClick } from "rooks"; -import { tw } from "twind"; import { BlockContextMenu } from "../../components/BlockContextMenu/BlockContextMenu"; import { DragVerticalIcon } from "../../shared/icons"; import { RemoteBlockMetadata } from "../userBlocks"; @@ -31,57 +39,44 @@ type BlockHandleProps = { export const BlockHandle = forwardRef( ({ entityId, onTypeChange, entityStore, view }, ref) => { - const [isPopoverVisible, setPopoverVisible] = useState(false); + const blockMenuRef = useRef(null); const popupState = usePopupState({ variant: "popover", popupId: "block-context-menu", }); - useOutsideClick(ref as RefObject, () => - setPopoverVisible(false), - ); + useOutsideClick(blockMenuRef, () => popupState.close()); const blockSuggesterProps: BlockSuggesterProps = useMemo( () => ({ onChange: (variant, block) => { onTypeChange(variant, block); - setPopoverVisible(false); + popupState.close(); }, }), - [onTypeChange], + [onTypeChange, popupState], ); return ( -
    - setPopoverVisible(true)} - /> + setPopoverVisible(false)} entityStore={entityStore} view={view} popupState={popupState} + ref={blockMenuRef} /> - - {/* {isPopoverVisible && ( - setPopoverVisible(false)} - entityStore={entityStore} - view={view} - popupState={popupState} - /> - )} */} -
    + ); }, ); @@ -244,9 +239,17 @@ export class BlockView implements NodeView { {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} -
    { /** diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx index 386e2b70733..60232147e2b 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx @@ -1,10 +1,6 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useState, useRef, forwardRef, useMemo } from "react"; import { tw } from "twind"; -import DeleteIcon from "@mui/icons-material/DeleteOutline"; -import CopyIcon from "@mui/icons-material/FileCopyOutlined"; -import LoopIcon from "@mui/icons-material/LoopOutlined"; -import LinkIcon from "@mui/icons-material/LinkOutlined"; import { useKey } from "rooks"; import { unstable_batchedUpdates } from "react-dom"; @@ -18,6 +14,21 @@ import { } from "@hashintel/hash-shared/entityStorePlugin"; 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 "../../blocks/page/BlockView"; import { BlockSuggesterProps } from "../../blocks/page/createSuggester/BlockSuggester"; import { NormalView } from "./NormalView"; @@ -35,13 +46,13 @@ import { useFilteredBlocks } from "../../blocks/page/createSuggester/useFiltered import { useAccountEntities } from "../hooks/useAccountEntities"; import { useCurrentWorkspaceContext } from "../../contexts/CurrentWorkspaceContext"; import { useBlockView } from "../../blocks/page/BlockViewContext"; -import PopupState, { bindMenu } from "material-ui-popup-state"; -import { Menu } from "@mui/material"; +import { useUsers } from "../hooks/useUsers"; +import { FontAwesomeIcon } from "../../shared/icons"; +import { BlockContextMenuItem } from "./BlockContextMenuItem"; type BlockContextMenuProps = { popupState: PopupState; blockSuggesterProps: BlockSuggesterProps; - closeMenu: () => void; entityId: string | null; entityStore: EntityStore; view: EditorView; @@ -49,299 +60,225 @@ type BlockContextMenuProps = { const MENU_ITEMS: Array = [ { - key: "delete", - title: "Delete", - icon: , + key: "add", + title: "Add an entity", + icon: , + }, + { + key: "copyLink", + title: "Copy Link", + icon: , }, { key: "duplicate", title: "Duplicate", - icon: , + icon: , }, { - key: "copyLink", - title: "Copy Link", - icon: , + key: "delete", + title: "Delete", + icon: , + }, + { + key: "swap-block", + title: "Swap block type", + icon: , }, { - key: "switchBlock", - title: "Turn into", - icon: , + key: "move-to-page", + title: "Move to page", + icon: , + }, + { + key: "comment", + title: "Comment", + icon: , }, ]; -export const BlockContextMenu: React.VFC = ({ - popupState, - blockSuggesterProps, - closeMenu, - entityId, - entityStore, - view, -}) => { - const blockData = entityId ? entityStore.saved[entityId] : null; - const { accountId } = useCurrentWorkspaceContext(); - const { fetchEntities } = useAccountEntities(); - const blockView = useBlockView(); - - // console.log("blockView => ", blockView); - - const entityStoreRef = useRef(entityStore); - useEffect(() => { - entityStoreRef.current = entityStore; - }); - - // const blockEntity = isBlockEntity(blockData) - // ? blockData.properties.entity - // : null; - - console.log("blockdata => ", blockData); - - useEffect(() => { - if (isBlockEntity(blockData) && accountId) { - const blockEntity = blockData.properties.entity; - - fetchEntities(accountId, { - componentId: blockData.properties.componentId, - }) - .then((entities) => { - // debugger; - if (entities.length === 0) return; - - const currentEntityStore = entityStoreRef.current; - // @todo UI for picking the entity - const targetEntity = entities[0]!; - - if (targetEntity.entityId === blockEntity.entityId) return; - - const tr = blockView.view.state.tr; - - const draftEntity = Object.values(currentEntityStore.draft).find( - (entity) => entity.entityId === targetEntity.entityId, - ); - - if (!draftEntity) { - const draftId = newDraftId(); - addEntityStoreAction(blockView.view.state, tr, { - type: "newDraftEntity", - payload: { - accountId: targetEntity.accountId, - draftId, - entityId: targetEntity.entityId, - }, - }); - addEntityStoreAction(blockView.view.state, tr, { - type: "updateEntityProperties", - payload: { - draftId, - merge: false, - properties: targetEntity.properties, - }, - }); - - blockView.view.dispatch(tr); - - const updatedStore = entityStorePluginStateFromTransaction( - tr, - blockView.view.state, - ); - - console.log("updatedStore ==> ", updatedStore); - - blockView.manager - .createRemoteBlock( - blockData.properties.componentId, - updatedStore.store, - `draft-${blockEntity.entityId}`, - ) - .then(() => {}) - .catch(() => {}); - - // 3. If it is not, put it in the entity store - } - - /** - * 4. Update the block entity in the entity store to point to this entity - */ - - // addEntityStoreAction(blockView.view.state, tr, { - // type: "updateEntityProperties", - // payload: { - // draftId: `draft-${blockEntity.entityId}`, - // merge: false, - // properties: targetEntity.properties, - // }, - // }); +export const BlockContextMenu = forwardRef( + ({ popupState, blockSuggesterProps, entityId, entityStore, view }, ref) => { + const blockData = entityId ? entityStore.saved[entityId] : null; + const { data: users } = useUsers(); + const { value: userBlocks } = useUserBlocks(); + const { accountId } = useCurrentWorkspaceContext(); + const { fetchEntities } = useAccountEntities(); + const blockView = useBlockView(); - /** - * 5. Update the prosemirror tree to reflect this - */ - blockView.view.dispatch(tr); - }) - .catch(() => {}); - } - }, [blockData, accountId, blockView, fetchEntities]); - - if (blockData && !isBlockEntity(blockData)) { - throw new Error("BlockContextMenu linked to non-block entity"); - } - - const [searchText, setSearchText] = useState(""); - - const [menuState, setMenuState] = useState({ - currentView: "normal", - selectedIndex: 0, - subMenuVisible: false, - }); - - const { currentView, selectedIndex, subMenuVisible } = menuState; - - const updateMenuState = (updatedState: Partial) => { - setMenuState((currentMenuState) => ({ - ...currentMenuState, - ...updatedState, - })); - }; - - const { value: userBlocks } = useUserBlocks(); - - const usableMenuItems = MENU_ITEMS.filter(({ key }) => { - return key !== "copyLink" || entityId; - }); - - const searchableActions = usableMenuItems.filter( - (item) => item.key !== "switchBlock", - ); + // console.log("blockView => ", blockView); - const lowerCaseSearchText = searchText.toLocaleLowerCase(); + const entityStoreRef = useRef(entityStore); - const filteredActions = searchableActions.filter((item) => - item.title.toLocaleLowerCase().includes(lowerCaseSearchText), - ); - - const filteredBlocks = useFilteredBlocks(lowerCaseSearchText, userBlocks); - - const filteredMenuItems: FilteredMenuItems = { - actions: filteredActions, - blocks: filteredBlocks, - }; - - const search = (newSearchText: string) => { - unstable_batchedUpdates(() => { - setSearchText(newSearchText); - - if (!newSearchText) { - if (currentView !== "normal") { - updateMenuState({ - currentView: "normal", - selectedIndex: 0, - subMenuVisible: false, - }); - } - } else if (currentView !== "search") { - updateMenuState({ - currentView: "search", - selectedIndex: 0, - subMenuVisible: false, - }); - } + useEffect(() => { + entityStoreRef.current = entityStore; }); - }; - - const getNextIndex = (event: KeyboardEvent, maxLength: number) => { - let index = selectedIndex + (event.key === "ArrowUp" ? -1 : 1); - index += maxLength; - index %= maxLength; - return index; - }; - - useKey(["ArrowUp", "ArrowDown"], (event) => { - event.preventDefault(); - if (subMenuVisible) return; - - if (currentView === "normal") { - const nextIndex = getNextIndex(event, usableMenuItems.length); - updateMenuState({ selectedIndex: nextIndex }); - } else { - const filteredItemsLength = - filteredMenuItems.actions.length + filteredMenuItems.blocks.length; - - const nextIndex = getNextIndex(event, filteredItemsLength); - updateMenuState({ selectedIndex: nextIndex }); + // const blockEntity = isBlockEntity(blockData) + // ? blockData.properties.entity + // : null; + + useEffect(() => { + // if (isBlockEntity(blockData) && accountId) { + // const blockEntity = blockData.properties.entity; + // fetchEntities(accountId, { + // componentId: blockData.properties.componentId, + // }) + // .then((entities) => { + // // debugger; + // if (entities.length === 0) return; + // const currentEntityStore = entityStoreRef.current; + // // @todo UI for picking the entity + // const targetEntity = entities[0]!; + // if (targetEntity.entityId === blockEntity.entityId) return; + // const tr = blockView.view.state.tr; + // const draftEntity = Object.values(currentEntityStore.draft).find( + // (entity) => entity.entityId === targetEntity.entityId, + // ); + // if (!draftEntity) { + // const draftId = newDraftId(); + // addEntityStoreAction(blockView.view.state, tr, { + // type: "newDraftEntity", + // payload: { + // accountId: targetEntity.accountId, + // draftId, + // entityId: targetEntity.entityId, + // }, + // }); + // addEntityStoreAction(blockView.view.state, tr, { + // type: "updateEntityProperties", + // payload: { + // draftId, + // merge: false, + // properties: targetEntity.properties, + // }, + // }); + // blockView.view.dispatch(tr); + // const updatedStore = entityStorePluginStateFromTransaction( + // tr, + // blockView.view.state, + // ); + // console.log("updatedStore ==> ", updatedStore); + // blockView.manager + // .createRemoteBlock( + // blockData.properties.componentId, + // updatedStore.store, + // `draft-${blockEntity.entityId}`, + // ) + // .then(() => {}) + // .catch(() => {}); + // // 3. If it is not, put it in the entity store + // } + // /** + // * 4. Update the block entity in the entity store to point to this entity + // */ + // // addEntityStoreAction(blockView.view.state, tr, { + // // type: "updateEntityProperties", + // // payload: { + // // draftId: `draft-${blockEntity.entityId}`, + // // merge: false, + // // properties: targetEntity.properties, + // // }, + // // }); + // /** + // * 5. Update the prosemirror tree to reflect this + // */ + // blockView.view.dispatch(tr); + // }) + // .catch(() => {}); + // } + }, [blockData, accountId, blockView, fetchEntities]); + + if (blockData && !isBlockEntity(blockData)) { + throw new Error("BlockContextMenu linked to non-block entity"); } - }); - useKey(["ArrowLeft", "ArrowRight"], (event) => { - if (usableMenuItems[selectedIndex]?.key === "switchBlock") { - updateMenuState({ subMenuVisible: event.key === "ArrowRight" }); - } - }); - - useKey(["Escape"], () => { - closeMenu(); - }); - - const onItemClick: ItemClickMethod = (key) => { - // handle menu item click here - switch (key) { - case "delete": - break; - case "switchBlock": - updateMenuState({ subMenuVisible: !subMenuVisible }); - - break; - case "copyLink": { - const url = new URL(document.location.href); - url.hash = getBlockDomId(entityId!); - void navigator.clipboard.writeText(url.toString()); - break; - } - } - - if (key !== "switchBlock") { - closeMenu(); - } - }; + const [menuState, setMenuState] = useState({ + currentView: "normal", + selectedIndex: 0, + subMenuVisible: false, + }); - const onNormalViewEnter = () => { - // if switchBlock is selected, make the block suggestor menu visible, else select the selected action - if (usableMenuItems[selectedIndex]?.key === "switchBlock") { - updateMenuState({ subMenuVisible: true }); - } else { - onItemClick(usableMenuItems[selectedIndex]!.key); - } - }; + const { currentView, selectedIndex, subMenuVisible } = menuState; + + const menuItems = useMemo(() => { + return [ + { + key: "add", + title: "Add an entity", + icon: , + }, + { + key: "copyLink", + title: "Copy Link", + icon: , + onClick: () => { + const url = new URL(document.location.href); + url.hash = getBlockDomId(entityId!); + void navigator.clipboard.writeText(url.toString()); + }, + }, + { + key: "duplicate", + title: "Duplicate", + icon: , + }, + { + key: "delete", + title: "Delete", + icon: , + }, + { + key: "swap-block", + title: "Swap block type", + icon: , + }, + { + key: "move-to-page", + title: "Move to page", + icon: , + }, + { + key: "comment", + title: "Comment", + icon: , + }, + ]; + }, [entityId]); + + const usableMenuItems = menuItems.filter(({ key }) => { + return key !== "copyLink" || entityId; + }); - const onSearchViewEnter = () => { - // if selected item is an action, execute the action, else convert the current block to the selected block - if (selectedIndex < filteredMenuItems.actions.length) { - onItemClick(filteredMenuItems.actions[selectedIndex]!.key); - } else { - const selectedBlock = - filteredMenuItems.blocks[ - selectedIndex - filteredMenuItems.actions.length - ]!; - blockSuggesterProps.onChange(selectedBlock.variant, selectedBlock.meta); - } - }; + useKey(["Escape"], () => { + popupState.close(); + }); - return ( - - {/*
    */} -
    - + + {/* { @@ -360,30 +297,78 @@ export const BlockContextMenu: React.VFC = ({ } } }} - /> - -
    - {currentView === "normal" ? ( - - ) : ( - - )} - {/*
    */} -
    - ); -}; + /> */} + + + + {usableMenuItems.map(({ key, title, icon, onClick }, index) => { + return ( + + // ) : null + // } + /> + ); + })} + + + + ({ + color: palette.gray[70], + display: "block", + })} + > + Last edited by {/* @todo use lastedited value when available */} + { + users.find( + (account) => + account.entityId === + blockData?.properties.entity.createdByAccountId, + )?.name + } + + + {typeof blockData?.properties.entity.updatedAt === "string" && ( + ({ + color: palette.gray[70], + })} + > + {format( + new Date(blockData.properties.entity.updatedAt), + "hh.mm a", + )} + {", "} + {format( + new Date(blockData.properties.entity.updatedAt), + "dd/MM/yyyy", + )} + + )} + + + ); + }, +); diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx index 2050b324a50..af8f43ca6e7 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx @@ -1,14 +1,16 @@ import { ListItemIcon, ListItemText, MenuItem } from "@mui/material"; -import { bindPopover, usePopupState } from "material-ui-popup-state/hooks"; +import { + bindPopover, + bindTrigger, + usePopupState, +} from "material-ui-popup-state/hooks"; import { VFC } from "react"; -import { tw } from "twind"; -import { Popover } from "../../shared/ui"; export const BlockContextMenuItem: VFC< { - selected: boolean; - onClick: VoidFunction; - onSelect: (shouldShowSubMenu: boolean) => void; + selected?: boolean; + onClick?: VoidFunction; + onSelect?: (shouldShowSubMenu: boolean) => void; icon: JSX.Element; title: JSX.Element | string; } & ( @@ -16,50 +18,64 @@ export const BlockContextMenuItem: VFC< subMenuVisible?: undefined; subMenu?: undefined; } - | { subMenuVisible: boolean; subMenu: JSX.Element | null } + | { subMenuVisible?: boolean; subMenu?: JSX.Element | null } ) -> = ({ selected, onClick, onSelect, icon, title, subMenuVisible, subMenu }) => { +> = ({ selected, onClick, onSelect, icon, title, subMenu }) => { const subMenuPopupState = usePopupState({ variant: "popover", popupId: `menu-${title}-id`, // @todo think of a better id }); + const { onClick: triggerOnClick, ...triggerAttrs } = + bindTrigger(subMenuPopupState); + return ( - + subMenuPopupState.open(), + })} + onClick={(evt) => { + if (subMenu) { + triggerOnClick(evt); + } else { + onClick(); + } + }} + sx={{ + position: "relative", + }} + > {icon} - {subMenu ? ( + {/* {subMenu ? ( <> - subMenuPopupState.open()} - onFocus={() => subMenuPopupState.open()} - className={tw`ml-auto`} + + - → - - {subMenu} - ) : null} - {/* */} + ) : null} */} ); }; diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx index 4e51c41746a..cfba9ba33f3 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx @@ -1,3 +1,4 @@ +import { Collapse } from "@mui/material"; import React, { useState, useRef, FormEvent } from "react"; import { unstable_batchedUpdates } from "react-dom"; import { tw } from "twind"; @@ -76,11 +77,14 @@ export const BlockLoaderInput: React.VFC = () => { type="url" value={blockUrl} onChange={(event) => setBlockUrl(event.target.value)} + onKeyDown={(event) => { + event.stopPropagation(); + }} placeholder="Load Block from URL..." className={tw`mt-2 block w-full px-2 py-1 bg-gray-50 border-1 text-sm rounded-sm `} required /> - {blockUrl && ( + - )} + ); }; diff --git a/packages/hash/frontend/src/components/BlockContextMenu/NormalView.tsx b/packages/hash/frontend/src/components/BlockContextMenu/NormalView.tsx index 417f10813cf..6d3bcf64e70 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/NormalView.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/NormalView.tsx @@ -38,70 +38,42 @@ export const NormalView: VoidFunctionComponent = ({ return ( <> -
      - {usableMenuItems.map(({ title, icon, key }, index) => { - return ( - onItemClick(key)} - onSelect={(shouldShowSubMenu) => { - if (shouldShowSubMenu && key === "switchBlock") { - updateMenuState({ - subMenuVisible: true, - selectedIndex: index, - }); - } else { - updateMenuState({ selectedIndex: index }); - } - }} - icon={icon} - title={title} - subMenu={ - key === "switchBlock" ? ( - - ) : null - } - subMenuVisible={ - index === selectedIndex && - key === "switchBlock" && - subMenuVisible - } - /> - ); - })} -
    -
    -

    - Last edited by {/* @todo use lastedited value when available */} - { - users.find( - (account) => - account.entityId === - blockData?.properties.entity.createdByAccountId, - )?.name - } -

    - {typeof blockData?.properties.entity.updatedAt === "string" && ( -

    - {format(new Date(blockData.properties.entity.updatedAt), "hh.mm a")} - {", "} - {format( - new Date(blockData.properties.entity.updatedAt), - "dd/MM/yyyy", - )} -

    - )} -
    + {usableMenuItems.map(({ title, icon, key }, index) => { + return ( + onItemClick(key)} + // onSelect={(shouldShowSubMenu) => { + // if (shouldShowSubMenu && key === "switchBlock") { + // updateMenuState({ + // subMenuVisible: true, + // selectedIndex: index, + // }); + // } else { + // updateMenuState({ selectedIndex: index }); + // } + // }} + icon={icon} + title={title} + subMenu={ + key === "switchBlock" ? ( + + ) : null + } + // subMenuVisible={ + // index === selectedIndex && key === "switchBlock" && subMenuVisible + // } + /> + ); + })} ); }; diff --git a/packages/hash/frontend/src/components/layout/MainContentWrapper.tsx b/packages/hash/frontend/src/components/layout/MainContentWrapper.tsx index a447569f0ec..1d2b5f138df 100644 --- a/packages/hash/frontend/src/components/layout/MainContentWrapper.tsx +++ b/packages/hash/frontend/src/components/layout/MainContentWrapper.tsx @@ -15,7 +15,9 @@ const Main = styled("main", { height: `calc(100vh - ${HEADER_HEIGHT}px)`, overflowY: "auto", flexGrow: 1, - paddingTop: "60px", + paddingTop: theme.spacing(7.5), + paddingLeft: theme.spacing(15), + paddingRight: theme.spacing(15), transition: theme.transitions.create("margin", { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, @@ -28,12 +30,6 @@ const Main = styled("main", { }), marginLeft: 0, }), - - "& > *": { - maxWidth: 700, - marginLeft: "auto", - marginRight: "auto", - }, })); export const MainContentWrapper: FunctionComponent = ({ children }) => { diff --git a/packages/hash/frontend/src/graphql/queries/entity.queries.ts b/packages/hash/frontend/src/graphql/queries/entity.queries.ts index 63921240f05..ed84eae3251 100644 --- a/packages/hash/frontend/src/graphql/queries/entity.queries.ts +++ b/packages/hash/frontend/src/graphql/queries/entity.queries.ts @@ -1,13 +1,23 @@ import { gql } from "@apollo/client"; +const entityFieldsFragment = gql` + fragment EntityFields on Entity { + accountId + entityId + entityTypeId + entityTypeName + entityTypeVersionId + ... on UnknownEntity { + properties + } + } +`; + export const getEntities = gql` query getEntities($accountId: ID!, $filter: EntityFilter) { entities(accountId: $accountId, filter: $filter) { - entityId - accountId - entityTypeId - entityTypeName - properties + ...EntityFields } } + ${entityFieldsFragment} `; diff --git a/packages/hash/frontend/src/pages/[accountId]/[pageEntityId].page.tsx b/packages/hash/frontend/src/pages/[accountId]/[pageEntityId].page.tsx index 386ec2bb658..a9f6740ebb9 100644 --- a/packages/hash/frontend/src/pages/[accountId]/[pageEntityId].page.tsx +++ b/packages/hash/frontend/src/pages/[accountId]/[pageEntityId].page.tsx @@ -12,6 +12,7 @@ import { GetPageQuery, GetPageQueryVariables, } from "@hashintel/hash-shared/graphql/apiTypes.gen"; +import { Container } from "@mui/material"; import { useCollabPositions } from "../../blocks/page/collab/useCollabPositions"; import { useCollabPositionTracking } from "../../blocks/page/collab/useCollabPositionTracking"; import { useCollabPositionReporter } from "../../blocks/page/collab/useCollabPositionReporter"; @@ -144,52 +145,54 @@ export const Page: React.VFC = ({ blocksMeta }) => { return ( -
    -
    -
    - -
    -
    - -
    - { - void router.push( - `/${accountId}/${pageEntityId}?version=${changedVersionId}`, - ); - }} + +
    +
    +
    +
    -
    -
    - +
    + +
    + { + void router.push( + `/${accountId}/${pageEntityId}?version=${changedVersionId}`, + ); + }} + /> +
    +
    - + +
    + +
    -
    -
    - -
    - - - -
    + + +
    + + + +
    +
    ); }; From 3d43a208ad9d1e31e95af4b2941d0a0c48dfffaa Mon Sep 17 00:00:00 2001 From: teenoh Date: Tue, 5 Apr 2022 14:58:13 +0100 Subject: [PATCH 05/42] update block suggester submenu ui --- .../BlockContextMenu/BlockContextMenu.tsx | 148 +++++++----------- .../BlockContextMenu/BlockContextMenuItem.tsx | 75 ++++----- .../BlockContextMenu/LoadEntityMenu.tsx | 21 +++ .../components/hooks/useAccountEntities.ts | 14 ++ 4 files changed, 124 insertions(+), 134 deletions(-) create mode 100644 packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenu.tsx diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx index 60232147e2b..cc27c3f189a 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx @@ -14,7 +14,15 @@ import { } from "@hashintel/hash-shared/entityStorePlugin"; import { EditorView } from "prosemirror-view"; import { Schema } from "prosemirror-model"; -import { Box, Divider, Menu, Typography } from "@mui/material"; +import { + Box, + Divider, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + Typography, +} from "@mui/material"; import { bindMenu } from "material-ui-popup-state"; import { PopupState } from "material-ui-popup-state/hooks"; import { format } from "date-fns"; @@ -30,7 +38,11 @@ import { faTrashCan, } from "@fortawesome/free-regular-svg-icons"; import { getBlockDomId } from "../../blocks/page/BlockView"; -import { BlockSuggesterProps } from "../../blocks/page/createSuggester/BlockSuggester"; +import { + BlockSuggester, + BlockSuggesterProps, + getVariantIcon, +} from "../../blocks/page/createSuggester/BlockSuggester"; import { NormalView } from "./NormalView"; import { SearchView } from "./SearchView"; import { @@ -49,6 +61,7 @@ import { useBlockView } from "../../blocks/page/BlockViewContext"; import { useUsers } from "../hooks/useUsers"; import { FontAwesomeIcon } from "../../shared/icons"; import { BlockContextMenuItem } from "./BlockContextMenuItem"; +import { LoadEntityMenu } from "./LoadEntityMenu"; type BlockContextMenuProps = { popupState: PopupState; @@ -58,44 +71,6 @@ type BlockContextMenuProps = { view: EditorView; }; -const MENU_ITEMS: Array = [ - { - key: "add", - title: "Add an entity", - icon: , - }, - { - key: "copyLink", - title: "Copy Link", - icon: , - }, - { - key: "duplicate", - title: "Duplicate", - icon: , - }, - { - key: "delete", - title: "Delete", - icon: , - }, - { - key: "swap-block", - title: "Swap block type", - icon: , - }, - { - key: "move-to-page", - title: "Move to page", - icon: , - }, - { - key: "comment", - title: "Comment", - icon: , - }, -]; - export const BlockContextMenu = forwardRef( ({ popupState, blockSuggesterProps, entityId, entityStore, view }, ref) => { const blockData = entityId ? entityStore.saved[entityId] : null; @@ -105,6 +80,8 @@ export const BlockContextMenu = forwardRef( const { fetchEntities } = useAccountEntities(); const blockView = useBlockView(); + const blocks = useFilteredBlocks("", userBlocks); + // console.log("blockView => ", blockView); const entityStoreRef = useRef(entityStore); @@ -192,20 +169,13 @@ export const BlockContextMenu = forwardRef( throw new Error("BlockContextMenu linked to non-block entity"); } - const [menuState, setMenuState] = useState({ - currentView: "normal", - selectedIndex: 0, - subMenuVisible: false, - }); - - const { currentView, selectedIndex, subMenuVisible } = menuState; - const menuItems = useMemo(() => { return [ { key: "add", title: "Add an entity", icon: , + subMenu: , }, { key: "copyLink", @@ -231,6 +201,30 @@ export const BlockContextMenu = forwardRef( key: "swap-block", title: "Swap block type", icon: , + subMenu: ( + <> + {blocks.map((option) => ( + { + blockSuggesterProps.onChange(option.variant, option.meta); + }} + key={`${option.meta.name}/${option.variant.name}`} + > + + + + + + ))} + + ), }, { key: "move-to-page", @@ -243,7 +237,7 @@ export const BlockContextMenu = forwardRef( icon: , }, ]; - }, [entityId]); + }, [entityId, blockSuggesterProps, blocks]); const usableMenuItems = menuItems.filter(({ key }) => { return key !== "copyLink" || entityId; @@ -278,51 +272,23 @@ export const BlockContextMenu = forwardRef( mb: 1, }} > - {/* { - search(event.target.value); - }} - className={tw`block w-full px-2 py-1 bg-gray-50 border-1 text-sm rounded-sm `} - placeholder="Filter actions..." - onKeyDown={(event) => { - // Is Enter causing a new-line? Read this: https://hashintel.slack.com/archives/C02K2ARC1BK/p1638433216067800 - if (event.key === "Enter") { - event.preventDefault(); - if (currentView === "normal") { - onNormalViewEnter(); - } else { - onSearchViewEnter(); - } - } - }} - /> */} - {usableMenuItems.map(({ key, title, icon, onClick }, index) => { - return ( - - // ) : null - // } - /> - ); - })} + {usableMenuItems.map( + ({ key, title, icon, onClick, subMenu }, index) => { + return ( + + ); + }, + )} void; - icon: JSX.Element; - title: JSX.Element | string; - } & ( - | { - subMenuVisible?: undefined; - subMenu?: undefined; - } - | { subMenuVisible?: boolean; subMenu?: JSX.Element | null } - ) -> = ({ selected, onClick, onSelect, icon, title, subMenu }) => { +export const BlockContextMenuItem: VFC<{ + itemKey: string; + onClick?: VoidFunction; + icon: JSX.Element; + title: JSX.Element | string; + subMenu?: JSX.Element; +}> = ({ onClick, icon, title, itemKey, subMenu }) => { const subMenuPopupState = usePopupState({ - variant: "popover", - popupId: `menu-${title}-id`, // @todo think of a better id + variant: "popper", + popupId: `${itemKey}-submenu`, }); - const { onClick: triggerOnClick, ...triggerAttrs } = - bindTrigger(subMenuPopupState); - return ( subMenuPopupState.open(), - })} - onClick={(evt) => { - if (subMenu) { - triggerOnClick(evt); - } else { - onClick(); - } - }} + {...(subMenu + ? { + ...bindHover(subMenuPopupState), + } + : { + onClick, + })} sx={{ position: "relative", }} > {icon} - {/* {subMenu ? ( + {subMenu ? ( <> - - ({ + ml: "auto", + color: palette.gray[50], + })} + /> + {subMenu} - + - ) : null} */} + ) : null} ); }; diff --git a/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenu.tsx new file mode 100644 index 00000000000..f390cd72e16 --- /dev/null +++ b/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenu.tsx @@ -0,0 +1,21 @@ +import { Box, MenuItem } from "@mui/material"; +import { useEffect } from "react"; +import { useAccountEntities } from "../hooks/useAccountEntities"; + +export const LoadEntityMenu = () => { + const accountId = ""; + const { data: entities, fetchEntities } = useAccountEntities(); + + useEffect(() => { + void fetchEntities(accountId, { entityTypeId: "" }); + }, [fetchEntities]); + + return ( + <> + + {[].map(() => { + return ; + })} + + ); +}; diff --git a/packages/hash/frontend/src/components/hooks/useAccountEntities.ts b/packages/hash/frontend/src/components/hooks/useAccountEntities.ts index ad3968aa8aa..614f35294fe 100644 --- a/packages/hash/frontend/src/components/hooks/useAccountEntities.ts +++ b/packages/hash/frontend/src/components/hooks/useAccountEntities.ts @@ -5,12 +5,23 @@ import { GetEntitiesQuery, GetEntitiesQueryVariables, EntityTypeChoice, + UnknownEntity, } from "../../graphql/apiTypes.gen"; // @todo properly type this export const useAccountEntities = () => { const [loading, setLoading] = useState(false); const [error, setError] = useState(); + const [data, setData] = useState< + Pick< + UnknownEntity, + | "accountId" + | "entityId" + | "entityTypeId" + | "entityTypeName" + | "properties" + >[] + >([]); const client = useApolloClient(); const fetchEntities = useCallback( @@ -30,6 +41,8 @@ export const useAccountEntities = () => { console.log("res => ", response.data.entities); + setData(response.data.entities); + return response.data.entities; }, [client], @@ -39,5 +52,6 @@ export const useAccountEntities = () => { fetchEntities, loading, error, + data, }; }; From 54e54d716cfa4181876e85676dfc7088a61bd1d3 Mon Sep 17 00:00:00 2001 From: teenoh Date: Tue, 5 Apr 2022 15:04:29 +0100 Subject: [PATCH 06/42] pull accountId from useRouteAccountInfo --- .../src/components/BlockContextMenu/BlockContextMenu.tsx | 4 ++-- .../src/components/BlockContextMenu/BlockContextMenuItem.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx index cc27c3f189a..06c490a1e48 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx @@ -56,12 +56,12 @@ import { BlockLoaderInput } from "./BlockLoaderInput"; import { useUserBlocks } from "../../blocks/userBlocks"; import { useFilteredBlocks } from "../../blocks/page/createSuggester/useFilteredBlocks"; import { useAccountEntities } from "../hooks/useAccountEntities"; -import { useCurrentWorkspaceContext } from "../../contexts/CurrentWorkspaceContext"; import { useBlockView } from "../../blocks/page/BlockViewContext"; import { useUsers } from "../hooks/useUsers"; import { FontAwesomeIcon } from "../../shared/icons"; import { BlockContextMenuItem } from "./BlockContextMenuItem"; import { LoadEntityMenu } from "./LoadEntityMenu"; +import { useRouteAccountInfo } from "../../shared/routing"; type BlockContextMenuProps = { popupState: PopupState; @@ -76,7 +76,7 @@ export const BlockContextMenu = forwardRef( const blockData = entityId ? entityStore.saved[entityId] : null; const { data: users } = useUsers(); const { value: userBlocks } = useUserBlocks(); - const { accountId } = useCurrentWorkspaceContext(); + const { accountId } = useRouteAccountInfo(); const { fetchEntities } = useAccountEntities(); const blockView = useBlockView(); diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx index 968bff1e272..600e01b6424 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx @@ -43,6 +43,7 @@ export const BlockContextMenuItem: VFC<{ sx={({ palette }) => ({ ml: "auto", color: palette.gray[50], + fontSize: 12, })} /> Date: Thu, 7 Apr 2022 17:14:51 +0100 Subject: [PATCH 07/42] add xs size to textfield --- .../inputs/mui-outlined-input-theme-options.ts | 12 ++++++++---- packages/hash/frontend/theme-override.d.ts | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/hash/frontend/src/shared/ui/theme/components/inputs/mui-outlined-input-theme-options.ts b/packages/hash/frontend/src/shared/ui/theme/components/inputs/mui-outlined-input-theme-options.ts index d343ffc3c04..3abe7638817 100644 --- a/packages/hash/frontend/src/shared/ui/theme/components/inputs/mui-outlined-input-theme-options.ts +++ b/packages/hash/frontend/src/shared/ui/theme/components/inputs/mui-outlined-input-theme-options.ts @@ -17,11 +17,11 @@ export const MuiOutlinedInputThemeOptions: Components["MuiOutlinedInput"] borderRadius: `${textFieldBorderRadius}px`, paddingLeft: theme.spacing(2), paddingRight: theme.spacing(2), - boxShadow: theme.boxShadows.xs, // this should be part of our shadows + boxShadow: theme.boxShadows.xs, - ...(ownerState.size === "large" && { - paddingLeft: theme.spacing(2.5), - paddingRight: theme.spacing(2), + ...(ownerState.size === "xs" && { + paddingLeft: theme.spacing(1.5), + paddingRight: theme.spacing(1.5), }), "&.Mui-focused, &.Mui-focused:hover": { @@ -55,6 +55,10 @@ export const MuiOutlinedInputThemeOptions: Components["MuiOutlinedInput"] color: theme.palette.red[80], }), + ...(size === "xs" && { + ...theme.typography.smallTextLabels, + padding: theme.spacing(1, 0), + }), ...(size === "small" && { ...theme.typography.smallTextLabels, padding: theme.spacing(1.5, 0), diff --git a/packages/hash/frontend/theme-override.d.ts b/packages/hash/frontend/theme-override.d.ts index a02b685d423..143ee3f9345 100644 --- a/packages/hash/frontend/theme-override.d.ts +++ b/packages/hash/frontend/theme-override.d.ts @@ -185,6 +185,7 @@ declare module "@mui/lab/TreeItem" { declare module "@mui/material/TextField" { interface TextFieldPropsSizeOverrides { + xs: true; small: true; medium: true; large: true; @@ -193,6 +194,7 @@ declare module "@mui/material/TextField" { declare module "@mui/material/InputBase" { interface InputBasePropsSizeOverrides { + xs: true; small: true; medium: true; large: true; From 0749e29fca1e2695bcfd98c44725f0d748656166 Mon Sep 17 00:00:00 2001 From: teenoh Date: Thu, 7 Apr 2022 17:16:55 +0100 Subject: [PATCH 08/42] update load entity menu --- .../frontend/src/blocks/page/BlockView.tsx | 2 - .../BlockContextMenu/BlockContextMenu.tsx | 498 +++++++++--------- .../BlockContextMenu/BlockContextMenuItem.tsx | 23 +- .../BlockContextMenu/BlockLoaderInput.tsx | 14 +- .../BlockContextMenu/LoadEntityMenu.tsx | 21 - .../LoadEntityMenuContent.tsx | 174 ++++++ .../pages/[account-slug]/[page-slug].page.tsx | 30 +- .../frontend/src/pages/playground.page.tsx | 5 + .../workspace-switcher.tsx | 67 +-- 9 files changed, 500 insertions(+), 334 deletions(-) delete mode 100644 packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenu.tsx create mode 100644 packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx diff --git a/packages/hash/frontend/src/blocks/page/BlockView.tsx b/packages/hash/frontend/src/blocks/page/BlockView.tsx index 62432ec434a..64d8604a094 100644 --- a/packages/hash/frontend/src/blocks/page/BlockView.tsx +++ b/packages/hash/frontend/src/blocks/page/BlockView.tsx @@ -45,8 +45,6 @@ export const BlockHandle = forwardRef( popupId: "block-context-menu", }); - useOutsideClick(blockMenuRef, () => popupState.close()); - const blockSuggesterProps: BlockSuggesterProps = useMemo( () => ({ onChange: (variant, block) => { diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx index 06c490a1e48..ee541cd1ce1 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState, useRef, forwardRef, useMemo } from "react"; import { tw } from "twind"; -import { useKey } from "rooks"; +import { useKey, useOutsideClick } from "rooks"; import { unstable_batchedUpdates } from "react-dom"; import { EntityStore, isBlockEntity } from "@hashintel/hash-shared/entityStore"; @@ -21,6 +21,7 @@ import { ListItemText, Menu, MenuItem, + MenuList, Typography, } from "@mui/material"; import { bindMenu } from "material-ui-popup-state"; @@ -60,7 +61,7 @@ import { useBlockView } from "../../blocks/page/BlockViewContext"; import { useUsers } from "../hooks/useUsers"; import { FontAwesomeIcon } from "../../shared/icons"; import { BlockContextMenuItem } from "./BlockContextMenuItem"; -import { LoadEntityMenu } from "./LoadEntityMenu"; +import { LoadEntityMenuContent } from "./LoadEntityMenuContent"; import { useRouteAccountInfo } from "../../shared/routing"; type BlockContextMenuProps = { @@ -71,270 +72,277 @@ type BlockContextMenuProps = { view: EditorView; }; -export const BlockContextMenu = forwardRef( - ({ popupState, blockSuggesterProps, entityId, entityStore, view }, ref) => { - const blockData = entityId ? entityStore.saved[entityId] : null; - const { data: users } = useUsers(); - const { value: userBlocks } = useUserBlocks(); - const { accountId } = useRouteAccountInfo(); - const { fetchEntities } = useAccountEntities(); - const blockView = useBlockView(); +export const BlockContextMenu = forwardRef< + HTMLDivElement, + BlockContextMenuProps +>(({ popupState, blockSuggesterProps, entityId, entityStore, view }, ref) => { + const blockData = entityId ? entityStore.saved[entityId] : null; + const { data: users } = useUsers(); + const { value: userBlocks } = useUserBlocks(); + const { accountId } = useRouteAccountInfo(); + const { fetchEntities } = useAccountEntities(); + const blockView = useBlockView(); - const blocks = useFilteredBlocks("", userBlocks); + const blocks = useFilteredBlocks("", userBlocks); - // console.log("blockView => ", blockView); + // console.log("blockView => ", blockView); - const entityStoreRef = useRef(entityStore); + const entityStoreRef = useRef(entityStore); - useEffect(() => { - entityStoreRef.current = entityStore; - }); + useEffect(() => { + entityStoreRef.current = entityStore; + }); - // const blockEntity = isBlockEntity(blockData) - // ? blockData.properties.entity - // : null; + // const blockEntity = isBlockEntity(blockData) + // ? blockData.properties.entity + // : null; - useEffect(() => { - // if (isBlockEntity(blockData) && accountId) { - // const blockEntity = blockData.properties.entity; - // fetchEntities(accountId, { - // componentId: blockData.properties.componentId, - // }) - // .then((entities) => { - // // debugger; - // if (entities.length === 0) return; - // const currentEntityStore = entityStoreRef.current; - // // @todo UI for picking the entity - // const targetEntity = entities[0]!; - // if (targetEntity.entityId === blockEntity.entityId) return; - // const tr = blockView.view.state.tr; - // const draftEntity = Object.values(currentEntityStore.draft).find( - // (entity) => entity.entityId === targetEntity.entityId, - // ); - // if (!draftEntity) { - // const draftId = newDraftId(); - // addEntityStoreAction(blockView.view.state, tr, { - // type: "newDraftEntity", - // payload: { - // accountId: targetEntity.accountId, - // draftId, - // entityId: targetEntity.entityId, - // }, - // }); - // addEntityStoreAction(blockView.view.state, tr, { - // type: "updateEntityProperties", - // payload: { - // draftId, - // merge: false, - // properties: targetEntity.properties, - // }, - // }); - // blockView.view.dispatch(tr); - // const updatedStore = entityStorePluginStateFromTransaction( - // tr, - // blockView.view.state, - // ); - // console.log("updatedStore ==> ", updatedStore); - // blockView.manager - // .createRemoteBlock( - // blockData.properties.componentId, - // updatedStore.store, - // `draft-${blockEntity.entityId}`, - // ) - // .then(() => {}) - // .catch(() => {}); - // // 3. If it is not, put it in the entity store - // } - // /** - // * 4. Update the block entity in the entity store to point to this entity - // */ - // // addEntityStoreAction(blockView.view.state, tr, { - // // type: "updateEntityProperties", - // // payload: { - // // draftId: `draft-${blockEntity.entityId}`, - // // merge: false, - // // properties: targetEntity.properties, - // // }, - // // }); - // /** - // * 5. Update the prosemirror tree to reflect this - // */ - // blockView.view.dispatch(tr); - // }) - // .catch(() => {}); - // } - }, [blockData, accountId, blockView, fetchEntities]); + useEffect(() => { + // if (isBlockEntity(blockData) && accountId) { + // const blockEntity = blockData.properties.entity; + // fetchEntities(accountId, { + // componentId: blockData.properties.componentId, + // }) + // .then((entities) => { + // // debugger; + // if (entities.length === 0) return; + // const currentEntityStore = entityStoreRef.current; + // // @todo UI for picking the entity + // const targetEntity = entities[0]!; + // if (targetEntity.entityId === blockEntity.entityId) return; + // const tr = blockView.view.state.tr; + // const draftEntity = Object.values(currentEntityStore.draft).find( + // (entity) => entity.entityId === targetEntity.entityId, + // ); + // if (!draftEntity) { + // const draftId = newDraftId(); + // addEntityStoreAction(blockView.view.state, tr, { + // type: "newDraftEntity", + // payload: { + // accountId: targetEntity.accountId, + // draftId, + // entityId: targetEntity.entityId, + // }, + // }); + // addEntityStoreAction(blockView.view.state, tr, { + // type: "updateEntityProperties", + // payload: { + // draftId, + // merge: false, + // properties: targetEntity.properties, + // }, + // }); + // blockView.view.dispatch(tr); + // const updatedStore = entityStorePluginStateFromTransaction( + // tr, + // blockView.view.state, + // ); + // console.log("updatedStore ==> ", updatedStore); + // blockView.manager + // .createRemoteBlock( + // blockData.properties.componentId, + // updatedStore.store, + // `draft-${blockEntity.entityId}`, + // ) + // .then(() => {}) + // .catch(() => {}); + // // 3. If it is not, put it in the entity store + // } + // /** + // * 4. Update the block entity in the entity store to point to this entity + // */ + // // addEntityStoreAction(blockView.view.state, tr, { + // // type: "updateEntityProperties", + // // payload: { + // // draftId: `draft-${blockEntity.entityId}`, + // // merge: false, + // // properties: targetEntity.properties, + // // }, + // // }); + // /** + // * 5. Update the prosemirror tree to reflect this + // */ + // blockView.view.dispatch(tr); + // }) + // .catch(() => {}); + // } + }, [blockData, accountId, blockView, fetchEntities]); - if (blockData && !isBlockEntity(blockData)) { - throw new Error("BlockContextMenu linked to non-block entity"); - } + if (blockData && !isBlockEntity(blockData)) { + throw new Error("BlockContextMenu linked to non-block entity"); + } - const menuItems = useMemo(() => { - return [ - { - key: "add", - title: "Add an entity", - icon: , - subMenu: , + const menuItems = useMemo(() => { + return [ + { + key: "add", + title: "Add an entity", + icon: , + subMenu: ( + + ), + subMenuWidth: 280, + }, + { + key: "copyLink", + title: "Copy Link", + icon: , + onClick: () => { + const url = new URL(document.location.href); + url.hash = getBlockDomId(entityId!); + void navigator.clipboard.writeText(url.toString()); }, - { - key: "copyLink", - title: "Copy Link", - icon: , - onClick: () => { - const url = new URL(document.location.href); - url.hash = getBlockDomId(entityId!); - void navigator.clipboard.writeText(url.toString()); - }, - }, - { - key: "duplicate", - title: "Duplicate", - icon: , - }, - { - key: "delete", - title: "Delete", - icon: , - }, - { - key: "swap-block", - title: "Swap block type", - icon: , - subMenu: ( - <> - {blocks.map((option) => ( - { - blockSuggesterProps.onChange(option.variant, option.meta); - }} - key={`${option.meta.name}/${option.variant.name}`} - > - - - - - - ))} - - ), - }, - { - key: "move-to-page", - title: "Move to page", - icon: , - }, - { - key: "comment", - title: "Comment", - icon: , - }, - ]; - }, [entityId, blockSuggesterProps, blocks]); + }, + { + key: "duplicate", + title: "Duplicate", + icon: , + }, + { + key: "delete", + title: "Delete", + icon: , + }, + { + key: "swap-block", + title: "Swap block type", + icon: , + subMenu: ( + + {blocks.map((option) => ( + { + blockSuggesterProps.onChange(option.variant, option.meta); + }} + key={`${option.meta.name}/${option.variant.name}`} + > + + + + + + ))} + + ), + subMenuWidth: 228, + }, + { + key: "move-to-page", + title: "Move to page", + icon: , + }, + { + key: "comment", + title: "Comment", + icon: , + }, + ]; + }, [entityId, blockSuggesterProps, blocks, entityStore]); - const usableMenuItems = menuItems.filter(({ key }) => { - return key !== "copyLink" || entityId; - }); + const usableMenuItems = menuItems.filter(({ key }) => { + return key !== "copyLink" || entityId; + }); - useKey(["Escape"], () => { - popupState.close(); - }); + useKey(["Escape"], () => { + popupState.close(); + }); - return ( - + - - - + + - {usableMenuItems.map( - ({ key, title, icon, onClick, subMenu }, index) => { - return ( - - ); - }, - )} + {usableMenuItems.map( + ({ key, title, icon, onClick, subMenu, subMenuWidth }) => { + return ( + + ); + }, + )} - - + + ({ + color: palette.gray[70], + display: "block", + })} > + Last edited by {/* @todo use lastedited value when available */} + { + users.find( + (account) => + account.entityId === + blockData?.properties.entity.createdByAccountId, + )?.name + } + + + {typeof blockData?.properties.entity.updatedAt === "string" && ( ({ color: palette.gray[70], - display: "block", })} > - Last edited by {/* @todo use lastedited value when available */} - { - users.find( - (account) => - account.entityId === - blockData?.properties.entity.createdByAccountId, - )?.name - } + {format(new Date(blockData.properties.entity.updatedAt), "hh.mm a")} + {", "} + {format( + new Date(blockData.properties.entity.updatedAt), + "dd/MM/yyyy", + )} - - {typeof blockData?.properties.entity.updatedAt === "string" && ( - ({ - color: palette.gray[70], - })} - > - {format( - new Date(blockData.properties.entity.updatedAt), - "hh.mm a", - )} - {", "} - {format( - new Date(blockData.properties.entity.updatedAt), - "dd/MM/yyyy", - )} - - )} - - - ); - }, -); + )} + + + ); +}); diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx index 600e01b6424..6ac1f3074d3 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx @@ -3,10 +3,11 @@ 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 { VFC } from "react"; +import { useEffect, useRef, useState, VFC } from "react"; import { FontAwesomeIcon } from "../../shared/icons"; export const BlockContextMenuItem: VFC<{ @@ -15,17 +16,29 @@ export const BlockContextMenuItem: VFC<{ icon: JSX.Element; title: JSX.Element | string; subMenu?: JSX.Element; -}> = ({ onClick, icon, title, itemKey, subMenu }) => { + subMenuWidth?: number; +}> = ({ onClick, icon, title, itemKey, subMenu, subMenuWidth }) => { const subMenuPopupState = usePopupState({ variant: "popper", popupId: `${itemKey}-submenu`, + // consider using parentPopupState }); + const [subMenuOffsetTop, setSubmenuOffsetTop] = useState< + number | undefined + >(); + const menuItemRef = useRef(null); + + if (subMenu && !subMenuOffsetTop && menuItemRef.current) { + setSubmenuOffsetTop(menuItemRef.current.offsetTop); + } return ( diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx index cfba9ba33f3..f6a207386a1 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx @@ -5,7 +5,7 @@ import { tw } from "twind"; import { useBlockView } from "../../blocks/page/BlockViewContext"; import { useUserBlocks } from "../../blocks/userBlocks"; -import { Button } from "../../shared/ui"; +import { Button, TextField } from "../../shared/ui"; /** trim whitespace and remove trailing slash */ const createNormalizedBlockUrl = (url: string) => url.trim().replace(/\/$/, ""); @@ -72,17 +72,13 @@ export const BlockLoaderInput: React.VFC = () => { return (
    - setBlockUrl(event.target.value)} - onKeyDown={(event) => { - event.stopPropagation(); - }} - placeholder="Load Block from URL..." - className={tw`mt-2 block w-full px-2 py-1 bg-gray-50 border-1 text-sm rounded-sm `} - required />
    -
    - -
    - { - void router.push( - `/${accountId}/${pageEntityId}?version=${changedVersionId}`, - ); - }} - /> -
    -
    +
    +
    +
    - -
    - -
    +
    diff --git a/packages/hash/frontend/src/pages/playground.page.tsx b/packages/hash/frontend/src/pages/playground.page.tsx index fac27e43bbe..91d56d5fda5 100644 --- a/packages/hash/frontend/src/pages/playground.page.tsx +++ b/packages/hash/frontend/src/pages/playground.page.tsx @@ -284,6 +284,11 @@ const Page: NextPageWithLayout = () => {

    + = () => { return ( - + + palette.gray[80], + fontWeight: 600, + }} + variant="smallTextLabels" + > + {activeWorkspace.name} + + palette.gray[70] }} + /> + + Date: Fri, 8 Apr 2022 15:55:10 +0100 Subject: [PATCH 09/42] tmp --- .../BlockContextMenu/BlockContextMenu.tsx | 102 +--------- .../LoadEntityMenuContent.tsx | 185 +++++++++++------- .../components/hooks/useAccountEntities.ts | 11 +- .../shared/src/ProsemirrorSchemaManager.ts | 28 +++ 4 files changed, 157 insertions(+), 169 deletions(-) diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx index ee541cd1ce1..d15cf1d5928 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx @@ -40,29 +40,17 @@ import { } from "@fortawesome/free-regular-svg-icons"; import { getBlockDomId } from "../../blocks/page/BlockView"; import { - BlockSuggester, BlockSuggesterProps, getVariantIcon, } from "../../blocks/page/createSuggester/BlockSuggester"; -import { NormalView } from "./NormalView"; -import { SearchView } from "./SearchView"; -import { - MenuItemType, - MenuState, - FilteredMenuItems, - ItemClickMethod, - iconStyles, -} from "./BlockContextMenuUtils"; + import { BlockLoaderInput } from "./BlockLoaderInput"; import { useUserBlocks } from "../../blocks/userBlocks"; import { useFilteredBlocks } from "../../blocks/page/createSuggester/useFilteredBlocks"; -import { useAccountEntities } from "../hooks/useAccountEntities"; -import { useBlockView } from "../../blocks/page/BlockViewContext"; import { useUsers } from "../hooks/useUsers"; import { FontAwesomeIcon } from "../../shared/icons"; import { BlockContextMenuItem } from "./BlockContextMenuItem"; import { LoadEntityMenuContent } from "./LoadEntityMenuContent"; -import { useRouteAccountInfo } from "../../shared/routing"; type BlockContextMenuProps = { popupState: PopupState; @@ -79,95 +67,15 @@ export const BlockContextMenu = forwardRef< const blockData = entityId ? entityStore.saved[entityId] : null; const { data: users } = useUsers(); const { value: userBlocks } = useUserBlocks(); - const { accountId } = useRouteAccountInfo(); - const { fetchEntities } = useAccountEntities(); - const blockView = useBlockView(); const blocks = useFilteredBlocks("", userBlocks); - // console.log("blockView => ", blockView); - const entityStoreRef = useRef(entityStore); useEffect(() => { entityStoreRef.current = entityStore; }); - // const blockEntity = isBlockEntity(blockData) - // ? blockData.properties.entity - // : null; - - useEffect(() => { - // if (isBlockEntity(blockData) && accountId) { - // const blockEntity = blockData.properties.entity; - // fetchEntities(accountId, { - // componentId: blockData.properties.componentId, - // }) - // .then((entities) => { - // // debugger; - // if (entities.length === 0) return; - // const currentEntityStore = entityStoreRef.current; - // // @todo UI for picking the entity - // const targetEntity = entities[0]!; - // if (targetEntity.entityId === blockEntity.entityId) return; - // const tr = blockView.view.state.tr; - // const draftEntity = Object.values(currentEntityStore.draft).find( - // (entity) => entity.entityId === targetEntity.entityId, - // ); - // if (!draftEntity) { - // const draftId = newDraftId(); - // addEntityStoreAction(blockView.view.state, tr, { - // type: "newDraftEntity", - // payload: { - // accountId: targetEntity.accountId, - // draftId, - // entityId: targetEntity.entityId, - // }, - // }); - // addEntityStoreAction(blockView.view.state, tr, { - // type: "updateEntityProperties", - // payload: { - // draftId, - // merge: false, - // properties: targetEntity.properties, - // }, - // }); - // blockView.view.dispatch(tr); - // const updatedStore = entityStorePluginStateFromTransaction( - // tr, - // blockView.view.state, - // ); - // console.log("updatedStore ==> ", updatedStore); - // blockView.manager - // .createRemoteBlock( - // blockData.properties.componentId, - // updatedStore.store, - // `draft-${blockEntity.entityId}`, - // ) - // .then(() => {}) - // .catch(() => {}); - // // 3. If it is not, put it in the entity store - // } - // /** - // * 4. Update the block entity in the entity store to point to this entity - // */ - // // addEntityStoreAction(blockView.view.state, tr, { - // // type: "updateEntityProperties", - // // payload: { - // // draftId: `draft-${blockEntity.entityId}`, - // // merge: false, - // // properties: targetEntity.properties, - // // }, - // // }); - // /** - // * 5. Update the prosemirror tree to reflect this - // */ - // blockView.view.dispatch(tr); - // }) - // .catch(() => {}); - // } - }, [blockData, accountId, blockView, fetchEntities]); - if (blockData && !isBlockEntity(blockData)) { throw new Error("BlockContextMenu linked to non-block entity"); } @@ -210,6 +118,7 @@ export const BlockContextMenu = forwardRef< key: "swap-block", title: "Swap block type", icon: , + // move to it's own component subMenu: ( {blocks.map((option) => ( @@ -276,6 +185,7 @@ export const BlockContextMenu = forwardRef< }} > ); }, @@ -313,7 +223,7 @@ export const BlockContextMenu = forwardRef< ({ - color: palette.gray[70], + color: palette.gray[60], display: "block", })} > @@ -331,7 +241,7 @@ export const BlockContextMenu = forwardRef< ({ - color: palette.gray[70], + color: palette.gray[60], })} > {format(new Date(blockData.properties.entity.updatedAt), "hh.mm a")} diff --git a/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx b/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx index d870c7a3377..074ea3b9c3d 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx @@ -12,41 +12,39 @@ import { ListItemText, MenuItem, MenuList, - Typography, } from "@mui/material"; -import { useEffect, useRef, VFC } from "react"; +import { useCallback, useEffect, VFC } from "react"; import { useBlockView } from "../../blocks/page/BlockViewContext"; import { FontAwesomeIcon } from "../../shared/icons"; import { useRouteAccountInfo } from "../../shared/routing"; -import { TextField } from "../../shared/ui"; +import { LoadingSpinner, TextField } from "../../shared/ui"; import { useAccountEntities } from "../hooks/useAccountEntities"; type LoadEntityMenuContentProps = { - entityId: string; + entityId: string | null; entityStore: EntityStore; }; export const LoadEntityMenuContent: VFC = ({ entityId, - entityStore, + // entityStore, }) => { const { accountId } = useRouteAccountInfo(); - const { data: entities, fetchEntities } = useAccountEntities(); + const { data: entities, fetchEntities, loading } = useAccountEntities(); const blockView = useBlockView(); + const entityStore = entityStorePluginStateFromTransaction( + blockView.view.state.tr, + blockView.view.state, + ).store; + // console.log("entityId --> ", entityId); const blockData = entityId ? entityStore.saved[entityId] : null; - const entityStoreRef = useRef(entityStore); - - useEffect(() => { - entityStoreRef.current = entityStore; - }); + console.log("block Data ==> ", blockData); const blockEntity = isBlockEntity(blockData) ? blockData.properties.entity : null; - console.log(Object.keys(entityStore.draft)); - useEffect(() => { if (isBlockEntity(blockData) && accountId) { void fetchEntities(accountId, { @@ -55,82 +53,125 @@ export const LoadEntityMenuContent: VFC = ({ } }, [blockData, accountId, fetchEntities]); - const handleClick = (id: string) => { - const targetData = entities.find((item) => item.entityId === id)!; - const targetEntity = isBlockEntity(targetData) - ? targetData.properties.entity - : null; - - if (!blockEntity) return; + const handleClick = useCallback( + (id: string) => { + const targetData = entities.find((item) => item.entityId === id)!; + const targetEntity = isBlockEntity(targetData) + ? targetData.properties.entity + : null; + + if ( + !targetEntity || + !blockEntity || + targetEntity.entityId === blockEntity.entityId + ) { + return; + } + + const tr = blockView.view.state.tr; + let currentEntityStore = entityStorePluginStateFromTransaction( + tr, + blockView.view.state, + ).store; - debugger; - if (targetEntity.entityId === blockEntity.entityId) return; + // check if entity exists in draft + let draftEntity = Object.values(currentEntityStore.draft).find( + (entity) => entity.entityId === targetEntity?.entityId, + ); - const currentEntityStore = entityStoreRef.current; - const tr = blockView.view.state.tr; + let draftId = ""; - // check if entity exists in draft - const draftEntity = Object.values(currentEntityStore.draft).find( - (entity) => entity.entityId === targetEntity?.entityId, - ); + // 3. If it is not, put it in the entity store + if (!draftEntity) { + draftId = newDraftId(); + addEntityStoreAction(blockView.view.state, tr, { + type: "newDraftEntity", + payload: { + accountId: targetEntity.accountId, + draftId, + entityId: targetEntity.entityId, + }, + }); + addEntityStoreAction(blockView.view.state, tr, { + type: "updateEntityProperties", + payload: { + draftId, + merge: false, + properties: targetEntity.properties, + }, + }); + + blockView.view.dispatch(tr); + } else { + draftId = draftEntity.draftId; + } + + currentEntityStore = entityStorePluginStateFromTransaction( + tr, + blockView.view.state, + ).store; - console.log("draftEntity ==> ", draftEntity); + draftEntity = currentEntityStore.draft[draftId]; - if (!draftEntity) { - const draftId = newDraftId(); - addEntityStoreAction(blockView.view.state, tr, { - type: "newDraftEntity", - payload: { - accountId: targetEntity.accountId, - draftId, - entityId: targetEntity.entityId, - }, - }); + // 4. Update the block entity in the entity store to point to this entity addEntityStoreAction(blockView.view.state, tr, { type: "updateEntityProperties", payload: { - draftId, + draftId: `draft-${blockData!.entityId}`, merge: false, - properties: targetEntity.properties, + properties: { + componentId: blockData?.properties.componentId, + entity: draftEntity!, + __typename: "BlockProperties", + }, }, }); - blockView.view.dispatch(tr); - - const updatedStore = entityStorePluginStateFromTransaction( + currentEntityStore = entityStorePluginStateFromTransaction( tr, blockView.view.state, - ); - console.log("updatedStore ==> ", updatedStore.store); - console.log(Object.keys(updatedStore.store.draft)); + ).store; + // console.log("updated store ==> ", currentEntityStore); + + // console.log( + // "updated block ==> ", + // currentEntityStore.draft[`draft-${blockData?.entityId}`], + // ); + + const pos = blockView.getPos(); + /** + * 5. Update the prosemirror tree to reflect this + * For now just insert the new block, + * can handle replacing once this works + */ // blockView.manager // .createRemoteBlock( // blockData?.properties.componentId!, - // updatedStore.store, - // `draft-${blockEntity.entityId}`, + // currentEntityStore, + // `draft-${blockData.entityId}`, // ) - // .then(() => {}) - // .catch(() => {}); - // 3. If it is not, put it in the entity store - } - - // 4. Update the block entity in the entity store to point to this entity + // .then((node) => { + // tr.replaceRangeWith(pos, pos + node.nodeSize, node); + // blockView.view.dispatch(tr); + // }) + // .catch((error) => { + // console.log(error); + // }); + + const node = blockView.manager.createLocalBlock( + blockData?.properties.componentId!, + currentEntityStore, + `draft-${blockData!.entityId}`, + ); - addEntityStoreAction(blockView.view.state, tr, { - type: "updateEntityProperties", - payload: { - draftId: `draft-${blockEntity.entityId}`, - merge: false, - properties: targetEntity.properties, - }, - }); + tr.replaceRangeWith(pos, pos + node.nodeSize, node); + blockView.view.dispatch(tr); - /** - * 5. Update the prosemirror tree to reflect this - */ - blockView.view.dispatch(tr); - }; + // blockView.view.dispatch(tr); + }, + [blockView, blockData, entities], + ); return ( @@ -148,6 +189,14 @@ export const LoadEntityMenuContent: VFC = ({ ), + endAdornment: loading ? ( + + + + ) : null, }} /> diff --git a/packages/hash/frontend/src/components/hooks/useAccountEntities.ts b/packages/hash/frontend/src/components/hooks/useAccountEntities.ts index 614f35294fe..3d67cc984cc 100644 --- a/packages/hash/frontend/src/components/hooks/useAccountEntities.ts +++ b/packages/hash/frontend/src/components/hooks/useAccountEntities.ts @@ -1,3 +1,4 @@ +import { unstable_batchedUpdates } from "react-dom"; import { useCallback, useState } from "react"; import { useApolloClient } from "@apollo/client"; import { getEntities } from "../../graphql/queries/entity.queries"; @@ -26,6 +27,7 @@ export const useAccountEntities = () => { const fetchEntities = useCallback( async (accountId: string, entityTypeFilter: EntityTypeChoice) => { + setLoading(true); const response = await client.query< GetEntitiesQuery, GetEntitiesQueryVariables @@ -39,11 +41,10 @@ export const useAccountEntities = () => { }, }); - console.log("res => ", response.data.entities); - - setData(response.data.entities); - - return response.data.entities; + unstable_batchedUpdates(() => { + setLoading(false); + setData(response.data.entities); + }); }, [client], ); diff --git a/packages/hash/shared/src/ProsemirrorSchemaManager.ts b/packages/hash/shared/src/ProsemirrorSchemaManager.ts index b9e95c9bbf4..022ba0c30d8 100644 --- a/packages/hash/shared/src/ProsemirrorSchemaManager.ts +++ b/packages/hash/shared/src/ProsemirrorSchemaManager.ts @@ -262,6 +262,34 @@ export class ProsemirrorSchemaManager { } } + /** + * @todo update comment + * Assumes the block info has already been fetched + */ + createLocalBlock( + targetComponentId: string, + entityStore?: EntityStore, + // @todo this needs to be mandatory otherwises properties may get lost + draftBlockId?: string | null, + ) { + debugger; + const blockEntity = draftBlockId ? entityStore?.draft[draftBlockId] : null; + + return this.schema.nodes.block!.create({}, [ + this.schema.nodes.entity!.create( + { draftId: draftBlockId }, + this.schema.nodes.entity!.create( + { + draftId: isDraftBlockEntity(blockEntity) + ? blockEntity.properties.entity.draftId + : null, + }, + [this.schema.nodes[targetComponentId]!.create({}, [])], + ), + ), + ]); + } + /** * Creating a new type of block in prosemirror, without necessarily having * requested the block metadata yet. From 93f75508083f2ad249e6d0ab17fbb71a2598b617 Mon Sep 17 00:00:00 2001 From: teenoh Date: Mon, 11 Apr 2022 09:01:40 +0100 Subject: [PATCH 10/42] create separate component for block list submenu --- .../BlockContextMenu/BlockListMenuContent.tsx | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 packages/hash/frontend/src/components/BlockContextMenu/BlockListMenuContent.tsx diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockListMenuContent.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockListMenuContent.tsx new file mode 100644 index 00000000000..359d5710b42 --- /dev/null +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockListMenuContent.tsx @@ -0,0 +1,87 @@ +import { faSearch } from "@fortawesome/free-solid-svg-icons"; +import { + Box, + InputAdornment, + ListItemIcon, + ListItemText, + MenuItem, + MenuList, + TextField, +} from "@mui/material"; +import { PopupState } from "material-ui-popup-state/core"; +import { useEffect, useRef, useState, VFC } from "react"; +import { + BlockSuggesterProps, + getVariantIcon, +} from "../../blocks/page/createSuggester/BlockSuggester"; +import { useFilteredBlocks } from "../../blocks/page/createSuggester/useFilteredBlocks"; +import { useUserBlocks } from "../../blocks/userBlocks"; +import { FontAwesomeIcon } from "../../shared/icons"; + +type BlockListMenuContentProps = { + popupState?: PopupState; + blockSuggesterProps: BlockSuggesterProps; +}; + +export const BlockListMenuContent: VFC = ({ + blockSuggesterProps, + popupState, +}) => { + const [searchQuery, setSearchQuery] = useState(""); + const searchInputRef = useRef(null); + const { value: userBlocks } = useUserBlocks(); + const blocks = useFilteredBlocks(searchQuery, userBlocks); + + useEffect(() => { + if (popupState?.isOpen) { + searchInputRef.current?.focus(); + } + }, [popupState]); + + return ( + + + { + setSearchQuery(evt.target.value); + }} + onKeyDown={(evt) => { + evt.stopPropagation(); + }} + value={searchQuery} + InputProps={{ + inputRef: searchInputRef, + startAdornment: ( + + + + ), + }} + /> + + {blocks.map((option) => ( + { + blockSuggesterProps.onChange(option.variant, option.meta); + }} + key={`${option.meta.name}/${option.variant.name}`} + > + + + + + + ))} + + ); +}; From c9c389a16de1e87f913ef99b244823295054c702 Mon Sep 17 00:00:00 2001 From: teenoh Date: Mon, 11 Apr 2022 09:03:45 +0100 Subject: [PATCH 11/42] add listeners for set keys to open submenus --- .../BlockContextMenu/BlockContextMenu.tsx | 155 +++++++----------- .../BlockContextMenu/BlockContextMenuItem.tsx | 21 ++- .../BlockContextMenu/BlockLoaderInput.tsx | 13 +- .../frontend/src/shared/ui/text-field.tsx | 2 +- 4 files changed, 80 insertions(+), 111 deletions(-) diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx index d15cf1d5928..d1c80d1769a 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx @@ -1,29 +1,11 @@ -import React, { useEffect, useState, useRef, forwardRef, useMemo } from "react"; -import { tw } from "twind"; - -import { useKey, useOutsideClick } from "rooks"; -import { unstable_batchedUpdates } from "react-dom"; +import React, { useRef, forwardRef, useMemo } from "react"; +import { useKey } from "rooks"; import { EntityStore, isBlockEntity } from "@hashintel/hash-shared/entityStore"; -import { - addEntityStoreAction, - entityStorePluginState, - entityStorePluginStateFromTransaction, - newDraftId, -} from "@hashintel/hash-shared/entityStorePlugin"; import { EditorView } from "prosemirror-view"; import { Schema } from "prosemirror-model"; -import { - Box, - Divider, - ListItemIcon, - ListItemText, - Menu, - MenuItem, - MenuList, - Typography, -} from "@mui/material"; +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"; @@ -39,18 +21,14 @@ import { faTrashCan, } from "@fortawesome/free-regular-svg-icons"; import { getBlockDomId } from "../../blocks/page/BlockView"; -import { - BlockSuggesterProps, - getVariantIcon, -} from "../../blocks/page/createSuggester/BlockSuggester"; +import { BlockSuggesterProps } from "../../blocks/page/createSuggester/BlockSuggester"; import { BlockLoaderInput } from "./BlockLoaderInput"; -import { useUserBlocks } from "../../blocks/userBlocks"; -import { useFilteredBlocks } from "../../blocks/page/createSuggester/useFilteredBlocks"; import { useUsers } from "../hooks/useUsers"; import { FontAwesomeIcon } from "../../shared/icons"; import { BlockContextMenuItem } from "./BlockContextMenuItem"; import { LoadEntityMenuContent } from "./LoadEntityMenuContent"; +import { BlockListMenuContent } from "./BlockListMenuContent"; type BlockContextMenuProps = { popupState: PopupState; @@ -63,18 +41,11 @@ type BlockContextMenuProps = { export const BlockContextMenu = forwardRef< HTMLDivElement, BlockContextMenuProps ->(({ popupState, blockSuggesterProps, entityId, entityStore, view }, ref) => { +>(({ popupState, blockSuggesterProps, entityId, entityStore }, ref) => { const blockData = entityId ? entityStore.saved[entityId] : null; const { data: users } = useUsers(); - const { value: userBlocks } = useUserBlocks(); - - const blocks = useFilteredBlocks("", userBlocks); - - const entityStoreRef = useRef(entityStore); - - useEffect(() => { - entityStoreRef.current = entityStore; - }); + const setEntityMenuItemRef = useRef(null); + const swapBlocksMenuItemRef = useRef(null); if (blockData && !isBlockEntity(blockData)) { throw new Error("BlockContextMenu linked to non-block entity"); @@ -83,9 +54,14 @@ export const BlockContextMenu = forwardRef< const menuItems = useMemo(() => { return [ { - key: "add", + key: "set-entity", title: "Add an entity", icon: , + // @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: ( , onClick: () => { @@ -118,30 +94,8 @@ export const BlockContextMenu = forwardRef< key: "swap-block", title: "Swap block type", icon: , - // move to it's own component subMenu: ( - - {blocks.map((option) => ( - { - blockSuggesterProps.onChange(option.variant, option.meta); - }} - key={`${option.meta.name}/${option.variant.name}`} - > - - - - - - ))} - + ), subMenuWidth: 228, }, @@ -156,16 +110,24 @@ export const BlockContextMenu = forwardRef< icon: , }, ]; - }, [entityId, blockSuggesterProps, blocks, entityStore]); - - const usableMenuItems = menuItems.filter(({ key }) => { - return key !== "copyLink" || entityId; - }); + }, [entityId, blockSuggesterProps, entityStore]); useKey(["Escape"], () => { popupState.close(); }); + useKey(["@"], () => { + if (popupState.isOpen) { + setEntityMenuItemRef.current?.focus(); + } + }); + + useKey(["/"], () => { + if (popupState.isOpen) { + swapBlocksMenuItemRef.current?.focus(); + } + }); + return ( - + - {usableMenuItems.map( - ({ key, title, icon, onClick, subMenu, subMenuWidth }) => { - return ( - - ); - }, - )} + {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 ( + + ); + })} - + ({ diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx index 6ac1f3074d3..1cdbadb9ca2 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenuItem.tsx @@ -7,26 +7,31 @@ import { usePopupState, } from "material-ui-popup-state/hooks"; import HoverPopover from "material-ui-popup-state/HoverPopover"; -import { useEffect, useRef, useState, VFC } from "react"; +import { cloneElement, forwardRef, RefObject, useRef, useState } from "react"; import { FontAwesomeIcon } from "../../shared/icons"; -export const BlockContextMenuItem: VFC<{ +type BlockContextMenuItemProps = { itemKey: string; - onClick?: VoidFunction; + onClick?: () => void; icon: JSX.Element; title: JSX.Element | string; subMenu?: JSX.Element; subMenuWidth?: number; -}> = ({ onClick, icon, title, itemKey, subMenu, subMenuWidth }) => { +}; + +export const BlockContextMenuItem = forwardRef< + HTMLLIElement, + BlockContextMenuItemProps +>(({ onClick, icon, title, itemKey, subMenu, subMenuWidth }, ref) => { const subMenuPopupState = usePopupState({ variant: "popper", popupId: `${itemKey}-submenu`, - // consider using parentPopupState }); const [subMenuOffsetTop, setSubmenuOffsetTop] = useState< number | undefined >(); - const menuItemRef = useRef(null); + const localRef = useRef(null); + const menuItemRef = (ref ?? localRef) as RefObject; if (subMenu && !subMenuOffsetTop && menuItemRef.current) { setSubmenuOffsetTop(menuItemRef.current.offsetTop); @@ -79,10 +84,10 @@ export const BlockContextMenuItem: VFC<{ }, }} > - {subMenu} + {cloneElement(subMenu, { popupState: subMenuPopupState })} ) : null} ); -}; +}); diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx index f6a207386a1..408f7116215 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx @@ -1,7 +1,6 @@ -import { Collapse } from "@mui/material"; +import { Box, Collapse } from "@mui/material"; import React, { useState, useRef, FormEvent } from "react"; import { unstable_batchedUpdates } from "react-dom"; -import { tw } from "twind"; import { useBlockView } from "../../blocks/page/BlockViewContext"; import { useUserBlocks } from "../../blocks/userBlocks"; @@ -71,13 +70,19 @@ export const BlockLoaderInput: React.VFC = () => { }; return ( - + setBlockUrl(event.target.value)} /> @@ -99,6 +104,6 @@ export const BlockLoaderInput: React.VFC = () => { : "Load Block"} - + ); }; diff --git a/packages/hash/frontend/src/shared/ui/text-field.tsx b/packages/hash/frontend/src/shared/ui/text-field.tsx index a7e2ab61f73..9a4ec8592e5 100644 --- a/packages/hash/frontend/src/shared/ui/text-field.tsx +++ b/packages/hash/frontend/src/shared/ui/text-field.tsx @@ -106,7 +106,7 @@ export const TextField: VFC = ({ }} helperText={ - {helperText} + {helperText} } FormHelperTextProps={{ From 8eb95926ffd8ad180256b81fbc0ca439da32df1b Mon Sep 17 00:00:00 2001 From: teenoh Date: Mon, 11 Apr 2022 15:31:31 +0100 Subject: [PATCH 12/42] add action for updating block properties with temp fix to bug --- .../shared/src/ProsemirrorSchemaManager.ts | 1 - packages/hash/shared/src/entityStorePlugin.ts | 73 ++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/packages/hash/shared/src/ProsemirrorSchemaManager.ts b/packages/hash/shared/src/ProsemirrorSchemaManager.ts index 022ba0c30d8..2ef875ca417 100644 --- a/packages/hash/shared/src/ProsemirrorSchemaManager.ts +++ b/packages/hash/shared/src/ProsemirrorSchemaManager.ts @@ -272,7 +272,6 @@ export class ProsemirrorSchemaManager { // @todo this needs to be mandatory otherwises properties may get lost draftBlockId?: string | null, ) { - debugger; const blockEntity = draftBlockId ? entityStore?.draft[draftBlockId] : null; return this.schema.nodes.block!.create({}, [ diff --git a/packages/hash/shared/src/entityStorePlugin.ts b/packages/hash/shared/src/entityStorePlugin.ts index 77cdae007f0..98244265230 100644 --- a/packages/hash/shared/src/entityStorePlugin.ts +++ b/packages/hash/shared/src/entityStorePlugin.ts @@ -72,6 +72,10 @@ export type EntityStorePluginAction = { received?: boolean } & ( entityId: string; }; } + | { + type: "updateBlockEntityProperties"; + payload: { draftId: string; targetEntity: BlockEntity }; + } ); const entityStorePluginKey = new PluginKey( @@ -113,6 +117,8 @@ export const entityStorePluginStateFromTransaction = ( ): EntityStorePluginState => getMeta(tr)?.store ?? entityStorePluginState(state); +export const newDraftId = () => `fake-${uuid()}`; + /** * As we're not yet working with a totally flat entity store, the same * entity can exist in multiple places in a draft entity store. This @@ -151,6 +157,49 @@ const updateEntitiesByDraftId = ( } }; +const updateBlockEntity = ( + draftEntityStore: Draft, + draftId: string, + targetEntity: BlockEntity, +) => { + let draftEntity = Object.values(draftEntityStore).find( + (entity) => entity.entityId === targetEntity?.entityId, + ); + + // Check if entity to load exists in store + // and add to store if it doesnt + if (!draftEntity) { + draftEntity = { + accountId: targetEntity.accountId, + draftId: newDraftId(), + entityId: targetEntity.entityId, + properties: targetEntity.properties, + entityVersionCreatedAt: new Date().toISOString(), + }; + + draftEntityStore[draftId] = draftEntity; + } + + const draftBlockEntity = draftEntityStore[draftId]; + + if (!draftBlockEntity) { + throw new Error("Block to update not present in store"); + } + + // we shouldn't need to update this since the api is meant to + // handle it. + // @todo remove the need for this + draftBlockEntity.entityVersionCreatedAt = new Date().toISOString(); + + draftBlockEntity.properties = { + entity: draftEntity, + }; + + draftEntityStore[draftId] = draftBlockEntity; + + return draftEntityStore; +}; + /** * We currently violate Immer's rules, as properties inside entities can be * other entities themselves, and we expect `entity.property.entity` to be @@ -222,6 +271,28 @@ const entityStoreReducer = ( }); } + case "updateBlockEntityProperties": { + if (!state.store.draft[action.payload.draftId]) { + throw new Error("Block missing to merge entity properties"); + } + + if (!action.payload.targetEntity) { + throw new Error("Entity missing to merge Block entity properties"); + } + + return produce(state, (draftState) => { + if (!action.received) { + draftState.trackedActions.push({ action, id: uuid() }); + } + + updateBlockEntity( + draftState.store.draft, + action.payload.draftId, + action.payload.targetEntity, + ); + }); + } + case "updateEntityId": { if (!state.store.draft[action.payload.draftId]) { throw new Error("Entity missing to update entity id"); @@ -353,8 +424,6 @@ const getRequiredDraftIdFromEntityNode = (entityNode: EntityNode): string => { return entityNode.attrs.draftId; }; -export const newDraftId = () => `fake-${uuid()}`; - class ProsemirrorStateChangeHandler { private readonly tr: Transaction; private handled = false; From d9513cfd5979a47c30925b57d2cb3f14460362aa Mon Sep 17 00:00:00 2001 From: teenoh Date: Mon, 11 Apr 2022 15:32:16 +0100 Subject: [PATCH 13/42] add feature flag --- packages/hash/frontend/next.config.js | 2 + .../BlockContextMenu/BlockContextMenu.tsx | 8 +- .../LoadEntityMenuContent.tsx | 147 +++++------------- 3 files changed, 52 insertions(+), 105 deletions(-) diff --git a/packages/hash/frontend/next.config.js b/packages/hash/frontend/next.config.js index 8ac893d144e..a95ad40d449 100644 --- a/packages/hash/frontend/next.config.js +++ b/packages/hash/frontend/next.config.js @@ -28,6 +28,8 @@ process.env.NEXT_PUBLIC_BLOCK_BASED_ENTITY_EDITOR = process.env.NEXT_PUBLIC_BLOCK_BASED_ENTITY_EDITOR ?? process.env.BLOCK_BASED_ENTITY_EDITOR; +process.env.NEXT_PUBLIC_LOAD_BLOCK_ENTITY_UI = process.env.LOAD_BLOCK_ENTITY_UI; + /** * @todo try using next-compose-plugins when upgrading next to 11 and/or to webpack 5 * was not building with compose-plugins on next 10 w/ webpack 4. diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx index d1c80d1769a..489a0142049 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx @@ -52,7 +52,7 @@ export const BlockContextMenu = forwardRef< } const menuItems = useMemo(() => { - return [ + const items = [ { key: "set-entity", title: "Add an entity", @@ -110,6 +110,12 @@ export const BlockContextMenu = forwardRef< icon: , }, ]; + + if (!process.env.NEXT_PUBLIC_LOAD_BLOCK_ENTITY_UI) { + items.shift(); + } + + return items; }, [entityId, blockSuggesterProps, entityStore]); useKey(["Escape"], () => { diff --git a/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx b/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx index 074ea3b9c3d..98126f59e00 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx @@ -1,9 +1,9 @@ import { faAsterisk, faSearch } from "@fortawesome/free-solid-svg-icons"; +import { BlockEntity } from "@hashintel/hash-shared/entity"; import { EntityStore, isBlockEntity } from "@hashintel/hash-shared/entityStore"; import { addEntityStoreAction, entityStorePluginStateFromTransaction, - newDraftId, } from "@hashintel/hash-shared/entityStorePlugin"; import { Box, @@ -13,7 +13,8 @@ import { MenuItem, MenuList, } from "@mui/material"; -import { useCallback, useEffect, VFC } from "react"; +import { PopupState } from "material-ui-popup-state/core"; +import { useCallback, useEffect, useRef, VFC } from "react"; import { useBlockView } from "../../blocks/page/BlockViewContext"; import { FontAwesomeIcon } from "../../shared/icons"; import { useRouteAccountInfo } from "../../shared/routing"; @@ -23,45 +24,56 @@ import { useAccountEntities } from "../hooks/useAccountEntities"; type LoadEntityMenuContentProps = { entityId: string | null; entityStore: EntityStore; + popupState?: PopupState; }; export const LoadEntityMenuContent: VFC = ({ entityId, - // entityStore, + popupState, }) => { const { accountId } = useRouteAccountInfo(); const { data: entities, fetchEntities, loading } = useAccountEntities(); const blockView = useBlockView(); - const entityStore = entityStorePluginStateFromTransaction( - blockView.view.state.tr, - blockView.view.state, - ).store; - // console.log("entityId --> ", entityId); - const blockData = entityId ? entityStore.saved[entityId] : null; + const searchInputRef = useRef(null); - console.log("block Data ==> ", blockData); - - const blockEntity = isBlockEntity(blockData) - ? blockData.properties.entity - : null; + useEffect(() => { + if (popupState?.isOpen) { + searchInputRef.current?.focus(); + } + }, [popupState]); useEffect(() => { + const entityStore = entityStorePluginStateFromTransaction( + blockView.view.state.tr, + blockView.view.state, + ).store; + const blockData = entityId ? entityStore.saved[entityId] : null; + if (isBlockEntity(blockData) && accountId) { void fetchEntities(accountId, { componentId: blockData.properties.componentId, }); } - }, [blockData, accountId, fetchEntities]); + }, [blockView, entityId, accountId, fetchEntities]); const handleClick = useCallback( - (id: string) => { - const targetData = entities.find((item) => item.entityId === id)!; + (targetData: BlockEntity) => { + let currentEntityStore = entityStorePluginStateFromTransaction( + blockView.view.state.tr, + blockView.view.state, + ).store; + const blockData = entityId ? currentEntityStore.saved[entityId] : null; + const targetEntity = isBlockEntity(targetData) ? targetData.properties.entity : null; + const blockEntity = isBlockEntity(blockData) + ? blockData.properties.entity + : null; if ( !targetEntity || + !blockData || !blockEntity || targetEntity.entityId === blockEntity.entityId ) { @@ -69,61 +81,12 @@ export const LoadEntityMenuContent: VFC = ({ } const tr = blockView.view.state.tr; - let currentEntityStore = entityStorePluginStateFromTransaction( - tr, - blockView.view.state, - ).store; - // check if entity exists in draft - let draftEntity = Object.values(currentEntityStore.draft).find( - (entity) => entity.entityId === targetEntity?.entityId, - ); - - let draftId = ""; - - // 3. If it is not, put it in the entity store - if (!draftEntity) { - draftId = newDraftId(); - addEntityStoreAction(blockView.view.state, tr, { - type: "newDraftEntity", - payload: { - accountId: targetEntity.accountId, - draftId, - entityId: targetEntity.entityId, - }, - }); - addEntityStoreAction(blockView.view.state, tr, { - type: "updateEntityProperties", - payload: { - draftId, - merge: false, - properties: targetEntity.properties, - }, - }); - - blockView.view.dispatch(tr); - } else { - draftId = draftEntity.draftId; - } - - currentEntityStore = entityStorePluginStateFromTransaction( - tr, - blockView.view.state, - ).store; - - draftEntity = currentEntityStore.draft[draftId]; - - // 4. Update the block entity in the entity store to point to this entity addEntityStoreAction(blockView.view.state, tr, { - type: "updateEntityProperties", + type: "updateBlockEntityProperties", payload: { - draftId: `draft-${blockData!.entityId}`, - merge: false, - properties: { - componentId: blockData?.properties.componentId, - entity: draftEntity!, - __typename: "BlockProperties", - }, + targetEntity, + draftId: `draft-${blockData.entityId}`, }, }); @@ -132,47 +95,25 @@ export const LoadEntityMenuContent: VFC = ({ blockView.view.state, ).store; - // console.log("updated store ==> ", currentEntityStore); - - // console.log( - // "updated block ==> ", - // currentEntityStore.draft[`draft-${blockData?.entityId}`], - // ); - const pos = blockView.getPos(); - /** - * 5. Update the prosemirror tree to reflect this - * For now just insert the new block, - * can handle replacing once this works - */ - // blockView.manager - // .createRemoteBlock( - // blockData?.properties.componentId!, - // currentEntityStore, - // `draft-${blockData.entityId}`, - // ) - // .then((node) => { - // tr.replaceRangeWith(pos, pos + node.nodeSize, node); - // blockView.view.dispatch(tr); - // }) - // .catch((error) => { - // console.log(error); - // }); const node = blockView.manager.createLocalBlock( - blockData?.properties.componentId!, + blockData?.properties.componentId, currentEntityStore, - `draft-${blockData!.entityId}`, + `draft-${blockData.entityId}`, ); tr.replaceRangeWith(pos, pos + node.nodeSize, node); blockView.view.dispatch(tr); - - // blockView.view.dispatch(tr); + popupState?.close(); }, - [blockView, blockData, entities], + [blockView, entityId, popupState], ); + // @todo filter entities displayed + // should only include block entities and + // should not include current entity displayed in the block + return ( @@ -180,10 +121,11 @@ export const LoadEntityMenuContent: VFC = ({ placeholder="Search for entities" fullWidth size="xs" - onChange={(evt) => { + onKeyDown={(evt) => { evt.stopPropagation(); }} InputProps={{ + inputRef: searchInputRef, startAdornment: ( @@ -202,10 +144,7 @@ export const LoadEntityMenuContent: VFC = ({ {entities.map((entity) => { return ( - handleClick(entity.entityId)} - > + handleClick(entity)}> From 9aca93b9a05228d0c39822414c9a346ac41e0e0d Mon Sep 17 00:00:00 2001 From: teenoh Date: Mon, 11 Apr 2022 16:14:23 +0100 Subject: [PATCH 14/42] fix lint issues --- .../frontend/src/blocks/page/BlockView.tsx | 13 +-- .../BlockContextMenu/BlockContextMenu.tsx | 9 +- .../BlockContextMenu/BlockListMenuContent.tsx | 14 +++ .../LoadEntityMenuContent.tsx | 3 +- .../BlockContextMenu/NormalView.tsx | 79 ---------------- .../BlockContextMenu/SearchView.tsx | 94 ------------------- .../components/hooks/useAccountEntities.ts | 4 +- .../pages/[account-slug]/[page-slug].page.tsx | 1 - packages/hash/shared/src/entityStorePlugin.ts | 6 +- 9 files changed, 21 insertions(+), 202 deletions(-) delete mode 100644 packages/hash/frontend/src/components/BlockContextMenu/NormalView.tsx delete mode 100644 packages/hash/frontend/src/components/BlockContextMenu/SearchView.tsx diff --git a/packages/hash/frontend/src/blocks/page/BlockView.tsx b/packages/hash/frontend/src/blocks/page/BlockView.tsx index 64d8604a094..dbe438b9bb5 100644 --- a/packages/hash/frontend/src/blocks/page/BlockView.tsx +++ b/packages/hash/frontend/src/blocks/page/BlockView.tsx @@ -5,22 +5,13 @@ import { } from "@hashintel/hash-shared/entityStorePlugin"; import { isEntityNode } from "@hashintel/hash-shared/prosemirror"; import { ProsemirrorSchemaManager } from "@hashintel/hash-shared/ProsemirrorSchemaManager"; -import { Box, Menu } from "@mui/material"; +import { Box } from "@mui/material"; import { BlockVariant } from "blockprotocol"; import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; import { ProsemirrorNode, Schema } from "prosemirror-model"; import { NodeSelection } from "prosemirror-state"; import { EditorView, NodeView } from "prosemirror-view"; -import { - createRef, - forwardRef, - RefObject, - RefObject, - useMemo, - useRef, - useState, -} from "react"; -import { useOutsideClick } from "rooks"; +import { createRef, forwardRef, useMemo, useRef } from "react"; import { BlockContextMenu } from "../../components/BlockContextMenu/BlockContextMenu"; import { DragVerticalIcon } from "../../shared/icons"; import { RemoteBlockMetadata } from "../userBlocks"; diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx index 489a0142049..9bf011ef917 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx @@ -62,12 +62,7 @@ export const BlockContextMenu = forwardRef< // in BlockContextMenuItem // For now the temporary fix is to make popupState optional // in LoadEntityMenuContent - subMenu: ( - - ), + subMenu: , subMenuWidth: 280, }, { @@ -116,7 +111,7 @@ export const BlockContextMenu = forwardRef< } return items; - }, [entityId, blockSuggesterProps, entityStore]); + }, [entityId, blockSuggesterProps]); useKey(["Escape"], () => { popupState.close(); diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockListMenuContent.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockListMenuContent.tsx index 359d5710b42..f0587055331 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockListMenuContent.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockListMenuContent.tsx @@ -7,6 +7,7 @@ import { MenuItem, MenuList, TextField, + Typography, } from "@mui/material"; import { PopupState } from "material-ui-popup-state/core"; import { useEffect, useRef, useState, VFC } from "react"; @@ -62,6 +63,19 @@ export const BlockListMenuContent: VFC = ({ }} /> + {blocks.length === 0 && ( + + ({ + color: palette.gray[60], + fontWeight: 500, + })} + > + No results for `{searchQuery}` + + + )} {blocks.map((option) => ( { diff --git a/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx b/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx index 98126f59e00..8f18b225a56 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/LoadEntityMenuContent.tsx @@ -1,6 +1,6 @@ import { faAsterisk, faSearch } from "@fortawesome/free-solid-svg-icons"; import { BlockEntity } from "@hashintel/hash-shared/entity"; -import { EntityStore, isBlockEntity } from "@hashintel/hash-shared/entityStore"; +import { isBlockEntity } from "@hashintel/hash-shared/entityStore"; import { addEntityStoreAction, entityStorePluginStateFromTransaction, @@ -23,7 +23,6 @@ import { useAccountEntities } from "../hooks/useAccountEntities"; type LoadEntityMenuContentProps = { entityId: string | null; - entityStore: EntityStore; popupState?: PopupState; }; diff --git a/packages/hash/frontend/src/components/BlockContextMenu/NormalView.tsx b/packages/hash/frontend/src/components/BlockContextMenu/NormalView.tsx deleted file mode 100644 index 6d3bcf64e70..00000000000 --- a/packages/hash/frontend/src/components/BlockContextMenu/NormalView.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { VoidFunctionComponent } from "react"; -import { tw } from "twind"; -import { format } from "date-fns"; -import { BlockEntity } from "@hashintel/hash-shared/entity"; - -import { - BlockSuggester, - BlockSuggesterProps, -} from "../../blocks/page/createSuggester/BlockSuggester"; -import { useUsers } from "../hooks/useUsers"; -import { BlockContextMenuItem } from "./BlockContextMenuItem"; -import { - MenuItemType, - MenuState, - ItemClickMethod, -} from "./BlockContextMenuUtils"; - -type NormalViewComponentProps = { - usableMenuItems: MenuItemType[]; - subMenuVisible: boolean; - updateMenuState: (updatedState: Partial) => void; - selectedIndex: number; - onItemClick: ItemClickMethod; - blockSuggesterProps: BlockSuggesterProps; - blockData: BlockEntity | null; -}; - -export const NormalView: VoidFunctionComponent = ({ - usableMenuItems, - selectedIndex, - subMenuVisible, - updateMenuState, - onItemClick, - blockSuggesterProps, - blockData, -}) => { - const { data: users } = useUsers(); - - return ( - <> - {usableMenuItems.map(({ title, icon, key }, index) => { - return ( - onItemClick(key)} - // onSelect={(shouldShowSubMenu) => { - // if (shouldShowSubMenu && key === "switchBlock") { - // updateMenuState({ - // subMenuVisible: true, - // selectedIndex: index, - // }); - // } else { - // updateMenuState({ selectedIndex: index }); - // } - // }} - icon={icon} - title={title} - subMenu={ - key === "switchBlock" ? ( - - ) : null - } - // subMenuVisible={ - // index === selectedIndex && key === "switchBlock" && subMenuVisible - // } - /> - ); - })} - - ); -}; diff --git a/packages/hash/frontend/src/components/BlockContextMenu/SearchView.tsx b/packages/hash/frontend/src/components/BlockContextMenu/SearchView.tsx deleted file mode 100644 index bfb53bf7e31..00000000000 --- a/packages/hash/frontend/src/components/BlockContextMenu/SearchView.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { VoidFunctionComponent } from "react"; -import { tw } from "twind"; -import { - BlockSuggesterProps, - getVariantIcon, -} from "../../blocks/page/createSuggester/BlockSuggester"; -import { BlockContextMenuItem } from "./BlockContextMenuItem"; -import { - FilteredMenuItems, - MenuState, - ItemClickMethod, - iconStyles, -} from "./BlockContextMenuUtils"; - -type SearchViewProps = { - filteredMenuItems: FilteredMenuItems; - entityId: string | null; - selectedIndex: number; - updateMenuState: (updatedState: Partial) => void; - onItemClick: ItemClickMethod; - blockSuggesterProps: BlockSuggesterProps; -}; - -export const SearchView: VoidFunctionComponent = ({ - filteredMenuItems, - entityId, - selectedIndex, - updateMenuState, - onItemClick, - blockSuggesterProps, -}) => { - return ( - <> - {!!filteredMenuItems.actions.length && ( - <> -
    Actions
    -
      - {filteredMenuItems.actions.map(({ title, icon, key }, index) => { - if (key === "copyLink" && !entityId) { - return null; - } - - return ( - onItemClick(key)} - onSelect={() => updateMenuState({ selectedIndex: index })} - icon={icon} - title={title} - /> - ); - })} -
    - - )} - - {!!filteredMenuItems.blocks.length && ( - <> -
    Turn Into
    -
      - {filteredMenuItems.blocks.map((option, index) => { - const { name } = option.variant; - - const icon = getVariantIcon(option); - - return ( - - blockSuggesterProps.onChange(option.variant, option.meta) - } - onSelect={() => updateMenuState({ selectedIndex: index })} - icon={ - // @todo add a fallback icon - {name} - } - title={name} - /> - ); - })} -
    - - )} - - {!filteredMenuItems.actions && !filteredMenuItems.blocks && ( -
    No Results
    - )} - - ); -}; diff --git a/packages/hash/frontend/src/components/hooks/useAccountEntities.ts b/packages/hash/frontend/src/components/hooks/useAccountEntities.ts index 3d67cc984cc..65a33f4ce53 100644 --- a/packages/hash/frontend/src/components/hooks/useAccountEntities.ts +++ b/packages/hash/frontend/src/components/hooks/useAccountEntities.ts @@ -9,10 +9,9 @@ import { UnknownEntity, } from "../../graphql/apiTypes.gen"; -// @todo properly type this +// @todo handle error state export const useAccountEntities = () => { const [loading, setLoading] = useState(false); - const [error, setError] = useState(); const [data, setData] = useState< Pick< UnknownEntity, @@ -52,7 +51,6 @@ export const useAccountEntities = () => { return { fetchEntities, loading, - error, data, }; }; diff --git a/packages/hash/frontend/src/pages/[account-slug]/[page-slug].page.tsx b/packages/hash/frontend/src/pages/[account-slug]/[page-slug].page.tsx index a4b68aad505..f0133887d47 100644 --- a/packages/hash/frontend/src/pages/[account-slug]/[page-slug].page.tsx +++ b/packages/hash/frontend/src/pages/[account-slug]/[page-slug].page.tsx @@ -12,7 +12,6 @@ import { GetPageQuery, GetPageQueryVariables, } from "@hashintel/hash-shared/graphql/apiTypes.gen"; -import { Container } from "@mui/material"; import { useCollabPositions } from "../../blocks/page/collab/useCollabPositions"; import { useCollabPositionTracking } from "../../blocks/page/collab/useCollabPositionTracking"; import { useCollabPositionReporter } from "../../blocks/page/collab/useCollabPositionReporter"; diff --git a/packages/hash/shared/src/entityStorePlugin.ts b/packages/hash/shared/src/entityStorePlugin.ts index 98244265230..9412f503f4e 100644 --- a/packages/hash/shared/src/entityStorePlugin.ts +++ b/packages/hash/shared/src/entityStorePlugin.ts @@ -167,7 +167,7 @@ const updateBlockEntity = ( ); // Check if entity to load exists in store - // and add to store if it doesnt + // and add to store if it doesn't exist if (!draftEntity) { draftEntity = { accountId: targetEntity.accountId, @@ -194,10 +194,6 @@ const updateBlockEntity = ( draftBlockEntity.properties = { entity: draftEntity, }; - - draftEntityStore[draftId] = draftBlockEntity; - - return draftEntityStore; }; /** From 3d115ff8091f0b0b6b3d640eccf9e7475f62bc58 Mon Sep 17 00:00:00 2001 From: teenoh Date: Tue, 12 Apr 2022 00:04:27 +0100 Subject: [PATCH 15/42] fix tests --- .../src/components/BlockContextMenu/BlockLoaderInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx index 408f7116215..47d61160a26 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx @@ -79,7 +79,7 @@ export const BlockLoaderInput: React.VFC = () => { Date: Tue, 12 Apr 2022 01:13:01 +0100 Subject: [PATCH 16/42] fix playwright tests --- .../src/components/BlockContextMenu/BlockContextMenu.tsx | 1 + .../src/components/BlockContextMenu/BlockLoaderInput.tsx | 6 ++++++ .../frontend/src/shared/layout/layout-with-sidebar.tsx | 2 +- .../layout/layout-with-sidebar/account-page-list.tsx | 2 +- packages/hash/playwright/tests/page-creation.spec.ts | 8 +++++--- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx index 9bf011ef917..ab0f0d6c15e 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockContextMenu.tsx @@ -146,6 +146,7 @@ export const BlockContextMenu = forwardRef< width: 228, }, }} + data-testid="block-context-menu" > diff --git a/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx b/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx index 47d61160a26..50595cca6c1 100644 --- a/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx +++ b/packages/hash/frontend/src/components/BlockContextMenu/BlockLoaderInput.tsx @@ -83,7 +83,13 @@ export const BlockLoaderInput: React.VFC = () => { required value={blockUrl} sx={{ flex: 1 }} + InputProps={{ + inputRef: blockUrlRef, + }} onChange={(event) => setBlockUrl(event.target.value)} + onKeyDown={(evt) => { + evt.stopPropagation(); + }} />