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}`;
}