diff --git a/apps/hash-frontend/src/components/block-loader/block-loader.tsx b/apps/hash-frontend/src/components/block-loader/block-loader.tsx index e2cd7a9f22c..7e7877cb68b 100644 --- a/apps/hash-frontend/src/components/block-loader/block-loader.tsx +++ b/apps/hash-frontend/src/components/block-loader/block-loader.tsx @@ -178,7 +178,9 @@ export const BlockLoader: FunctionComponent = ({ * The traversal depths will not be accurate, because the actual traversal was rooted at the block collection. * This data is only used briefly – we fetch the block's subgraph from the API further on this effect. */ - const latestEditionId = Object.keys(entityEditionMap).sort().pop()!; + const latestEditionId = Object.keys(entityEditionMap) + .toSorted() + .at(-1)!; subgraphToRewrite = { ...blockCollectionSubgraph, roots: [ @@ -231,8 +233,8 @@ export const BlockLoader: FunctionComponent = ({ * doing so only for the latest edition is an optimization which assumes blocks only care about the latest value. */ const latestSubgraphEditionTimestamp = Object.keys(entityOrTypeEditionMap) - .sort() - .pop() as EntityRevisionId; + .toSorted() + .at(-1) as EntityRevisionId; /** * Check if we have a version of this entity in the local store, if provided, and if it's newer than in the subgraph. diff --git a/apps/hash-frontend/src/components/grid/utils.ts b/apps/hash-frontend/src/components/grid/utils.ts index 42919edd5af..38bb4d8d23d 100644 --- a/apps/hash-frontend/src/components/grid/utils.ts +++ b/apps/hash-frontend/src/components/grid/utils.ts @@ -18,7 +18,7 @@ export const getYCenter = ( * @returns cell horizontal padding */ export const getCellHorizontalPadding = (atFirstColumn?: boolean) => - atFirstColumn ? 36 : 22; + atFirstColumn ? 42 : 22; export type BlankCell = CustomCell<{ kind: "blank-cell" }>; diff --git a/apps/hash-frontend/src/components/grid/utils/draw-cell-fade-out-gradient.ts b/apps/hash-frontend/src/components/grid/utils/draw-cell-fade-out-gradient.ts index a6bb0811b14..23a585b0700 100644 --- a/apps/hash-frontend/src/components/grid/utils/draw-cell-fade-out-gradient.ts +++ b/apps/hash-frontend/src/components/grid/utils/draw-cell-fade-out-gradient.ts @@ -26,7 +26,7 @@ export const drawCellFadeOutGradient = ( ctx.fillRect(rectLeft, rect.y, extraWidth, rect.height); } - const grdWidth = 50; + const grdWidth = 20; const grdLeft = rectLeft - grdWidth; const grd = ctx.createLinearGradient(rectLeft - grdWidth, 0, rectLeft, 0); grd.addColorStop(0, "#ffffff00"); diff --git a/apps/hash-frontend/src/components/grid/utils/draw-chip-with-icon.ts b/apps/hash-frontend/src/components/grid/utils/draw-chip-with-icon.ts index 4bd1acc9a52..1e0e63236e8 100644 --- a/apps/hash-frontend/src/components/grid/utils/draw-chip-with-icon.ts +++ b/apps/hash-frontend/src/components/grid/utils/draw-chip-with-icon.ts @@ -9,6 +9,63 @@ import { getYCenter } from "../utils"; import type { CustomIcon } from "./custom-grid-icons"; import { drawChip } from "./draw-chip"; +const filledIconCanvasCache: { + [originalUrl: string]: { + [fillColor: string]: HTMLCanvasElement; + }; +} = {}; + +/** + * In order to fill SVGs with a new color, we draw them on an off-screen canvas, + * and fill the non-transparent parts of the image with the desired color. + */ +const getFilledCanvas = ({ + fill, + iconUrl, + image, +}: { + fill: string; + iconUrl: string; + image: HTMLImageElement | ImageBitmap; +}): HTMLImageElement | ImageBitmap | HTMLCanvasElement => { + if (!iconUrl.endsWith(".svg")) { + return image; + } + + const cachedCanvas = filledIconCanvasCache[iconUrl]?.[fill]; + + if (cachedCanvas) { + return cachedCanvas; + } + + const offScreenCanvas = document.createElement("canvas"); + const offScreenContext = offScreenCanvas.getContext("2d"); + + if (!offScreenContext) { + throw new Error("Could not create off-screen canvas"); + } + + offScreenCanvas.width = image.width; + offScreenCanvas.height = image.height; + + // Draw the image onto the off-screen canvas + offScreenContext.drawImage(image, 0, 0); + + offScreenContext.globalCompositeOperation = "source-in"; + offScreenContext.fillStyle = fill; + offScreenContext.fillRect( + 0, + 0, + offScreenCanvas.width, + offScreenCanvas.height, + ); + + filledIconCanvasCache[iconUrl] ??= {}; + filledIconCanvasCache[iconUrl][fill] = offScreenCanvas; + + return offScreenCanvas; +}; + const drawClippedImage = ({ ctx, height, @@ -71,6 +128,21 @@ const drawClippedImage = ({ return width; }; +export type DrawChipWithIconProps = { + args: DrawArgs; + icon?: + | { + inbuiltIcon: CustomIcon; + } + | { imageSrc: string } + | { entityTypeIcon: string }; + iconFill?: string; + text: string; + left: number; + color: ChipCellColor; + variant?: ChipCellVariant; +}; + /** * @param args draw args of cell * @param text text content of chip @@ -84,26 +156,18 @@ const drawClippedImage = ({ */ export const drawChipWithIcon = ({ args, + icon, + iconFill, text, - icon = "bpAsterisk", - imageSrc, left, color, variant, -}: { - args: DrawArgs; - text: string; - icon?: CustomIcon; - imageSrc?: string; - left: number; - color: ChipCellColor; - variant?: ChipCellVariant; -}) => { +}: DrawChipWithIconProps) => { const { ctx, theme, imageLoader, col, row } = args; const yCenter = getYCenter(args); const paddingX = 12; - const iconHeight = imageSrc ? 24 : 12; + const iconHeight = icon && "imageSrc" in icon ? 24 : 12; const gap = 8; const iconLeft = left + paddingX; @@ -113,18 +177,22 @@ export const drawChipWithIcon = ({ const iconTop = yCenter - iconHeight / 2; - const { bgColor, borderColor, iconColor, textColor } = getChipColors( - color, - variant, - ); + const { + bgColor, + borderColor, + iconColor: defaultColor, + textColor, + } = getChipColors(color, variant); + + const iconColor = iconFill ?? defaultColor; let chipWidth = iconHeight + gap + textWidth + 2 * paddingX; let chipHeight; let chipTop; - if (imageSrc) { - const image = imageLoader.loadOrGetImage(imageSrc, col, row); + if (icon && "imageSrc" in icon) { + const image = imageLoader.loadOrGetImage(icon.imageSrc, col, row); if (image) { const maxWidth = 80; @@ -154,7 +222,7 @@ export const drawChipWithIcon = ({ width, }); } else { - throw new Error(`Image not loaded: ${imageSrc}`); + throw new Error(`Image not loaded: ${icon.imageSrc}`); } } else { ({ height: chipHeight, top: chipTop } = drawChip( @@ -165,15 +233,62 @@ export const drawChipWithIcon = ({ borderColor, )); - args.spriteManager.drawSprite( - icon, - "normal", - ctx, - iconLeft, - iconTop, - iconHeight, - { ...theme, fgIconHeader: iconColor }, - ); + if (icon && "inbuiltIcon" in icon) { + args.spriteManager.drawSprite( + icon.inbuiltIcon, + "normal", + ctx, + iconLeft, + iconTop, + iconHeight, + { ...theme, fgIconHeader: iconColor }, + ); + } else if (icon && "entityTypeIcon" in icon) { + if (icon.entityTypeIcon.match(/\p{Extended_Pictographic}$/u)) { + /** + * This is an emoji icon + */ + ctx.fillStyle = iconColor; + const currentFont = ctx.font; + ctx.font = `bold ${iconHeight}px Inter`; + ctx.fillText(icon.entityTypeIcon, iconLeft, yCenter); + ctx.font = currentFont; + } else { + let iconUrl; + if (icon.entityTypeIcon.startsWith("/")) { + iconUrl = new URL(icon.entityTypeIcon, window.location.origin).href; + } else if (icon.entityTypeIcon.startsWith("https")) { + iconUrl = icon.entityTypeIcon; + } + + if (iconUrl) { + const image = imageLoader.loadOrGetImage(iconUrl, col, row); + + if (image) { + const canvasWithFill = getFilledCanvas({ + fill: iconColor, + iconUrl, + image, + }); + + const aspectRatio = image.width / image.height; + + const width = + aspectRatio > 1 ? iconHeight : iconHeight * aspectRatio; + const height = + aspectRatio > 1 ? iconHeight / aspectRatio : iconHeight; + + ctx.drawImage( + canvasWithFill, + iconLeft + (iconHeight - width) / 2, + iconTop + (iconHeight - height) / 2, + width, + height, + ); + } + } + } + } } const textLeft = left + chipWidth - paddingX - textWidth; diff --git a/apps/hash-frontend/src/pages/[shortname].page/pinned-entity-type-tab-contents.tsx b/apps/hash-frontend/src/pages/[shortname].page/pinned-entity-type-tab-contents.tsx index d199a2fc5f8..b1096ce3af9 100644 --- a/apps/hash-frontend/src/pages/[shortname].page/pinned-entity-type-tab-contents.tsx +++ b/apps/hash-frontend/src/pages/[shortname].page/pinned-entity-type-tab-contents.tsx @@ -1,3 +1,4 @@ +import { EntityOrTypeIcon } from "@hashintel/design-system"; import type { Entity } from "@local/hash-graph-sdk/entity"; import type { EntityTypeWithMetadata } from "@local/hash-graph-types/ontology"; import type { OwnedById } from "@local/hash-graph-types/web"; @@ -5,7 +6,10 @@ import { generateEntityLabel } from "@local/hash-isomorphic-utils/generate-entit import { systemEntityTypes } from "@local/hash-isomorphic-utils/ontology-type-ids"; import { includesPageEntityTypeId } from "@local/hash-isomorphic-utils/page-entity-type-ids"; import type { EntityRootType, Subgraph } from "@local/hash-subgraph"; -import { extractEntityUuidFromEntityId } from "@local/hash-subgraph"; +import { + extractEntityUuidFromEntityId, + linkEntityTypeUrl, +} from "@local/hash-subgraph"; import { Box, Divider, @@ -29,12 +33,9 @@ import type { Org, User } from "../../lib/user-and-org"; import { useEntityTypesContextRequired } from "../../shared/entity-types-context/hooks/use-entity-types-context-required"; import { ArrowDownAZRegularIcon } from "../../shared/icons/arrow-down-a-z-regular-icon"; import { ArrowUpZARegularIcon } from "../../shared/icons/arrow-up-a-z-regular-icon"; -import { CanvasIcon } from "../../shared/icons/canvas-icon"; import { ClockRegularIcon } from "../../shared/icons/clock-regular-icon"; -import { PageLightIcon } from "../../shared/icons/page-light-icon"; import { PlusRegularIcon } from "../../shared/icons/plus-regular"; import { Button, Link, MenuItem } from "../../shared/ui"; -import { useEntityIcon } from "../../shared/use-entity-icon"; import { ProfileSectionHeading } from "../[shortname]/shared/profile-section-heading"; import { InlineSelect } from "../shared/inline-select"; import type { ProfilePageTab } from "./util"; @@ -59,20 +60,6 @@ const EntityRow: FunctionComponent<{ ? format(updatedAt, "d MMMM yyyy") : `${formatDistanceToNowStrict(updatedAt)} ago`; - const icon = useEntityIcon({ - entity, - entityTypes: entityType ? [entityType] : undefined, - pageIcon: entity.metadata.entityTypeIds.includes( - systemEntityTypes.canvas.entityTypeId, - ) ? ( - palette.gray[40] }} - /> - ) : ( - - ), - }); - return ( - svg": { - color: ({ palette }) => palette.gray[50], - }, - }} - > - {icon} + + palette.blue[70]} + isLink={ + /** + * @todo H-3363 use closed schema to take account of indirectly inherited link status + */ + !!entityType?.schema.allOf?.some( + (allOf) => allOf.$ref === linkEntityTypeUrl, + ) + } + /> {label} diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/edit-entity-slide-over.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/edit-entity-slide-over.tsx index 5abaf2df701..51aab06ca54 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/edit-entity-slide-over.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/edit-entity-slide-over.tsx @@ -1,5 +1,9 @@ import { useMutation, useQuery } from "@apollo/client"; -import { ArrowUpRightRegularIcon, Skeleton } from "@hashintel/design-system"; +import { + ArrowUpRightRegularIcon, + EntityOrTypeIcon, + Skeleton, +} from "@hashintel/design-system"; import { mergePropertyObjectAndMetadata, patchesFromPropertyObjects, @@ -15,9 +19,10 @@ import { import type { EntityRootType, Subgraph } from "@local/hash-subgraph"; import { extractOwnedByIdFromEntityId, + linkEntityTypeUrl, splitEntityId, } from "@local/hash-subgraph"; -import { getRoots } from "@local/hash-subgraph/stdlib"; +import { getEntityTypeById, getRoots } from "@local/hash-subgraph/stdlib"; import { Box, Drawer, Stack, Typography } from "@mui/material"; import type { RefObject } from "react"; import { memo, useCallback, useMemo, useState } from "react"; @@ -357,6 +362,28 @@ const EditEntitySlideOver = memo( [incompleteOnEntityClick, slideContainerRef], ); + /** + * @todo H-3363 use the closed schema to get the first icon + */ + const entityTypes = useMemo( + () => + entity && localEntitySubgraph + ? entity.metadata.entityTypeIds.toSorted().map((entityTypeId) => { + const entityType = getEntityTypeById( + localEntitySubgraph, + entityTypeId, + ); + + if (!entityType) { + throw new Error(`Cannot find entity type ${entityTypeId}`); + } + + return entityType; + }) + : [], + [entity, localEntitySubgraph], + ); + return ( - + + allOf.$ref === linkEntityTypeUrl, + ) + } + fill={({ palette }) => palette.gray[50]} + fontSize={40} + /> + + {entityLabel} {entityOwningShortname && !hideOpenInNew && ( diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/incoming-links-section/incoming-links-table.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/incoming-links-section/incoming-links-table.tsx index 253c105f0d0..d7346421b89 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/incoming-links-section/incoming-links-table.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/incoming-links-section/incoming-links-table.tsx @@ -1,4 +1,5 @@ import type { EntityType, VersionedUrl } from "@blockprotocol/type-system"; +import { EntityOrTypeIcon } from "@hashintel/design-system"; import { typedEntries } from "@local/advanced-types/typed-entries"; import type { Entity } from "@local/hash-graph-sdk/entity"; import type { EntityId } from "@local/hash-graph-types/entity"; @@ -139,6 +140,15 @@ const TableRow = memo(({ row }: { row: IncomingLinkRow }) => { } fontSize={linksTableFontSize} label={row.sourceEntityLabel} + icon={ + palette.gray[50]} + icon={row.sourceEntityTypes[0]!.icon} + isLink={!!row.sourceEntity.linkData} + /> + } /> @@ -148,6 +158,15 @@ const TableRow = memo(({ row }: { row: IncomingLinkRow }) => { palette.blue[70]} + icon={linkEntityType.icon} + isLink + /> + } type sx={{ fontSize: linksTableFontSize, @@ -179,6 +198,16 @@ const TableRow = memo(({ row }: { row: IncomingLinkRow }) => { palette.blue[70]} + icon={sourceEntityType.icon} + /* @todo H-3363 use closed entity type schema to check link status */ + isLink={!!row.sourceEntity.linkData} + /> + } sx={{ fontSize: linksTableFontSize, }} @@ -196,6 +225,15 @@ const TableRow = memo(({ row }: { row: IncomingLinkRow }) => { row.onEntityClick(row.linkEntity.entityId)} fontSize={linksTableFontSize} + icon={ + palette.gray[50]} + icon={row.linkEntityTypes[0]!.icon} + isLink + /> + } label={row.linkEntityLabel} /> diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section.tsx index 9cc577b209b..d5ba68a400c 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section.tsx @@ -10,7 +10,7 @@ import { Paper, Stack } from "@mui/material"; import { useState } from "react"; import { Grid } from "../../../../../../components/grid/grid"; -import { renderChipCell } from "../../../../../shared/chip-cell"; +import { createRenderChipCell } from "../../../../../shared/chip-cell"; import { SectionWrapper } from "../../../../shared/section-wrapper"; import { LinksSectionEmptyState } from "../../shared/links-section-empty-state"; import { useEntityEditor } from "../entity-editor-context"; @@ -107,7 +107,7 @@ export const OutgoingLinksSection = ({ renderLinkCell, renderLinkedWithCell, renderSummaryChipCell, - renderChipCell, + createRenderChipCell(), ]} /> diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/link-cell.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/link-cell.ts index 62f39805844..7c68ffd8e99 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/link-cell.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/link-cell.ts @@ -29,7 +29,7 @@ export const renderLinkCell: CustomRenderer = { ctx.font = theme.baseFontStyle; const yCenter = getYCenter(args); - const iconLeft = rect.x + getCellHorizontalPadding(true); + const iconLeft = rect.x + getCellHorizontalPadding(false); const iconSize = 16; spriteManager.drawSprite( diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell.ts index 3b45f43e420..525aff0fb64 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell.ts @@ -144,7 +144,10 @@ export const renderLinkedWithCell: CustomRenderer = { const { width: chipWidth } = drawChipWithIcon({ args, color: "white", - imageSrc, + /** + * @todo H-1978 use entity type icon if present in its schema + */ + icon: imageSrc ? { imageSrc } : { inbuiltIcon: "bpAsterisk" }, text: label, left: accumulatedLeft, }); diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/readonly-outgoing-links-table.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/readonly-outgoing-links-table.tsx index f87775e033e..3b42af63ae8 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/readonly-outgoing-links-table.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/readonly-outgoing-links-table.tsx @@ -1,4 +1,5 @@ import type { EntityType, VersionedUrl } from "@blockprotocol/type-system"; +import { EntityOrTypeIcon } from "@hashintel/design-system"; import { typedEntries } from "@local/advanced-types/typed-entries"; import type { Entity } from "@local/hash-graph-sdk/entity"; import type { EntityId } from "@local/hash-graph-types/entity"; @@ -130,6 +131,16 @@ const TableRow = memo(({ row }: { row: OutgoingLinkRow }) => { {row.linkEntityTypes.map((linkEntityType) => ( palette.blue[70]} + icon={linkEntityType.icon} + /* @todo H-3363 use closed entity type schema to check link status */ + isLink + /> + } showInFull type sx={{ @@ -151,6 +162,15 @@ const TableRow = memo(({ row }: { row: OutgoingLinkRow }) => { row.onEntityClick(row.targetEntity.metadata.recordId.entityId) } fontSize={linksTableFontSize} + icon={ + palette.gray[50]} + icon={row.targetEntityTypes[0]!.icon} + isLink={!!row.targetEntity.linkData} + /> + } label={row.targetEntityLabel} /> @@ -160,6 +180,16 @@ const TableRow = memo(({ row }: { row: OutgoingLinkRow }) => { {row.targetEntityTypes.map((targetEntityType) => ( palette.blue[70]} + icon={targetEntityType.icon} + /* @todo H-3363 use closed entity type schema to check link status */ + isLink={!!row.targetEntity.linkData} + /> + } type sx={{ fontSize: linksTableFontSize, @@ -180,6 +210,15 @@ const TableRow = memo(({ row }: { row: OutgoingLinkRow }) => { row.onEntityClick(row.linkEntity.metadata.recordId.entityId) } fontSize={linksTableFontSize} + icon={ + palette.gray[50]} + icon={row.linkEntityTypes[0]!.icon} + isLink + /> + } label={row.linkEntityLabel} /> diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/types.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/types.ts index 3a6cfb33cc1..09b05ed6b20 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/types.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/types.ts @@ -20,7 +20,6 @@ export type LinkRow = { isList: boolean; isUploading: boolean; expectedEntityTypes: EntityTypeWithMetadata[]; - expectedEntityTypeTitles: string[]; linkAndTargetEntities: (LinkAndTargetEntity & { // Adding the subgraph we found these in makes it easy to retrieve their type(s), e.g. for labelling sourceSubgraph: Subgraph | null; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/use-create-get-cell-content.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/use-create-get-cell-content.ts index 8c94e6a4462..7a7572a25c3 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/use-create-get-cell-content.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/use-create-get-cell-content.ts @@ -1,5 +1,7 @@ import type { Item } from "@glideapps/glide-data-grid"; import { GridCellKind } from "@glideapps/glide-data-grid"; +import { linkEntityTypeUrl } from "@local/hash-subgraph"; +import { useTheme } from "@mui/material"; import { useCallback } from "react"; import type { ChipCell } from "../../../../../../shared/chip-cell"; @@ -13,6 +15,8 @@ import type { LinkRow } from "./types"; export const useCreateGetCellContent = () => { const { readonly } = useEntityEditor(); + const theme = useTheme(); + const createGetCellContent = useCallback( (rows: LinkRow[]) => ([colIndex, rowIndex]: Item): @@ -32,7 +36,7 @@ export const useCreateGetCellContent = () => { throw new Error("columnKey not found"); } - const expectsAnything = !row.expectedEntityTypeTitles.length; + const expectsAnything = !row.expectedEntityTypes.length; switch (columnKey) { case "linkTitle": @@ -60,25 +64,42 @@ export const useCreateGetCellContent = () => { readonly, }, }; - case "expectedEntityTypes": + case "expectedEntityTypes": { + const expectedEntityTypeTitles = row.expectedEntityTypes.map( + (type) => type.schema.title, + ); return { kind: GridCellKind.Custom, readonly: true, allowOverlay: true, // in case we have so many expected types that we need to open on click to see them all - copyData: String(row.expectedEntityTypeTitles), + copyData: String(expectedEntityTypeTitles.join(", ")), data: { kind: "chip-cell", chips: expectsAnything ? [{ text: "Anything" }] - : row.expectedEntityTypeTitles.map((title) => ({ - text: title, + : row.expectedEntityTypes.map(({ schema }) => ({ + text: schema.title, + icon: schema.icon + ? { entityTypeIcon: schema.icon } + : { + /** + * @todo H-3363 use closed schema to take account of indirect inheritance links + */ + inbuiltIcon: schema.allOf?.some( + (allOf) => allOf.$ref === linkEntityTypeUrl, + ) + ? "bpLink" + : "bpAsterisk", + }, + iconFill: theme.palette.blue[70], })), color: expectsAnything ? "blue" : "white", }, }; + } } }, - [readonly], + [readonly, theme], ); return createGetCellContent; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/use-rows.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/use-rows.ts index e57c02f7acd..4c83040ce55 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/use-rows.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/use-rows.ts @@ -158,10 +158,6 @@ export const useRows = () => { linkAndTargetEntities.push(...additions); - const expectedEntityTypeTitles = expectedEntityTypes.map( - (val) => val.schema.title, - ); - const isFile = expectedEntityTypes.some( (expectedType) => isSpecialEntityTypeLookup?.[expectedType.schema.$id]?.isFile, @@ -186,7 +182,6 @@ export const useRows = () => { isList: linkSchema.maxItems === undefined || linkSchema.maxItems > 1, expectedEntityTypes, - expectedEntityTypeTitles, entitySubgraph, markLinkAsArchived: markLinkEntityToArchive, onEntityClick, diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table.tsx index 2d80a41dd42..fba5fad4b44 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table.tsx @@ -3,7 +3,7 @@ import { useMemo, useRef } from "react"; import { Grid } from "../../../../../../components/grid/grid"; import { useGridTooltip } from "../../../../../../components/grid/utils/use-grid-tooltip"; -import { renderChipCell } from "../../../../../shared/chip-cell"; +import { createRenderChipCell } from "../../../../../shared/chip-cell"; import { useEntityEditor } from "../entity-editor-context"; import { renderSummaryChipCell } from "../shared/summary-chip-cell"; import { createRenderChangeTypeCell } from "./property-table/cells/change-type-cell"; @@ -36,7 +36,7 @@ export const PropertyTable = ({ const customRenderers = useMemo( () => [ renderValueCell, - renderChipCell, + createRenderChipCell(), createRenderPropertyNameCell(togglePropertyExpand, propertyExpandStatus), renderSummaryChipCell, createRenderChangeTypeCell(gridRef), diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/change-type-cell.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/change-type-cell.tsx index ba941bde99c..78786727a12 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/change-type-cell.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/change-type-cell.tsx @@ -64,7 +64,7 @@ export const createRenderChangeTypeCell = ( text: currentType.title, left: chipLeft, color: "blue", - icon: editorSpec.gridIcon, + icon: { inbuiltIcon: editorSpec.gridIcon }, }); const { width: chipWidth } = drawTheLeftChip(); diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/property-name-cell.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/property-name-cell.tsx index 9f3012250fe..88cc62aba32 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/property-name-cell.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/property-name-cell.tsx @@ -40,7 +40,9 @@ export const createRenderPropertyNameCell = ( } = cell.data.propertyRow; const yCenter = getYCenter(args); - const columnPadding = getCellHorizontalPadding(true); + const columnPadding = getCellHorizontalPadding( + !!children.length || depth > 0, + ); const indentMultiplier = 16; const indentWidth = indent * indentMultiplier; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-create-get-cell-content.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-create-get-cell-content.ts index ef841ba6b8a..dae854ded3c 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-create-get-cell-content.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-create-get-cell-content.ts @@ -169,7 +169,7 @@ export const useCreateGetCellContent = ( return { text: type.title, - icon: editorSpec.gridIcon, + icon: { inbuiltIcon: editorSpec.gridIcon }, faIconDefinition: { icon: editorSpec.icon }, }; }), diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section.tsx index 5fe3277d6b1..793ae6367f8 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section.tsx @@ -8,6 +8,7 @@ import { zeroedGraphResolveDepths, } from "@local/hash-isomorphic-utils/graph-queries"; import type { EntityTypeRootType } from "@local/hash-subgraph"; +import { linkEntityTypeUrl } from "@local/hash-subgraph"; import { getEntityTypeById, getRoots } from "@local/hash-subgraph/stdlib"; import { Box, Stack } from "@mui/material"; import { useMemo, useState } from "react"; @@ -139,6 +140,10 @@ export const TypeButton = ({ const isNotYetInDb = entity.metadata.recordId.entityId.includes("draft"); const canChangeTypes = !readonly && !isNotYetInDb; + const isLink = !!currentEntityType.schema.allOf?.some( + (allOf) => allOf.$ref === linkEntityTypeUrl, + ); + /** @todo H-3363 take account of inherited icons */ return ( <> @@ -146,6 +151,7 @@ export const TypeButton = ({ disableClick={disableTypeClick} LinkComponent={Link} icon={currentEntityType.schema.icon} + isLink={isLink} onDelete={canChangeTypes ? onDeleteClicked : undefined} url={entityTypeId} title={entityTypeTitle} diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section/entity-type-change-modal.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section/entity-type-change-modal.tsx index 0969c5afca2..97899a97a67 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section/entity-type-change-modal.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section/entity-type-change-modal.tsx @@ -1,6 +1,6 @@ import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; import { AlertModal, FontAwesomeIcon } from "@hashintel/design-system"; -import { Box, Typography } from "@mui/material"; +import { Box, Stack, Typography } from "@mui/material"; import { useMemo } from "react"; export type EntityTypeChangeDetails = { @@ -179,7 +179,7 @@ const ModalHeader = ({ proposedChange: EntityTypeChangeDetails["proposedChange"]; }) => { return ( - <> + {proposedChange.type} {proposedChange.entityTypeTitle} {" entity type "} @@ -187,12 +187,12 @@ const ModalHeader = ({ {proposedChange.type === "Update" && ( <> - + v{proposedChange.newVersion} )} - + ); }; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-page-wrapper/entity-page-header.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-page-wrapper/entity-page-header.tsx index c4fd54bfda2..d36b641d469 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-page-wrapper/entity-page-header.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-page-wrapper/entity-page-header.tsx @@ -1,13 +1,13 @@ -import { faAsterisk } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@hashintel/design-system"; +import { EntityOrTypeIcon } from "@hashintel/design-system"; import type { Entity } from "@local/hash-graph-sdk/entity"; import type { EntityRootType, Subgraph } from "@local/hash-subgraph"; import { extractDraftIdFromEntityId } from "@local/hash-subgraph"; +import { getEntityTypeById } from "@local/hash-subgraph/stdlib"; import { Box, Collapse, Stack, Typography } from "@mui/material"; import { Container } from "@mui/system"; import { useRouter } from "next/router"; import type { ReactNode } from "react"; -import { useContext } from "react"; +import { useContext, useMemo } from "react"; import { NotificationsWithLinksContextProvider } from "../../../../shared/notifications-with-links-context"; import { TopContextBar } from "../../../../shared/top-context-bar"; @@ -43,6 +43,25 @@ export const EntityPageHeader = ({ const shortname = paramsShortname?.slice(1) ?? activeWorkspace?.shortname; + /** + * @todo H-3363 use the closed schema to get the first icon + */ + const entityTypes = useMemo( + () => + entity && entitySubgraph + ? entity.metadata.entityTypeIds.toSorted().map((entityTypeId) => { + const entityType = getEntityTypeById(entitySubgraph, entityTypeId); + + if (!entityType) { + throw new Error(`Cannot find entity type ${entityTypeId}`); + } + + return entityType; + }) + : [], + [entity, entitySubgraph], + ); + if (!shortname) { throw new Error("Cannot render before workspace is available"); } @@ -62,7 +81,15 @@ export const EntityPageHeader = ({ title: entityLabel, href: "#", id: "entityId", - icon: , + icon: ( + palette.gray[50]} + fontSize="inherit" + /> + ), }, ]} scrollToTop={() => {}} @@ -99,8 +126,14 @@ export const EntityPageHeader = ({ spacing={2} sx={{ color: lightTitle ? "gray.50" : "gray.90", marginTop: 2 }} > - - + palette.gray[50]} + icon={entityTypes[0]?.schema.icon} + isLink={!!entity?.linkData} + fontSize={40} + /> + {entityLabel} diff --git a/apps/hash-frontend/src/pages/actions.page/draft-entity/draft-entity-type.tsx b/apps/hash-frontend/src/pages/actions.page/draft-entity/draft-entity-type.tsx index e1c505bcf02..4a9f01127f3 100644 --- a/apps/hash-frontend/src/pages/actions.page/draft-entity/draft-entity-type.tsx +++ b/apps/hash-frontend/src/pages/actions.page/draft-entity/draft-entity-type.tsx @@ -1,9 +1,7 @@ -import { - ArrowUpRegularIcon, - AsteriskRegularIcon, -} from "@hashintel/design-system"; +import { ArrowUpRegularIcon, EntityOrTypeIcon } from "@hashintel/design-system"; import type { Entity } from "@local/hash-graph-sdk/entity"; import type { EntityRootType, Subgraph } from "@local/hash-subgraph"; +import { linkEntityTypeUrl } from "@local/hash-subgraph"; import { getEntityTypeById } from "@local/hash-subgraph/stdlib"; import { Box, Typography } from "@mui/material"; import type { FunctionComponent } from "react"; @@ -79,16 +77,20 @@ export const DraftEntityType: FunctionComponent<{ }} > - {entityType.schema.icon ?? ( - palette.blue[70], - position: "relative", - top: 1, - }} - /> - )}{" "} + palette.gray[50]} + fontSize={12} + icon={entityType.schema.icon} + isLink={ + /** + * @todo H-3363 use closed schema to take account of indirectly inherited link status + */ + !!entityType.schema.allOf?.some( + (allOf) => allOf.$ref === linkEntityTypeUrl, + ) + } + /> {entityType.schema.title} diff --git a/apps/hash-frontend/src/pages/entities.page.tsx b/apps/hash-frontend/src/pages/entities.page.tsx index 41743f497f9..7f7c2219cf3 100644 --- a/apps/hash-frontend/src/pages/entities.page.tsx +++ b/apps/hash-frontend/src/pages/entities.page.tsx @@ -2,6 +2,7 @@ import type { VersionedUrl } from "@blockprotocol/type-system"; import { extractVersion } from "@blockprotocol/type-system"; import { AsteriskRegularIcon, + EntityOrTypeIcon, EyeIconSolid, PenToSquareIconSolid, } from "@hashintel/design-system"; @@ -9,6 +10,7 @@ import type { EntityTypeWithMetadata } from "@local/hash-graph-types/ontology"; import { isBaseUrl } from "@local/hash-graph-types/ontology"; import { systemEntityTypes } from "@local/hash-isomorphic-utils/ontology-type-ids"; import { pluralize } from "@local/hash-isomorphic-utils/pluralize"; +import { linkEntityTypeUrl } from "@local/hash-subgraph"; import { extractBaseUrl } from "@local/hash-subgraph/type-system-patch"; import type { SxProps, Theme } from "@mui/material"; import { @@ -32,7 +34,6 @@ import { useEntityTypeEntitiesContextValue } from "../shared/entity-type-entitie import { useLatestEntityTypesOptional } from "../shared/entity-types-context/hooks"; import { useEntityTypesContextRequired } from "../shared/entity-types-context/hooks/use-entity-types-context-required"; import { generateLinkParameters } from "../shared/generate-link-parameters"; -import { AsteriskLightIcon } from "../shared/icons/asterisk-light-icon"; import { CanvasNewIcon } from "../shared/icons/canvas-new-icon"; import { FileCirclePlusRegularIcon } from "../shared/icons/file-circle-plus-regular-icon"; import { FilesLightIcon } from "../shared/icons/files-light-icon"; @@ -265,7 +266,20 @@ const EntitiesPage: NextPageWithLayout = () => { icon: isViewAllPagesPage ? ( ) : ( - + palette.gray[50]} + fontSize="inherit" + icon={entityType.schema.icon} + isLink={ + /** + * @todo H-3363 use closed schema to take account of indirectly inherited link status + */ + !!entityType.schema.allOf?.some( + (allOf) => allOf.$ref === linkEntityTypeUrl, + ) + } + /> ), }); } @@ -293,26 +307,29 @@ const EntitiesPage: NextPageWithLayout = () => { > - - ({ - svg: { - fontSize: 40, - mr: 2, - color: palette.gray[70], - verticalAlign: "middle", - }, - })} - > - {isViewAllPagesPage ? ( - - ) : ( - - )} - - {pageTitle} - + + {isViewAllPagesPage ? ( + + ) : ( + palette.gray[70]} + fontSize={40} + icon={entityType?.schema.icon} + isLink={ + /** + * @todo H-3363 use closed schema to take account of indirectly inherited link status + */ + !!entityType?.schema.allOf?.some( + (allOf) => allOf.$ref === linkEntityTypeUrl, + ) + } + /> + )} + + {pageTitle} + + {entityType && ( diff --git a/libs/@hashintel/design-system/src/selector-autocomplete/selector-autocomplete-option.tsx b/libs/@hashintel/design-system/src/selector-autocomplete/selector-autocomplete-option.tsx index f9ae7986a23..53f1ff9c5dd 100644 --- a/libs/@hashintel/design-system/src/selector-autocomplete/selector-autocomplete-option.tsx +++ b/libs/@hashintel/design-system/src/selector-autocomplete/selector-autocomplete-option.tsx @@ -2,15 +2,15 @@ import type { EntityPropertiesObject } from "@blockprotocol/graph"; import type { BaseUrl, VersionedUrl } from "@blockprotocol/type-system/slim"; import { Box, Paper, Popper, Stack, Tooltip, Typography } from "@mui/material"; import clsx from "clsx"; -import type { HTMLAttributes, ReactNode } from "react"; +import type { HTMLAttributes, ReactElement } from "react"; import { useRef, useState } from "react"; import { Chip } from "../chip"; import { GRID_CLICK_IGNORE_CLASS } from "../constants"; +import { EntityOrTypeIcon } from "../entity-or-type-icon"; import { FeatherRegularIcon } from "../icon-feather-regular"; import { ImageWithCheckedBackground } from "../image-with-checked-background"; import { OntologyChip } from "../ontology-chip"; -import { EntityTypeIcon } from "../ontology-icons"; import { parseUrlForOntologyChip } from "../parse-url-for-ontology-chip"; const descriptionPropertyKey: BaseUrl = @@ -24,32 +24,25 @@ const mimeTypePropertyKey: BaseUrl = const imageThumbnailWidth = 90; +type TypeDisplayData = { $id: VersionedUrl; icon?: string; title: string }; + export type SelectorAutocompleteOptionProps = { liProps: HTMLAttributes; description?: string; entityProperties?: EntityPropertiesObject; - icon: ReactNode | null; + icon: string | ReactElement | null; title: string; - /** the typeId associated with this entity type or entity, displayed as a chip in the option */ - typeIds: [VersionedUrl, ...VersionedUrl[]]; + types: [TypeDisplayData, ...TypeDisplayData[]]; draft?: boolean; }; -const slugToTitleCase = (slug?: string) => - slug - ? slug - .split("-") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" ") - : undefined; - export const SelectorAutocompleteOption = ({ liProps, description, entityProperties, icon, title, - typeIds, + types, draft = false, }: SelectorAutocompleteOptionProps) => { const optionRef = useRef(null); @@ -116,9 +109,24 @@ export const SelectorAutocompleteOption = ({ } - label={slugToTitleCase(typeIds[0].split("/").slice(-3, -2)[0])} + variant="outlined" + icon={ + typeof icon === "string" || !icon ? ( + + entityProperties ? palette.gray[50] : palette.blue[70] + } + fontSize={12} + icon={icon} + /* @todo H-3363 set this using closed schema */ + isLink={false} + /> + ) : ( + icon + ) + } + label={types[0].title} /> @@ -138,89 +146,100 @@ export const SelectorAutocompleteOption = ({ alignItems="center" maxWidth="50%" > - {icon ? ( - ({ + alignItems: "center", + background: palette.gray[10], + border: `1px solid ${palette.gray[30]}`, + borderRadius: 4, + height: 26, + })} + > + ({ alignItems: "center", - background: palette.gray[10], - border: `1px solid ${palette.gray[30]}`, + background: "white", + borderRight: `1px solid ${palette.gray[30]}`, borderRadius: 4, - height: 26, + display: "flex", + px: 1.2, + height: "100%", })} > - ({ - alignItems: "center", - background: "white", - borderRight: `1px solid ${palette.gray[30]}`, - borderRadius: 4, - display: "flex", - px: 1.2, - height: "100%", - fontSize: 14, - "> svg": { - fontSize: 14, - }, - })} - > - {icon} - - ({ - color: palette.black, - fontSize: 12, - fontWeight: 500, - px: 1.2, - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis", - })} - > - {title} - - - ) : ( + {typeof icon === "string" || !icon ? ( + + entityProperties ? palette.gray[50] : palette.blue[70] + } + fontSize={12} + icon={icon} + /* @todo H-3363 set this using closed schema */ + isLink={false} + /> + ) : ( + icon + )} + ({ + color: palette.black, + fontSize: 12, + fontWeight: 500, + px: 1.2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", - mr: 0.5, - fontWeight: 500, - }} + })} > {title} - )} + - {typeIds.map((typeId) => ( - - {entityProperties ? ( - } - color="gray" - label={slugToTitleCase(typeId.split("/").slice(-3, -2)[0])} - sx={{ - ml: 1, - }} - /> - ) : ( - ({ - border: `1px solid ${palette.gray[30]}`, - flexShrink: 1, - minWidth: 150, - ml: 1.25, - mr: 2, - })} - /> - )} - - ))} + + {types.map((type) => ( + + {entityProperties ? ( + + `1px solid ${palette.gray[30]}`, + borderRadius: 4, + }} + > + palette.blue[70]} + /* @todo H-3363 set this using closed schema */ + isLink={false} + sx={{ mr: 0.8 }} + /> + {type.title} + + ) : ( + ({ + border: `1px solid ${palette.gray[30]}`, + flexShrink: 1, + minWidth: 150, + ml: 1.25, + mr: 2, + })} + /> + )} + + ))} + {draft ? ( } diff --git a/libs/@hashintel/design-system/src/type-card.tsx b/libs/@hashintel/design-system/src/type-card.tsx index 9fb9b97f5ee..90f70893ab0 100644 --- a/libs/@hashintel/design-system/src/type-card.tsx +++ b/libs/@hashintel/design-system/src/type-card.tsx @@ -3,8 +3,8 @@ import { Box, Collapse, Tooltip, Typography } from "@mui/material"; import type { ElementType } from "react"; import { useState } from "react"; +import { EntityOrTypeIcon } from "./entity-or-type-icon"; import { FontAwesomeIcon } from "./fontawesome-icon"; -import { AsteriskRegularIcon } from "./icon-asterisk-regular"; import { IconButton } from "./icon-button"; import { CloseIcon } from "./icon-close"; import { WhiteCard } from "./white-card"; @@ -14,6 +14,7 @@ interface TypeCardProps { onDelete?: () => void; LinkComponent?: ElementType; icon?: string | null; + isLink: boolean; url: string; title: string; version: number; @@ -23,26 +24,12 @@ interface TypeCardProps { }; } -const TypeIcon = ({ icon }: { icon?: string | null }) => { - if (!icon) { - return ; - } - if ( - icon.startsWith("http://") || - icon.startsWith("https://") || - icon.startsWith("/") - ) { - return ; - } - - return icon; -}; - export const TypeCard = ({ disableClick, onDelete, LinkComponent, icon, + isLink, url, title, version, @@ -75,8 +62,14 @@ export const TypeCard = ({ whiteSpace: "nowrap", }} > - - + palette.blue[70]} + isLink={isLink} + icon={icon} + /> + {title} {` v${version}`} diff --git a/libs/@hashintel/design-system/theme-override.d.ts b/libs/@hashintel/design-system/theme-override.d.ts index 3d13bb45c6c..2ea5f91d5a5 100644 --- a/libs/@hashintel/design-system/theme-override.d.ts +++ b/libs/@hashintel/design-system/theme-override.d.ts @@ -228,6 +228,7 @@ declare module "@mui/material/Chip" { green: true; navy: true; aqua: true; + white: true; // Disable defaults default: false; primary: false; diff --git a/libs/@hashintel/type-editor/package.json b/libs/@hashintel/type-editor/package.json index b496cebe380..fe3fb0270ef 100644 --- a/libs/@hashintel/type-editor/package.json +++ b/libs/@hashintel/type-editor/package.json @@ -25,6 +25,7 @@ "@fortawesome/free-regular-svg-icons": "6.6.0", "@fortawesome/free-solid-svg-icons": "6.6.0", "@hashintel/design-system": "0.0.8", + "@local/hash-subgraph": "0.0.0-private", "clsx": "1.2.1", "lodash.memoize": "4.1.2", "lodash.uniqueid": "4.0.1", diff --git a/libs/@hashintel/type-editor/src/entity-type-editor/inheritance-row/inherited-type-card.tsx b/libs/@hashintel/type-editor/src/entity-type-editor/inheritance-row/inherited-type-card.tsx index eaba499109f..4dabef0c2a1 100644 --- a/libs/@hashintel/type-editor/src/entity-type-editor/inheritance-row/inherited-type-card.tsx +++ b/libs/@hashintel/type-editor/src/entity-type-editor/inheritance-row/inherited-type-card.tsx @@ -4,6 +4,8 @@ import { extractVersion, } from "@blockprotocol/type-system/slim"; import { TypeCard } from "@hashintel/design-system"; +// eslint-disable-next-line no-restricted-imports -- TODO remove this dependency to make package publishable +import { linkEntityTypeUrl } from "@local/hash-subgraph"; import { useFormContext, useWatch } from "react-hook-form"; import { useEntityTypesOptions } from "../../shared/entity-types-options-context"; @@ -52,9 +54,13 @@ export const InheritedTypeCard = ({ ); }; + /** @todo H-3363 take account of inheritance by using closed schema */ + const isLink = directParentEntityTypeIds.includes(linkEntityTypeUrl); + return ( option.$id === value.$id} options={entityTypeOptions} - optionToRenderData={({ $id, title, description }) => ({ + optionToRenderData={({ $id, title, icon, description }) => ({ uniqueId: $id, - icon: , - typeIds: [$id], + icon: icon ?? null, + types: [{ $id, title, icon }], title, description, })} diff --git a/libs/@hashintel/type-editor/src/entity-type-editor/link-list-card/destination-entity-type.tsx b/libs/@hashintel/type-editor/src/entity-type-editor/link-list-card/destination-entity-type.tsx index ba98eec6ffc..04059c7a252 100644 --- a/libs/@hashintel/type-editor/src/entity-type-editor/link-list-card/destination-entity-type.tsx +++ b/libs/@hashintel/type-editor/src/entity-type-editor/link-list-card/destination-entity-type.tsx @@ -32,6 +32,7 @@ export const DestinationEntityType = ({ label={ void; @@ -29,7 +30,13 @@ export const TypeChipLabel = ({ return ( - + palette.blue[70]} + /> {children} {!readonly && diff --git a/libs/@hashintel/type-editor/src/entity-type-editor/shared/insert-property-field/type-selector.tsx b/libs/@hashintel/type-editor/src/entity-type-editor/shared/insert-property-field/type-selector.tsx index 41b520ef7aa..e715e2752ce 100644 --- a/libs/@hashintel/type-editor/src/entity-type-editor/shared/insert-property-field/type-selector.tsx +++ b/libs/@hashintel/type-editor/src/entity-type-editor/shared/insert-property-field/type-selector.tsx @@ -50,8 +50,8 @@ export const TypeSelector = ({ inputRef={inputRef} isOptionEqualToValue={(option, value) => option.$id === value.$id} optionToRenderData={({ $id, Icon, title, description }) => ({ - typeIds: [$id], - icon: Icon ? : null, + types: [{ $id, title }], + icon: Icon ? : null, uniqueId: $id, title, description, diff --git a/libs/@hashintel/type-editor/src/entity-type-editor/shared/version-upgrade-indicator.tsx b/libs/@hashintel/type-editor/src/entity-type-editor/shared/version-upgrade-indicator.tsx index 9c23a8518df..a0ed6d753bf 100644 --- a/libs/@hashintel/type-editor/src/entity-type-editor/shared/version-upgrade-indicator.tsx +++ b/libs/@hashintel/type-editor/src/entity-type-editor/shared/version-upgrade-indicator.tsx @@ -33,6 +33,7 @@ export const VersionUpgradeIndicator = ({ fontSize: 11, fontWeight: 700, textTransform: "uppercase", + width: "auto", gap: 0.625, lineHeight: "18px", ":hover": { diff --git a/libs/@local/hash-subgraph/src/stdlib/subgraph/element/entity-type.ts b/libs/@local/hash-subgraph/src/stdlib/subgraph/element/entity-type.ts index c14c10796b0..bf2602483ac 100644 --- a/libs/@local/hash-subgraph/src/stdlib/subgraph/element/entity-type.ts +++ b/libs/@local/hash-subgraph/src/stdlib/subgraph/element/entity-type.ts @@ -88,7 +88,7 @@ export const getBreadthFirstEntityTypesAndParents = ( entityTypeIds: VersionedUrl[], ): EntityTypeWithMetadata[] => { const visited = new Set(); - const queue: VersionedUrl[] = [...entityTypeIds]; + const queue: VersionedUrl[] = [...entityTypeIds].toSorted(); const result: EntityTypeWithMetadata[] = []; while (queue.length > 0) {