From 3d613d4b600d117537fdddbb81a3ea7e9a9ee8a4 Mon Sep 17 00:00:00 2001 From: Christopher Raquet Date: Mon, 28 Apr 2025 13:05:11 +0200 Subject: [PATCH 1/2] Fix generic result view tag types --- .../result/GenericResultViewTag.tsx | 55 ++++++++----------- src/lib/config/SearchConfig.ts | 4 +- src/lib/utils.ts | 2 +- .../Others/GenericResultView.stories.tsx | 6 +- src/stories/ReactSearchComponent.stories.tsx | 12 ++-- 5 files changed, 35 insertions(+), 44 deletions(-) diff --git a/src/components/result/GenericResultViewTag.tsx b/src/components/result/GenericResultViewTag.tsx index b56984e..aa590e5 100644 --- a/src/components/result/GenericResultViewTag.tsx +++ b/src/components/result/GenericResultViewTag.tsx @@ -1,7 +1,7 @@ import { MouseEvent, ReactNode, useCallback, useEffect, useMemo, useState } from "react" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import { Badge } from "@/components/ui/badge" -import { SearchResult } from "@elastic/search-ui" +import { SearchResult, FieldValue } from "@elastic/search-ui" import { useCopyToClipboard } from "usehooks-ts" import { CheckIcon } from "lucide-react" import { autoUnwrap } from "@/lib/utils" @@ -11,6 +11,10 @@ export interface GenericResultViewTagProps { * The elasticsearch field that this tag will display */ field: string + /** + * When specified, does not read the field from elastic and instead just displays this value + */ + valueOverride?: string | number | boolean result: SearchResult /** * Icon for this tag, can be any react component. Ideally a [lucide icon](https://lucide.dev) with 16px by 16px site. @@ -25,27 +29,20 @@ export interface GenericResultViewTagProps { * Can't the used together with `singleValueMapper` * @param value */ - valueMapper?: (value: string | string[]) => ReactNode + valueMapper?: (value: FieldValue) => ReactNode /** * Optional, here you can map each value of the elasticsearch field to a string or a React component. Can't be used * together with `valueMapper` * @param value */ - singleValueMapper?: (value: string) => ReactNode - onClick?: (e: MouseEvent, tagValue: ReactNode, fieldValue: string | string[]) => void + singleValueMapper?: (value: string | number | boolean) => ReactNode + onClick?: (e: MouseEvent, tagValue: ReactNode, fieldValue: FieldValue) => void clickBehavior?: "copy-text" | "follow-url" | string } -export function GenericResultViewTag({ - field, - result, - icon, - label, - valueMapper, - singleValueMapper, - clickBehavior = "copy-text", - onClick -}: GenericResultViewTagProps) { +export function GenericResultViewTag(props: GenericResultViewTagProps) { + const { field, valueOverride, result, icon, label, valueMapper, singleValueMapper, clickBehavior = "copy-text", onClick } = props + const [showCopiedNotice, setShowCopiedNotice] = useState(false) useEffect(() => { @@ -57,11 +54,12 @@ export function GenericResultViewTag({ }, [showCopiedNotice]) const fieldValue = useMemo(() => { - return autoUnwrap(result[field]) as string | string[] - }, [field, result]) + if (valueOverride !== undefined) return valueOverride + return autoUnwrap(result[field]) as FieldValue + }, [field, result, valueOverride]) const value = useMemo(() => { - if (!fieldValue) return undefined + if (fieldValue === null || fieldValue === undefined) return undefined if (valueMapper) return valueMapper(fieldValue) if (singleValueMapper) return Array.isArray(fieldValue) ? fieldValue.map(singleValueMapper) : singleValueMapper(fieldValue) else return fieldValue @@ -79,13 +77,13 @@ export function GenericResultViewTag({ ) const handleClick = useCallback( - (fieldValue: string | string[], value: ReactNode, e: MouseEvent) => { + (fieldValue: FieldValue, value: ReactNode, e: MouseEvent) => { if (onClick) onClick(e, value, fieldValue) if (clickBehavior === "copy-text" && !showCopiedNotice) { copyTagValue(e) setShowCopiedNotice(true) } else if (clickBehavior === "follow-url" && !Array.isArray(fieldValue)) { - window.open(fieldValue, "_blank") + window.open(fieldValue.toString(), "_blank") } }, [clickBehavior, copyTagValue, onClick, showCopiedNotice] @@ -101,7 +99,7 @@ export function GenericResultViewTag({ }, [clickBehavior, onClick]) const base = useCallback( - (fieldValue: string | string[], value: ReactNode, key?: string) => { + (fieldValue: FieldValue, value: ReactNode, key?: string) => { return ( handleClick(fieldValue, value, e)}> @@ -121,19 +119,12 @@ export function GenericResultViewTag({ [handleClick, icon, showCopiedNotice] ) - if (!label) return Array.isArray(value) ? value.map((v, i) => base(fieldValue[value.indexOf(v)], v, field + i)) : base(fieldValue, value) - if (!value) return null + if (value === undefined) return null - if (Array.isArray(value)) { - return value.map((entry, i) => ( - - {base(fieldValue[value.indexOf(entry)], entry)} - -
{label}
-
{clickBehaviourText}
-
-
- )) + if (Array.isArray(value) && Array.isArray(fieldValue)) { + return value.map((entry, i) => ) + } else if (!label) { + return base(fieldValue, value) } return ( diff --git a/src/lib/config/SearchConfig.ts b/src/lib/config/SearchConfig.ts index 8610a63..ebaeab2 100644 --- a/src/lib/config/SearchConfig.ts +++ b/src/lib/config/SearchConfig.ts @@ -1,4 +1,4 @@ -import { FilterType, FilterValueValue, RequestState, SearchFieldConfiguration, SortOption } from "@elastic/search-ui" +import { FieldValue, FilterType, RequestState, SearchFieldConfiguration, SortOption } from "@elastic/search-ui" import { ReactNode } from "react" export interface CoreFacetConfig { @@ -32,7 +32,7 @@ export interface CoreFacetConfig { * * @param value */ - singleValueMapper?: (value: FilterValueValue) => ReactNode + singleValueMapper?: (value: FieldValue) => ReactNode /** * Not properly implemented at the moment. Use with caution. diff --git a/src/lib/utils.ts b/src/lib/utils.ts index dde6c73..7ad043b 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -26,7 +26,7 @@ export function prettyPrintURL(url: string) { } export function autoUnwrap(item?: E | { raw?: E }) { - if (!item) { + if (item === undefined || item === null) { return undefined } else if (typeof item === "object" && "raw" in item) { return item.raw diff --git a/src/stories/Others/GenericResultView.stories.tsx b/src/stories/Others/GenericResultView.stories.tsx index 1d420eb..a190685 100644 --- a/src/stories/Others/GenericResultView.stories.tsx +++ b/src/stories/Others/GenericResultView.stories.tsx @@ -123,14 +123,14 @@ export const Full: Story = { { icon: , field: "hadPrimarySource", - singleValueMapper: prettyPrintURL, + singleValueMapper: (v) => prettyPrintURL(v + ""), label: "Primary Source", clickBehavior: "follow-url" }, { icon: , field: "licenseURL", - singleValueMapper: prettyPrintURL, + singleValueMapper: (v) => prettyPrintURL(v + ""), label: "License URL", clickBehavior: "follow-url" }, @@ -161,7 +161,7 @@ export const Full: Story = { }, { field: "stringArrayTest", - singleValueMapper: (v) => v.toUpperCase() + singleValueMapper: (v) => (v + "").toUpperCase() } ] } diff --git a/src/stories/ReactSearchComponent.stories.tsx b/src/stories/ReactSearchComponent.stories.tsx index ab80054..ec5997e 100644 --- a/src/stories/ReactSearchComponent.stories.tsx +++ b/src/stories/ReactSearchComponent.stories.tsx @@ -159,7 +159,7 @@ export const GenericResultRenderer: Story = { icon: , label: "Contact", field: "contact", - singleValueMapper: (v) => , + singleValueMapper: (v) => , clickBehavior: "follow-url" }, { @@ -170,7 +170,7 @@ export const GenericResultRenderer: Story = { { icon: , field: "hadPrimarySource", - singleValueMapper: (v) => mapPrimarySource(v), + singleValueMapper: (v) => mapPrimarySource(v + ""), label: "Source", onClick: (e) => "innerText" in e.target && @@ -180,7 +180,7 @@ export const GenericResultRenderer: Story = { { icon: , field: "licenseURL", - singleValueMapper: (v) => , + singleValueMapper: (v) => , label: "License URL", clickBehavior: "follow-url" }, @@ -194,13 +194,13 @@ export const GenericResultRenderer: Story = { icon: , label: "NMR Method", field: "NMR_Method", - singleValueMapper: (v) => + singleValueMapper: (v) => }, { icon: , label: "NMR Solvent", field: "NMR_Solvent", - singleValueMapper: (v) => + singleValueMapper: (v) => }, { icon: , @@ -211,7 +211,7 @@ export const GenericResultRenderer: Story = { icon: , label: "Acquisition Nucleus", field: "Acquisition_Nucleus", - singleValueMapper: (v) => + singleValueMapper: (v) => } ]} titleField="name" From 2337a83aafaf49456498cb340d9fc129139d3a77 Mon Sep 17 00:00:00 2001 From: Christopher Raquet Date: Mon, 28 Apr 2025 13:49:04 +0200 Subject: [PATCH 2/2] Fix tsc-alias version --- package-lock.json | 9 ++++----- package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 500c612..a62fe6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,7 +80,7 @@ "tailwindcss-animate": "^1.0.7", "tailwindcss-scoped-preflight": "^3.4.12", "ts-loader": "^9.5.2", - "tsc-alias": "^1.8.10", + "tsc-alias": "1.8.13", "typescript": "^5.7.3", "typescript-eslint": "^8.15.0", "vite": "^6.0.9", @@ -18277,15 +18277,14 @@ "license": "MIT" }, "node_modules/tsc-alias": { - "version": "1.8.15", - "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.15.tgz", - "integrity": "sha512-yKLVx8ddUurRwhVcS6JFF2ZjksOX2ZWDRIdgt+PQhJBDegIdAdilptiHsuAbx9UFxa16GFrxeKQ2kTcGvR6fkQ==", + "version": "1.8.13", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.13.tgz", + "integrity": "sha512-hpuglrm2DoHZE62L8ntYqRNiSQ7J8kvIxEsajzY/QfGOm7EcdhgG5asqoWYi2E2KX0SqUuhOTnV8Ry8D/TnsEA==", "dev": true, "license": "MIT", "dependencies": { "chokidar": "^3.5.3", "commander": "^9.0.0", - "get-tsconfig": "^4.10.0", "globby": "^11.0.4", "mylas": "^2.1.9", "normalize-path": "^3.0.0", diff --git a/package.json b/package.json index 1ec5d90..47b4d91 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "tailwindcss-animate": "^1.0.7", "tailwindcss-scoped-preflight": "^3.4.12", "ts-loader": "^9.5.2", - "tsc-alias": "^1.8.10", + "tsc-alias": "1.8.13", "typescript": "^5.7.3", "typescript-eslint": "^8.15.0", "vite": "^6.0.9",