@xyo-network/react-node-renderer 2.47.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +165 -0
- package/README.md +13 -0
- package/dist/cjs/components/RelationalGraph.js +24 -0
- package/dist/cjs/components/RelationalGraph.js.map +1 -0
- package/dist/cjs/components/index.js +5 -0
- package/dist/cjs/components/index.js.map +1 -0
- package/dist/cjs/components/story/TestData.js +48 -0
- package/dist/cjs/components/story/TestData.js.map +1 -0
- package/dist/cjs/components/story/index.js +5 -0
- package/dist/cjs/components/story/index.js.map +1 -0
- package/dist/cjs/hooks/index.js +6 -0
- package/dist/cjs/hooks/index.js.map +1 -0
- package/dist/cjs/hooks/useCytoscapeElements.js +68 -0
- package/dist/cjs/hooks/useCytoscapeElements.js.map +1 -0
- package/dist/cjs/hooks/useCytoscapeOptions.js +54 -0
- package/dist/cjs/hooks/useCytoscapeOptions.js.map +1 -0
- package/dist/cjs/hooks/useIcons.js +33 -0
- package/dist/cjs/hooks/useIcons.js.map +1 -0
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lib/index.js +5 -0
- package/dist/cjs/lib/index.js.map +1 -0
- package/dist/cjs/lib/utils.js +41 -0
- package/dist/cjs/lib/utils.js.map +1 -0
- package/dist/docs.json +18746 -0
- package/dist/esm/components/RelationalGraph.js +22 -0
- package/dist/esm/components/RelationalGraph.js.map +1 -0
- package/dist/esm/components/index.js +2 -0
- package/dist/esm/components/index.js.map +1 -0
- package/dist/esm/components/story/TestData.js +45 -0
- package/dist/esm/components/story/TestData.js.map +1 -0
- package/dist/esm/components/story/index.js +2 -0
- package/dist/esm/components/story/index.js.map +1 -0
- package/dist/esm/hooks/index.js +3 -0
- package/dist/esm/hooks/index.js.map +1 -0
- package/dist/esm/hooks/useCytoscapeElements.js +61 -0
- package/dist/esm/hooks/useCytoscapeElements.js.map +1 -0
- package/dist/esm/hooks/useCytoscapeOptions.js +50 -0
- package/dist/esm/hooks/useCytoscapeOptions.js.map +1 -0
- package/dist/esm/hooks/useIcons.js +28 -0
- package/dist/esm/hooks/useIcons.js.map +1 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib/index.js +2 -0
- package/dist/esm/lib/index.js.map +1 -0
- package/dist/esm/lib/utils.js +36 -0
- package/dist/esm/lib/utils.js.map +1 -0
- package/dist/types/components/RelationalGraph.d.ts +9 -0
- package/dist/types/components/RelationalGraph.d.ts.map +1 -0
- package/dist/types/components/index.d.ts +2 -0
- package/dist/types/components/index.d.ts.map +1 -0
- package/dist/types/components/story/TestData.d.ts +3 -0
- package/dist/types/components/story/TestData.d.ts.map +1 -0
- package/dist/types/components/story/index.d.ts +2 -0
- package/dist/types/components/story/index.d.ts.map +1 -0
- package/dist/types/hooks/index.d.ts +3 -0
- package/dist/types/hooks/index.d.ts.map +1 -0
- package/dist/types/hooks/useCytoscapeElements.d.ts +6 -0
- package/dist/types/hooks/useCytoscapeElements.d.ts.map +1 -0
- package/dist/types/hooks/useCytoscapeOptions.d.ts +3 -0
- package/dist/types/hooks/useCytoscapeOptions.d.ts.map +1 -0
- package/dist/types/hooks/useIcons.d.ts +3 -0
- package/dist/types/hooks/useIcons.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/lib/index.d.ts +2 -0
- package/dist/types/lib/index.d.ts.map +1 -0
- package/dist/types/lib/utils.d.ts +4 -0
- package/dist/types/lib/utils.d.ts.map +1 -0
- package/package.json +91 -0
- package/src/components/RelationalGraph.stories.tsx +86 -0
- package/src/components/RelationalGraph.tsx +35 -0
- package/src/components/index.ts +1 -0
- package/src/components/story/TestData.tsx +49 -0
- package/src/components/story/index.ts +1 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useCytoscapeElements.ts +67 -0
- package/src/hooks/useCytoscapeOptions.ts +57 -0
- package/src/hooks/useIcons.tsx +33 -0
- package/src/index.ts +2 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/utils.ts +41 -0
- package/src/types/images.d.ts +5 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FlexBoxProps, FlexCol } from '@xylabs/react-flexbox'
|
|
2
|
+
import { WithChildren } from '@xylabs/react-shared'
|
|
3
|
+
import { useShareForwardedRef } from '@xyo-network/react-shared'
|
|
4
|
+
import cytoscape, { Core, CytoscapeOptions } from 'cytoscape'
|
|
5
|
+
import { forwardRef, useEffect, useState } from 'react'
|
|
6
|
+
|
|
7
|
+
export interface NodeRelationalGraph extends WithChildren<FlexBoxProps> {
|
|
8
|
+
options?: CytoscapeOptions
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const NodeRelationalGraph = forwardRef<HTMLDivElement, NodeRelationalGraph>(({ children, options, ...props }, ref) => {
|
|
12
|
+
const sharedRef = useShareForwardedRef(ref)
|
|
13
|
+
// TODO - likely a value to stick in context
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
15
|
+
const [cy, setCy] = useState<Core>()
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (sharedRef) {
|
|
19
|
+
setCy(
|
|
20
|
+
cytoscape({
|
|
21
|
+
container: sharedRef.current,
|
|
22
|
+
...options,
|
|
23
|
+
}),
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}, [options, sharedRef])
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<FlexCol ref={sharedRef} {...props}>
|
|
30
|
+
{children}
|
|
31
|
+
</FlexCol>
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
NodeRelationalGraph.displayName = 'NodeRelationalGraph'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './RelationalGraph'
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { CytoscapeOptions } from 'cytoscape'
|
|
2
|
+
|
|
3
|
+
const elements: CytoscapeOptions['elements'] = [
|
|
4
|
+
{
|
|
5
|
+
// node a
|
|
6
|
+
data: { id: 'a', name: 'element a' },
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
// node b
|
|
10
|
+
data: { id: 'b', name: 'element b' },
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
// node c
|
|
14
|
+
data: { id: 'c', name: 'element c' },
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
// edge ab
|
|
18
|
+
data: { id: 'ab', source: 'a', target: 'b' },
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
// edge ac
|
|
22
|
+
data: { id: 'ac', source: 'a', target: 'c' },
|
|
23
|
+
},
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
const style: CytoscapeOptions['style'] = [
|
|
27
|
+
{
|
|
28
|
+
selector: 'node',
|
|
29
|
+
style: {
|
|
30
|
+
label: 'data(id)',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
selector: 'edge',
|
|
36
|
+
style: {
|
|
37
|
+
'curve-style': 'bezier',
|
|
38
|
+
'line-color': '#ccc',
|
|
39
|
+
'target-arrow-color': '#ccc',
|
|
40
|
+
'target-arrow-shape': 'triangle',
|
|
41
|
+
width: 3,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
export const options: CytoscapeOptions = {
|
|
47
|
+
elements,
|
|
48
|
+
style,
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TestData'
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useAsyncEffect } from '@xylabs/react-shared'
|
|
2
|
+
import { ModuleWrapper } from '@xyo-network/module'
|
|
3
|
+
import { NodeWrapper } from '@xyo-network/node'
|
|
4
|
+
import { useProvidedWrappedNode } from '@xyo-network/react-node'
|
|
5
|
+
import { CytoscapeOptions } from 'cytoscape'
|
|
6
|
+
import { useState } from 'react'
|
|
7
|
+
|
|
8
|
+
import { parseModuleType } from '../lib'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Note: Relies on describe but could eventually be converted to a discover call
|
|
12
|
+
* Logic would be similar to what the bridge does
|
|
13
|
+
*/
|
|
14
|
+
export const useCytoscapeElements = () => {
|
|
15
|
+
const [node] = useProvidedWrappedNode()
|
|
16
|
+
const [elements, setElements] = useState<CytoscapeOptions['elements']>()
|
|
17
|
+
|
|
18
|
+
useAsyncEffect(
|
|
19
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
20
|
+
async () => {
|
|
21
|
+
if (node) {
|
|
22
|
+
try {
|
|
23
|
+
const newElements = []
|
|
24
|
+
const wrapper = NodeWrapper.wrap(node)
|
|
25
|
+
const description = await wrapper?.describe()
|
|
26
|
+
const rootNodeId = description.name ?? description.address.substring(0, 6)
|
|
27
|
+
newElements.push({
|
|
28
|
+
data: { id: rootNodeId, type: parseModuleType(description.queries) },
|
|
29
|
+
})
|
|
30
|
+
const children = description.children
|
|
31
|
+
await Promise.all(
|
|
32
|
+
(children ?? [])?.map(async (address) => {
|
|
33
|
+
const [result] = await wrapper.resolveWrapped(ModuleWrapper, { address: [address] })
|
|
34
|
+
try {
|
|
35
|
+
const description = await result.describe()
|
|
36
|
+
const newNodeId = description.name ?? description.address.substring(0, 6)
|
|
37
|
+
const newNode = {
|
|
38
|
+
data: {
|
|
39
|
+
id: newNodeId,
|
|
40
|
+
type: parseModuleType(description.queries),
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
newElements.push(newNode)
|
|
44
|
+
const newEdge = {
|
|
45
|
+
data: {
|
|
46
|
+
id: `${rootNodeId}/${newNodeId}`,
|
|
47
|
+
source: rootNodeId,
|
|
48
|
+
target: newNodeId,
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
newElements.push(newEdge)
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error(e, result)
|
|
54
|
+
}
|
|
55
|
+
}),
|
|
56
|
+
)
|
|
57
|
+
setElements(newElements)
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.error(e)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
[node],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return elements
|
|
67
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useTheme } from '@mui/material'
|
|
2
|
+
import { CytoscapeOptions } from 'cytoscape'
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
|
|
5
|
+
import { CyNodeIcons, useIcons } from './useIcons'
|
|
6
|
+
|
|
7
|
+
export const useCytoscapeOptions = (elements: CytoscapeOptions['elements']) => {
|
|
8
|
+
const theme = useTheme()
|
|
9
|
+
const icons = useIcons()
|
|
10
|
+
|
|
11
|
+
const options = useMemo<CytoscapeOptions>(
|
|
12
|
+
() => ({
|
|
13
|
+
elements,
|
|
14
|
+
layout: {
|
|
15
|
+
minNodeSpacing: 75,
|
|
16
|
+
name: 'concentric',
|
|
17
|
+
},
|
|
18
|
+
style: [
|
|
19
|
+
{
|
|
20
|
+
selector: 'node[id]',
|
|
21
|
+
style: {
|
|
22
|
+
color: theme.palette.text.primary,
|
|
23
|
+
'font-family': 'Lexend Deca, Helvetica, sans-serif',
|
|
24
|
+
'font-size': 14,
|
|
25
|
+
'text-margin-y': -5,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
selector: 'node',
|
|
30
|
+
style: {
|
|
31
|
+
'background-color': theme.palette.primary.main,
|
|
32
|
+
'background-height': '75%',
|
|
33
|
+
// TODO - make dynamic
|
|
34
|
+
'background-image': (elem) => icons[elem.data('type') as CyNodeIcons],
|
|
35
|
+
'background-image-smoothing': 'yes',
|
|
36
|
+
'background-width': '75%',
|
|
37
|
+
label: 'data(id)',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
selector: 'edge',
|
|
42
|
+
style: {
|
|
43
|
+
'curve-style': 'bezier',
|
|
44
|
+
'line-color': theme.palette.divider,
|
|
45
|
+
'line-opacity': 0.1,
|
|
46
|
+
'target-arrow-color': theme.palette.divider,
|
|
47
|
+
'target-arrow-shape': 'triangle',
|
|
48
|
+
width: 3,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
}),
|
|
53
|
+
[elements, icons, theme.palette.divider, theme.palette.primary.main, theme.palette.text.primary],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return options
|
|
57
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import GridViewRoundedIcon from '@mui/icons-material/GridViewRounded'
|
|
2
|
+
import HubIcon from '@mui/icons-material/Hub'
|
|
3
|
+
import QuestionMarkRoundedIcon from '@mui/icons-material/QuestionMarkRounded'
|
|
4
|
+
import VisibilityRoundedIcon from '@mui/icons-material/VisibilityRounded'
|
|
5
|
+
import { SvgIconTypeMap, useTheme } from '@mui/material'
|
|
6
|
+
import { OverridableComponent } from '@mui/material/OverridableComponent'
|
|
7
|
+
import { useMemo } from 'react'
|
|
8
|
+
|
|
9
|
+
import { encodeSvg } from '../lib'
|
|
10
|
+
|
|
11
|
+
export type CyNodeIcons = 'node' | 'archivist' | 'diviner' | 'module'
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
14
|
+
const CyIconSet: Record<CyNodeIcons, OverridableComponent<SvgIconTypeMap<{}, 'svg'>>> = {
|
|
15
|
+
archivist: GridViewRoundedIcon,
|
|
16
|
+
diviner: VisibilityRoundedIcon,
|
|
17
|
+
module: QuestionMarkRoundedIcon,
|
|
18
|
+
node: HubIcon,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const useIcons = () => {
|
|
22
|
+
const theme = useTheme()
|
|
23
|
+
const icons = useMemo(() => {
|
|
24
|
+
const iconMap: Record<CyNodeIcons, string> = { archivist: '', diviner: '', module: '', node: '' }
|
|
25
|
+
return Object.entries(CyIconSet).reduce((acc, [name, IconComponent]) => {
|
|
26
|
+
const icon = <IconComponent fontSize="small" />
|
|
27
|
+
acc[name as CyNodeIcons] = encodeSvg(icon, theme.palette.getContrastText(theme.palette.text.primary))
|
|
28
|
+
return acc
|
|
29
|
+
}, iconMap)
|
|
30
|
+
}, [theme.palette])
|
|
31
|
+
|
|
32
|
+
return icons
|
|
33
|
+
}
|
package/src/index.ts
ADDED
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './utils'
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ReactElement } from 'react'
|
|
2
|
+
// eslint-disable-next-line import/no-internal-modules
|
|
3
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
4
|
+
|
|
5
|
+
const dataUri = 'data:image/svg+xml,'
|
|
6
|
+
|
|
7
|
+
export const encodeSvg = (reactElement: ReactElement, color?: string) => {
|
|
8
|
+
const svgString = renderToStaticMarkup(reactElement)
|
|
9
|
+
|
|
10
|
+
const doc = new DOMParser().parseFromString(svgString, 'text/html')
|
|
11
|
+
const svgElement = doc.getElementsByTagName('svg')[0]
|
|
12
|
+
if (svgElement) {
|
|
13
|
+
svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
|
|
14
|
+
svgElement.setAttribute('height', '100')
|
|
15
|
+
svgElement.style.fill = color ?? 'black'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return `${dataUri}${window.encodeURIComponent(svgElement.outerHTML)}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const parseModuleType = (queries?: string[]) => {
|
|
22
|
+
let type
|
|
23
|
+
if (queries) {
|
|
24
|
+
for (let i = 0; i < queries.length; i++) {
|
|
25
|
+
if (queries[i].includes('archivist')) {
|
|
26
|
+
type = 'archivist'
|
|
27
|
+
break
|
|
28
|
+
}
|
|
29
|
+
if (queries[i].includes('diviner')) {
|
|
30
|
+
type = 'diviner'
|
|
31
|
+
break
|
|
32
|
+
}
|
|
33
|
+
if (queries[i].includes('node')) {
|
|
34
|
+
type = 'node'
|
|
35
|
+
break
|
|
36
|
+
}
|
|
37
|
+
type = 'module'
|
|
38
|
+
}
|
|
39
|
+
return type
|
|
40
|
+
}
|
|
41
|
+
}
|