diff --git a/public/locales/en.json b/public/locales/en.json index 9142a1f4..f6fd2513 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -351,7 +351,7 @@ "Graphs": { "colorsProvider": "Provider", "colorsProviderConfig": "Provider Config", - "colorizedTitle": "Group by: ", + "colorBy": "Color by", "colorsFlux": "Flux", "loadingError": "Error loading graph data", "loadingGraph": "Loading graph data...", diff --git a/src/components/Graphs/Graph.module.css b/src/components/Graphs/Graph.module.css index 15887d86..7da8ba0e 100644 --- a/src/components/Graphs/Graph.module.css +++ b/src/components/Graphs/Graph.module.css @@ -1,7 +1,7 @@ .graphContainer { display: flex; height: 600px; - overflow: hidden; + padding: 2px; font-family: var(--sapFontFamily); } @@ -9,22 +9,13 @@ flex: 1; display: flex; flex-direction: column; + background-color: var(--sapTile_Background, #fff); } -.graphHeader { - padding: 0.5rem; +.panelContent { display: flex; + align-items: flex-start; gap: 1rem; - align-items: center; - color: var(--sapTextColor, #222); - font-size: var(--sapFontSize); -} - -.graphToolbar { - padding: 0.5rem; - display: flex; - gap: 1rem; - align-items: center; } .message { @@ -40,20 +31,6 @@ color: #c00; } -.colorizedTitle { - font-weight: 500; - color: var(--sapTextColor, #222); -} - -/* Remove the default fieldset frame when used for grouping only */ -.fieldsetReset { - border: 0; - margin: 0; - padding: 0; - min-inline-size: 0; -} - -/* React Flow Controls dark mode */ :global([data-theme='dark'] .react-flow__controls) { background-color: rgba(28, 28, 28, 0.9); border: 1px solid rgba(255, 255, 255, 0.15); diff --git a/src/components/Graphs/Graph.tsx b/src/components/Graphs/Graph.tsx index 3d215da4..57add1eb 100644 --- a/src/components/Graphs/Graph.tsx +++ b/src/components/Graphs/Graph.tsx @@ -1,7 +1,7 @@ import React, { useState, useCallback, useMemo } from 'react'; import { ReactFlow, Background, Controls, MarkerType, Node, Panel } from '@xyflow/react'; + import type { NodeProps } from '@xyflow/react'; -import { RadioButton, FlexBox, FlexBoxAlignItems } from '@ui5/webcomponents-react'; import styles from './Graph.module.css'; import '@xyflow/react/dist/style.css'; import { NodeData, ColorBy } from './types'; @@ -32,7 +32,7 @@ const Graph: React.FC = () => { const { t } = useTranslation(); const { openInAside } = useSplitter(); const { isDarkTheme } = useTheme(); - const [colorBy, setColorBy] = useState('provider'); + const [colorBy, setColorBy] = useState('source'); const handleYamlClick = useCallback( (item: ManagedResourceItem) => { @@ -92,35 +92,8 @@ const Graph: React.FC = () => { > - - -
-
- {t('Graphs.colorizedTitle')} - setColorBy('provider')} - /> - setColorBy('source')} - /> - setColorBy('flux')} - /> -
-
-
-
- - + + diff --git a/src/components/Graphs/Legend.module.css b/src/components/Graphs/Legend.module.css index bcc02f5b..a4236e15 100644 --- a/src/components/Graphs/Legend.module.css +++ b/src/components/Graphs/Legend.module.css @@ -1,22 +1,36 @@ +.legendWrapper { + display: flex; + align-items: flex-start; + gap: 1rem; +} + .legendContainer { padding: 1rem; - min-width: 220px; - max-width: 300px; - max-height: 280px; + min-width: 150px; + max-width: 250px; + max-height: 120px; border: 1px solid var(--sapList_BorderColor, #ccc); border-radius: 8px; background-color: var(--sapTile_Background, #fff); - margin: 1rem; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); - overflow: auto; - align-self: flex-start; + overflow-y: auto; color: var(--sapTextColor, #222); font-size: var(--sapFontSize); } -.legendTitle { - margin-bottom: 10px; - color: var(--sapTitleColor, var(--sapTextColor, #222)); +.colorFilterContainer { + padding: 1rem; + border: 1px solid var(--sapList_BorderColor, #ccc); + border-radius: 8px; + background-color: var(--sapTile_Background, #fff); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + color: var(--sapTextColor, #222); + font-size: var(--sapFontSize); + display: flex; + justify-content: center; + align-items: center; + height: 16px; + width: 16px; } .legendRow { @@ -30,9 +44,29 @@ } .legendColorBox { - width: 16px; - height: 16px; + width: 14px; + height: 14px; margin-right: 8px; border-radius: 3px; border: 1px solid var(--sapList_BorderColor, #999); +} + +.popoverContent { + padding: 0.5rem; + min-width: 150px; +} + +.popoverHeader { + margin: 0 0 0.5rem 0; + padding: 0 0 0.5rem 0; + border-bottom: 1px solid var(--sapList_BorderColor, #e0e0e0); + font-size: var(--sapFontSize, 14px); + font-weight: 600; + color: var(--sapTextColor, #222); +} + +.popoverButtonContainer { + display: flex; + flex-direction: column; + gap: 0.25rem; } \ No newline at end of file diff --git a/src/components/Graphs/Legend.tsx b/src/components/Graphs/Legend.tsx index 8c4f5cc1..d4393521 100644 --- a/src/components/Graphs/Legend.tsx +++ b/src/components/Graphs/Legend.tsx @@ -1,5 +1,9 @@ -import React from 'react'; +import React, { useId, useState } from 'react'; +import { Button, Popover } from '@ui5/webcomponents-react'; +import { useTranslation } from 'react-i18next'; +import { ColorBy } from './types'; import styles from './Legend.module.css'; + export interface LegendItem { name: string; color: string; @@ -7,17 +11,73 @@ export interface LegendItem { interface LegendProps { legendItems: LegendItem[]; + colorBy: ColorBy; + onColorByChange: (colorBy: ColorBy) => void; } -export const Legend: React.FC = ({ legendItems }) => { +export const Legend: React.FC = ({ legendItems, colorBy, onColorByChange }) => { + const { t } = useTranslation(); + const [colorPopoverOpen, setColorPopoverOpen] = useState(false); + const colorButtonId = useId(); + return ( -
- {legendItems.map(({ name, color }) => ( -
-
- {name} -
- ))} +
+
+ {legendItems.map(({ name, color }) => ( +
+
+ {name} +
+ ))} +
+
+ setColorPopoverOpen(false)} + > +
+

{t('Graphs.colorBy')}

+
+ + + +
+
+
+
); }; diff --git a/src/components/Graphs/useGraph.ts b/src/components/Graphs/useGraph.ts index d8c53531..87fe15cf 100644 --- a/src/components/Graphs/useGraph.ts +++ b/src/components/Graphs/useGraph.ts @@ -2,7 +2,7 @@ import { useMemo, useEffect, useState } from 'react'; import { useApiResource, useProvidersConfigResource } from '../../lib/api/useApiResource'; import { ManagedResourcesRequest } from '../../lib/api/types/crossplane/listManagedResources'; import { resourcesInterval } from '../../lib/shared/constants'; -import { Node, Edge, Position, MarkerType } from '@xyflow/react'; +import { Node, Edge, Position } from '@xyflow/react'; import dagre from 'dagre'; import { NodeData, ColorBy } from './types'; import { buildTreeData, generateColorMap } from './graphUtils'; @@ -24,14 +24,18 @@ function buildGraph( treeData.forEach((n) => { const colorKey: string = colorBy === 'source' ? n.providerType : colorBy === 'flux' ? (n.fluxName ?? 'default') : n.providerConfigName; + const borderColor = colorMap[colorKey] || '#ccc'; + //some opacity for background + const backgroundColor = `${borderColor}08`; + const node: Node = { id: n.id, type: 'custom', data: { ...n }, style: { - border: `2px solid ${colorMap[colorKey] || '#ccc'}`, + border: `2px solid ${borderColor}`, borderRadius: 8, - backgroundColor: 'var(--sapTile_Background, #fff)', + backgroundColor, width: nodeWidth, height: nodeHeight, }, @@ -53,7 +57,7 @@ function buildGraph( id: `e-${n.parentId}-${n.id}`, source: n.parentId, target: n.id, - markerEnd: { type: MarkerType.ArrowClosed }, + style: { strokeWidth: 2, stroke: '#888' }, }); } n.extraRefs?.forEach((refId) => { @@ -63,7 +67,7 @@ function buildGraph( id: `e-${refId}-${n.id}`, source: refId, target: n.id, - markerEnd: { type: MarkerType.ArrowClosed }, + style: { strokeWidth: 2, stroke: '#888' }, }); } });