diff --git a/src/frontend/app-common/app-menu.tsx b/src/frontend/app-common/app-menu.tsx index b417ff82..c634db1f 100644 --- a/src/frontend/app-common/app-menu.tsx +++ b/src/frontend/app-common/app-menu.tsx @@ -1,5 +1,7 @@ -import { ReactNode } from "react"; -import { FloatingPosition, Menu } from "@mantine/core"; +import { PropsWithChildren, ReactNode } from "react"; +import { FloatingPosition, Menu, ActionIcon } from "@mantine/core"; +import { IconDots } from "@tabler/icons-react"; +import { IconSize } from "../common/style-constants"; interface AppContextMenuProps { menuItems: ReactNode; @@ -56,3 +58,22 @@ export function AppContextMenu(props: AppContextMenuProps): ReactNode { ); } + +/** + * An explicit button which opens a menu with the given items. Used alongside + * the right-click context menu so the menu is reachable without a right-click. + */ +export function MenuButton(props: PropsWithChildren): ReactNode { + return ( + + e.stopPropagation()} + > + + + + ); +} diff --git a/src/frontend/cards/card-components.tsx b/src/frontend/cards/card-components.tsx index e045fc5b..4622fe95 100644 --- a/src/frontend/cards/card-components.tsx +++ b/src/frontend/cards/card-components.tsx @@ -1,6 +1,5 @@ -import { ActionIcon, Group, Menu, Table, Text } from "@mantine/core"; +import { Group, Menu, Table, Text } from "@mantine/core"; import { - IconDots, IconExternalLink, IconEyeOff, IconLink, @@ -11,11 +10,11 @@ import { import { IconColor, IconSize } from "../common/style-constants"; import { copyUrlToClipboard, makeUrl, openUrlInNewTab } from "../common/url"; import { PropsWithChildren, ReactNode, useCallback } from "react"; -import { AppContextMenu } from "../app-common/app-menu"; +import { AppContextMenu, MenuButton } from "../app-common/app-menu"; import { SearchHit } from "../search/search"; import { SearchHitTitle } from "../search/search-results"; import { CardThumbnail } from "../insert/thumbnail"; -import { DocumentPath } from "../../shared/onshape-path"; +import { InstancePath, isElementPath } from "../../shared/onshape-path"; import { openCannotDeriveAssemblyAlert } from "../app/alerts"; import { useInsertMutation, @@ -30,14 +29,16 @@ import { RequireAccessLevel } from "../api-utils/access-level"; import { useReloadThumbnailMutation } from "./card-hooks"; interface OpenDocumentItemsProps { - path: DocumentPath; + path: InstancePath; + configuration?: Configuration; } - /** * Menu items which can be used to open or copy a link to a document. */ export function OpenDocumentItems(props: OpenDocumentItemsProps) { - const url = makeUrl(props.path); + const url = isElementPath(props.path) + ? makeUrl({ ...props.path, configuration: props.configuration }) + : makeUrl(props.path); return ( <> - e.stopPropagation()} - > - - - - ); -} - /** * Wraps one or more admin-only menu items into an Admin submenu. */ diff --git a/src/frontend/cards/insertable-card.tsx b/src/frontend/cards/insertable-card.tsx index 45ae3d5b..ed7834c8 100644 --- a/src/frontend/cards/insertable-card.tsx +++ b/src/frontend/cards/insertable-card.tsx @@ -14,6 +14,7 @@ import { getFavoriteForInsertable, InsertableOut } from "../../shared/api-models"; +import { Configuration } from "../../shared/configuration-models"; import { ElementType } from "../../shared/types"; import { SearchHit } from "../search/search"; import { @@ -114,26 +115,35 @@ export function InsertableCard(props: InsertableCardProps): ReactNode { interface InsertableMenuItemsProps { favorite: Favorite | undefined; insertable: InsertableOut; + inInsertMenu?: boolean; + configuration?: Configuration; } export function InsertableMenuItems( props: InsertableMenuItemsProps ): ReactNode { - const { favorite, insertable } = props; + const { favorite, insertable, inInsertMenu, configuration } = props; return ( <> - - + {!inInsertMenu && ( + <> + + + + )} - + -): string; +export function makeUrl(path: InstancePath): string; +export function makeUrl(path: DocumentPath): string; export function makeUrl( path: DocumentPath, configuration?: Record @@ -29,12 +28,14 @@ export function makeUrl( if (isElementPath(path)) { url += `/e/${path.elementId}`; } - if (configuration) { - url += "?configuration=" + encodeConfigurationForQuery(configuration); + const config = + configuration ?? + (isConfigurablePath(path) ? path.configuration : undefined); + if (config) { + url += "?configuration=" + encodeConfigurationForQuery(config); } return url; } - export interface ConfigurationPath { configuration?: string; } diff --git a/src/frontend/insert/insert-menu.tsx b/src/frontend/insert/insert-menu.tsx index 8b10bbc3..58254ab3 100644 --- a/src/frontend/insert/insert-menu.tsx +++ b/src/frontend/insert/insert-menu.tsx @@ -16,6 +16,8 @@ import { NotificationAction, renderNotification } from "../common/notifications"; +import { MenuButton } from "../app-common/app-menu"; +import { InsertableMenuItems } from "../cards/insertable-card"; import { ConfigurationWrapper } from "./configurations"; import { useInsertMutation } from "./insert-hooks"; import { Configuration } from "../../shared/configuration-models"; @@ -94,6 +96,14 @@ function InsertMenuContent(props: InsertMenuContentProps): ReactNode { {parameters} + + + ; +} + export function isDocumentPath(path: any): path is DocumentPath { return ( typeof path === "object" && @@ -44,6 +48,15 @@ export function isPartPath(path: DocumentPath): path is PartPath { return isElementPath(path) && (path as PartPath).partId !== undefined; } +export function isConfigurablePath( + path: DocumentPath +): path is ConfigurablePath { + return ( + isElementPath(path) && + (path as ConfigurablePath).configuration !== undefined + ); +} + export function toDocumentApiPath(path: DocumentPath): string { return `/d/${path.documentId}`; }