Skip to content

Commit d8ca34a

Browse files
authored
Merge pull request #72 from kit-data-manager/restructure
Restructure and streamline parts of the repository, update exports
2 parents 880517b + aa9ed1d commit d8ca34a

31 files changed

+290
-198
lines changed

README.md

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
[![Node.js CI](https://github.com/kit-data-manager/react-fairdo-search/actions/workflows/build.yml/badge.svg)](https://github.com/kit-data-manager/react-fairdo-search/actions/workflows/build.yml)
55
![NPM Type Definitions](https://img.shields.io/npm/types/%40kit-data-manager%2Freact-fairdo-search)
66

7-
8-
> ⚠️ This component is in active development. Consider it **not** ready for production.
9-
10-
All-in-one React component for rendering an elastic search UI based on the provided configuration. Includes
7+
All-in-one, highly configurable React component for rendering an elastic search UI based on the provided configuration. Includes
118
an interactive graph of related records.
129

1310
This is an ESM Module intended for use in modern React applications. Make sure your bundler supports importing CSS files in JavaScript/TypeScript. Next.js is supported out of the box.
@@ -20,13 +17,55 @@ This is an ESM Module intended for use in modern React applications. Make sure y
2017

2118
## Docs
2219

23-
[Visit the Storybook](https://kit-data-manager.github.io/react-fairdo-search/?path=/docs/getting-started--docs)
20+
[Visit the Storybook](https://kit-data-manager.github.io/react-fairdo-search/?path=/docs/getting-started--docs) for examples and some documentation. For more documentation, consult the TypeScript typings.
21+
22+
## Quick Start
23+
24+
Refer to the Getting Started Guide in the Storybook
2425

2526
## Customization
2627

28+
### Result View
29+
30+
Most notably, the result view component should be customized. A `GenericResultView` Component is provided that should work
31+
for some scenarious out of the box. Otherwise, you should implement your own Result View in React. For a starting point, feel
32+
free to copy the code of `GenericResultView.ts`, though most of the customization should be thrown out to make it more lightweight.
33+
34+
### Generic Result View
35+
36+
Each field of the generic result view can be mapped to a field in the elastic search index. Additionally, you can specify multiple
37+
tags from multiple different fields in the elastic index. Take a look at the Storybook for an example.
38+
39+
To map (parts of) the result before feeding it to the `GenericResultView`, you can define you own component that itself
40+
uses the `GenericResultView` with modified data:
41+
42+
```typescript jsx
43+
import { useMemo } from "react"
44+
import { GenericResultView, ResultViewProps } from "@kit-data-manager/react-fairdo-search"
45+
46+
export function MyResultView(props: ResultViewProps) {
47+
const mappedResult = useMemo(() => {
48+
const copy = structuredClone(props.result)
49+
copy.someProperty = copy.something + copy.somethingElse
50+
return copy
51+
}, [props.result])
52+
53+
return <GenericResultView result={mappedResult} titleField={"someProperty"} />
54+
}
55+
```
56+
57+
Then simply pass your custom component to the search component.
58+
59+
### Styling
60+
2761
Styling is done using tailwind and css variables. Feel free to override these variables in your own CSS.
2862

29-
## Development
63+
64+
## Contributing
65+
66+
Feedback and issue reports are welcome, simply [create an issue](https://github.com/kit-data-manager/react-fairdo-search/issues/new) here on GitHub.
67+
68+
### Development
3069

3170
Install dependencies with
3271

src/components/FairDOElasticSearch.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client"
22

3-
import type { FairDOConfig } from "@/config/FairDOConfig"
3+
import type { FairDOConfig } from "@/lib/config/FairDOConfig"
44
import type { SearchContextState } from "@elastic/search-ui"
55
import { FairDOSearchProvider } from "@/components/FairDOSearchProvider"
66
import { RelationsGraphProvider } from "@/components/graph/RelationsGraphProvider"
@@ -9,18 +9,18 @@ import { DefaultFacet, OptionViewProps } from "@/components/search/DefaultFacet"
99
import { DefaultSearchBox } from "@/components/search/DefaultSearchBox"
1010
import { ErrorView } from "@/components/search/ErrorView"
1111
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
12-
import { FairDOConfigBuilder } from "@/config/FairDOConfigBuilder"
12+
import { FairDOConfigBuilder } from "@/lib/config/FairDOConfigBuilder"
1313
import { ErrorBoundary, Facet, Paging, PagingInfo, Results, ResultsPerPage, SearchBox, SearchProvider, WithSearch } from "@elastic/react-search-ui"
1414
import { Layout, ResultViewProps } from "@elastic/react-search-ui-views"
1515
import { LoaderCircle } from "lucide-react"
1616
import { ComponentType, useCallback, useMemo } from "react"
1717
import "../index.css"
1818
import "../elastic-ui.css"
1919
import { TooltipProvider } from "./ui/tooltip"
20-
import { useAutoDarkMode } from "@/components/utils"
2120
import { DefaultSorting } from "@/components/search/DefaultSorting"
2221
import { NodeTypes } from "@xyflow/react"
2322
import { ResultViewSelector } from "@/components/result/ResultViewSelector"
23+
import { useAutoDarkMode } from "@/lib/hooks"
2424

2525
/**
2626
* All-in-one component for rendering an elastic search UI based on the provided configuration. Includes

src/components/FairDOSearchContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import type { ResponseState } from "@elastic/search-ui"
44
import type ElasticsearchAPIConnector from "@elastic/search-ui-elasticsearch-connector"
55
import { createContext } from "react"
6-
import { FairDOConfig } from "@/config/FairDOConfig"
6+
import { FairDOConfig } from "@/lib/config/FairDOConfig"
77

88
/**
99
* Extends the elasticsearch SearchContext with additional utilities

src/components/FairDOSearchProvider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { FairDOConfig } from "@/config/FairDOConfig"
1+
import type { FairDOConfig } from "@/lib/config/FairDOConfig"
22
import type { SearchContextState } from "@elastic/search-ui"
33
import type { PropsWithChildren } from "react"
44
import { FairDOSearchContext } from "@/components/FairDOSearchContext"
5-
import { FairDOConfigBuilder } from "@/config/FairDOConfigBuilder"
5+
import { FairDOConfigBuilder } from "@/lib/config/FairDOConfigBuilder"
66
import { arrayToObjectEntries } from "@/lib/utils"
77
import { WithSearch } from "@elastic/react-search-ui"
88
import { useCallback, useMemo } from "react"

src/components/graph/GraphNodeUtils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { GraphNode } from "@/components/graph/GraphNode"
2-
import { toArray } from "@/components/result"
2+
3+
import { toArray } from "@/lib/utils"
34

45
/**
56
* Utilities for working with the RelationsGraph
67
*/
7-
export class GraphNodeUtils {
8+
export abstract class GraphNodeUtils {
89
/**
910
* Build a sequential graph (n:n:n:...) by passing just the identifiers of the nodes in layers.
1011
* @param type Type of the nodes (use "result" to display your resultView)

src/components/graph/GraphUtils.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Edge, Node } from "@xyflow/react"
2+
import Dagre from "@dagrejs/dagre"
3+
import { GraphNode } from "@/components/graph/GraphNode"
4+
5+
export abstract class GraphUtils {
6+
public static buildGraphFromNodes(nodes: GraphNode[]) {
7+
const initialNodes: { id: string; type: string; position: { x: number; y: number }; data: Record<string, unknown> }[] = nodes.map((node) => ({
8+
type: node.type,
9+
id: node.id,
10+
position: { x: 0, y: 0 },
11+
data: node.data ?? {}
12+
}))
13+
14+
const initialEdges: { id: string; source: string; target: string }[] = []
15+
16+
for (const node of nodes) {
17+
for (const out of node.out) {
18+
initialEdges.push({
19+
id: `${node.id}-${out}`,
20+
source: node.id,
21+
target: out
22+
})
23+
}
24+
}
25+
26+
return { initialNodes, initialEdges }
27+
}
28+
29+
public static computeNodeLayout(nodes: (Node & { type: string })[], edges: Edge[]) {
30+
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}))
31+
g.setGraph({ rankdir: "LR", nodesep: 30, ranksep: 150 })
32+
33+
edges.forEach((edge) => g.setEdge(edge.source, edge.target))
34+
nodes.forEach((node) =>
35+
g.setNode(node.id, {
36+
...node,
37+
width: node.measured?.width ?? 0,
38+
height: node.measured?.height ?? 0
39+
})
40+
)
41+
42+
Dagre.layout(g)
43+
44+
return {
45+
nodes: nodes.map((node) => {
46+
const position = g.node(node.id)
47+
// We are shifting the dagre node position (anchor=center center) to the top left
48+
// so it matches the React Flow node anchor point (top left).
49+
const x = position.x - (node.measured?.width ?? 0) / 2
50+
const y = position.y - (node.measured?.height ?? 0) / 2
51+
52+
return { ...node, position: { x, y } }
53+
}),
54+
edges
55+
}
56+
}
57+
}

src/components/graph/RelationsGraph.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { buildGraphFromNodes, computeNodeLayout } from "@/components/graph/helpers"
1+
import { GraphUtils } from "@/components/graph/GraphUtils"
22
import {
33
Background,
44
BackgroundVariant,
@@ -40,7 +40,7 @@ export function RelationsGraph(props: {
4040
}, [])
4141

4242
const { initialEdges, initialNodes } = useMemo(() => {
43-
return buildGraphFromNodes(props.nodes)
43+
return GraphUtils.buildGraphFromNodes(props.nodes)
4444
}, [props.nodes])
4545

4646
const nodeTypes = useMemo(() => {
@@ -62,7 +62,7 @@ export function RelationsGraph(props: {
6262
}, [initialEdges, initialNodes, setEdges, setNodes])
6363

6464
const onLayout = useCallback(() => {
65-
const layouted = computeNodeLayout(nodes, edges)
65+
const layouted = GraphUtils.computeNodeLayout(nodes, edges)
6666

6767
setNodes([...layouted.nodes])
6868
setEdges([...layouted.edges])

src/components/graph/helpers.ts

Lines changed: 0 additions & 55 deletions
This file was deleted.

src/components/graph/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
export * from "./RelationsGraph"
22
export * from "./RelationsGraphModal"
3-
export * from "./ResultViewWrapper"
4-
export * from "./helpers"
53
export * from "./GraphNode"
64
export * from "./GraphNodeUtils"
5+
export * from "./RelationsGraphContext"
6+
export * from "./RelationsGraphOptions"
7+
8+
export type * from "./RelationsGraph"
9+
export type * from "./RelationsGraphModal"
10+
export type * from "./GraphNode"
11+
export type * from "./GraphNodeUtils"
12+
export type * from "./RelationsGraphContext"
13+
export type * from "./RelationsGraphOptions"

src/components/result/GenericResultView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { RelationsGraphContext } from "@/components/graph/RelationsGraphContext"
33
import { FairDOSearchContext } from "@/components/FairDOSearchContext"
44
import { useStore } from "zustand/index"
55
import { resultCache } from "@/lib/ResultCache"
6-
import { autoUnwrap, autoUnwrapArray, toArray } from "@/components/result/utils"
76
import { DateTime } from "luxon"
87
import { ChevronDown, Download, GitFork, LoaderCircle, Microscope, Pencil, PlusIcon, SearchIcon, TableProperties } from "lucide-react"
98
import { Badge } from "@/components/ui/badge"
@@ -16,6 +15,7 @@ import { GenericResultViewImage } from "@/components/result/GenericResultViewIma
1615
import { GraphNodeUtils } from "@/components/graph/GraphNodeUtils"
1716
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"
1817
import { PidComponent } from "@kit-data-manager/react-pid-component"
18+
import { autoUnwrap, autoUnwrapArray, toArray } from "@/lib/utils"
1919

2020
const HTTP_REGEX = /https?:\/\/.*/
2121

src/components/result/GenericResultViewTag.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { MouseEvent, ReactNode, useCallback, useEffect, useMemo, useState } from
22
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
33
import { Badge } from "@/components/ui/badge"
44
import { SearchResult } from "@elastic/search-ui"
5-
import { autoUnwrap } from "@/components/result/utils"
65
import { useCopyToClipboard } from "usehooks-ts"
76
import { CheckIcon } from "lucide-react"
7+
import { autoUnwrap } from "@/lib/utils"
88

99
export interface GenericResultViewTagProps {
1010
/**

src/components/result/ObjectRender.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { PidDisplay } from "@/components/result/PidDisplay"
2-
import { PidResolver } from "@/lib/pidResolver"
1+
import { PidNameDisplay } from "@/components/result/PidNameDisplay"
2+
import { PidResolver } from "@/lib/PidResolver"
33

44
/**
55
* @internal
@@ -9,7 +9,7 @@ import { PidResolver } from "@/lib/pidResolver"
99
export function ObjectRender({ data }: { data: Record<string, unknown> }) {
1010
if ("raw" in data && typeof data.raw === "string") {
1111
if (PidResolver.isPID(data.raw)) {
12-
return <PidDisplay pid={data.raw} />
12+
return <PidNameDisplay pid={data.raw} />
1313
} else {
1414
return <div>{data.raw}</div>
1515
}

src/components/result/PidDisplay.tsx renamed to src/components/result/PidNameDisplay.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { PidResolver, pidResolver } from "@/lib/pidResolver"
1+
import { PidResolver, pidResolver } from "@/lib/PidResolver"
22
import { memo, useCallback } from "react"
33
import { tempResolver } from "@/lib/TempResolver"
44
import useSWRImmutable from "swr/immutable"
@@ -8,15 +8,8 @@ import useSWRImmutable from "swr/immutable"
88
* @param pid A valid PID
99
* @constructor
1010
*/
11-
export const PidDisplay = memo(function PidDisplay({ pid }: { pid: string }) {
11+
export const PidNameDisplay = memo(function PidNameDisplay({ pid }: { pid: string }) {
1212
const resolveContent = useCallback(async (pid: string) => {
13-
// Hardcoded for NEP deployment, should be removed in the future if possible!
14-
if (pid === "21.T11981/935ad20c-e8f7-485d-8987-b4f22431ff4b") {
15-
return "chemotion-repository.net"
16-
} else if (pid === "21.T11981/352621cf-b0c6-4105-89a4-324f16cf7776") {
17-
return "nmrxiv.org"
18-
}
19-
2013
if (PidResolver.isPID(pid)) {
2114
const content = await pidResolver.resolve(pid)
2215
return content.name

src/components/result/index.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
export * from "./GenericResultView"
2-
export type * from "./GenericResultView"
32
export * from "./GenericResultViewImageCarousel"
4-
export type * from "./GenericResultViewImageCarousel"
5-
export * from "./PidDisplay"
6-
export type * from "./PidDisplay"
7-
export * from "./utils"
3+
export * from "./PidNameDisplay"
84
export * from "./OrcidDisplay"
5+
export * from "./GenericResultViewImage"
6+
export * from "./GenericResultViewTag"
7+
8+
export type * from "./GenericResultView"
9+
export type * from "./GenericResultViewImageCarousel"
10+
export type * from "./PidNameDisplay"
911
export type * from "./OrcidDisplay"
12+
export type * from "./GenericResultViewImage"
13+
export type * from "./GenericResultViewTag"

src/components/result/utils.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)