From 55b9b3f8e6fb69c328ef3e7b6dbc1de92b0d3b77 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Mon, 1 Jul 2024 13:30:33 +0900 Subject: [PATCH 1/3] Convert to tabbar --- frontend/components/ui/index.ts | 2 + frontend/pages/Modules/Show.tsx | 253 ++++-------------- .../CircularDependenciesContent.tsx | 5 + .../CircularDependenciesContent/index.ts | 1 + .../DependenciesContent.tsx | 40 +++ .../components/DependenciesContent/index.ts | 1 + .../ReverseDependenciesContent.tsx | 106 ++++++++ .../ReverseDependenciesContent/index.ts | 0 .../SourcesContent/SourcesContent.tsx | 69 +++++ .../components/SourcesContent/index.ts | 1 + 10 files changed, 281 insertions(+), 197 deletions(-) create mode 100644 frontend/pages/Modules/components/CircularDependenciesContent/CircularDependenciesContent.tsx create mode 100644 frontend/pages/Modules/components/CircularDependenciesContent/index.ts create mode 100644 frontend/pages/Modules/components/DependenciesContent/DependenciesContent.tsx create mode 100644 frontend/pages/Modules/components/DependenciesContent/index.ts create mode 100644 frontend/pages/Modules/components/ReverseDependenciesContent/ReverseDependenciesContent.tsx create mode 100644 frontend/pages/Modules/components/ReverseDependenciesContent/index.ts create mode 100644 frontend/pages/Modules/components/SourcesContent/SourcesContent.tsx create mode 100644 frontend/pages/Modules/components/SourcesContent/index.ts diff --git a/frontend/components/ui/index.ts b/frontend/components/ui/index.ts index 32e69e2..b0ee113 100644 --- a/frontend/components/ui/index.ts +++ b/frontend/components/ui/index.ts @@ -30,6 +30,8 @@ export { Sidebar, SingleComboBox, Stack, + TabBar, + TabItem, Table, TableReel, Td, diff --git a/frontend/pages/Modules/Show.tsx b/frontend/pages/Modules/Show.tsx index 0b3c161..6a75bf5 100644 --- a/frontend/pages/Modules/Show.tsx +++ b/frontend/pages/Modules/Show.tsx @@ -1,17 +1,48 @@ -import React from 'react' +import React, { useMemo } from 'react' import { useParams } from 'react-router-dom' import styled from 'styled-components' import { Link } from '@/components/Link' import { Loading } from '@/components/Loading' -import { Cluster, EmptyTableBody, Heading, Section, Stack, Table, Td, Text, Th } from '@/components/ui' +import { Cluster, Heading, Section, Stack, TabBar, TabItem } from '@/components/ui' import { path } from '@/constants/path' import { spacing } from '@/constants/theme' import { useModule } from '@/repositories/moduleRepository' +import { SourcesContent } from './components/SourcesContent/SourcesContent' +import { DependenciesContent } from './components/DependenciesContent' +import { ReverseDependenciesContent } from './components/ReverseDependenciesContent/ReverseDependenciesContent' +import { CircularDependenciesContent } from './components/CircularDependenciesContent' export const Show: React.FC = () => { const pathModule = useParams()['*'] ?? '' const { data, isLoading } = useModule(pathModule) + const [activeTab, setActiveTab] = React.useState<'sources' | 'dependencies' | 'reverseDependencies' | 'circularDependencies'>( + 'sources', + ) + + const content = useMemo(() => { + if (isLoading || data === undefined) { + return + } + + switch (activeTab) { + case 'sources': { + return + } + case 'dependencies': { + return + } + case 'reverseDependencies': { + return + } + case 'circularDependencies': { + return + } + default: { + throw new Error(`Invalid tab: ${activeTab}`) + } + } + }, [data, isLoading, activeTab]) return ( @@ -26,202 +57,30 @@ export const Show: React.FC = () => {
-
- - Links - Graph - -
- - {data && !isLoading ? ( - <> -
- - Module Dependencies ({data.moduleDependencies.length}) -
- - - - - - - {data.sources.length === 0 ? ( - - No module dependencies - - ) : ( - - {data.moduleDependencies.map((module) => ( - - - - ))} - - )} -
Module
- - {module} - -
-
-
-
- -
- - Module Reverse Dependencies ({data.moduleReverseDependencies.length}) -
- - - - - - - {data.sources.length === 0 ? ( - - No module reverse dependencies - - ) : ( - - {data.moduleReverseDependencies.map((module) => ( - - - - ))} - - )} -
Module
- - {module} - -
-
-
-
- -
- - Sources ({data.sources.length}) -
- - - - - - - - - - - {data.sources.length === 0 ? ( - - No sources - - ) : ( - - {data.sources.map((source) => - source.dependencies.map((dependency) => - dependency.methodIds.map((methodId) => ( - - - - - - - - )), - ), - )} - - )} -
SourceDependency ModuleDependencyMethod IdPath
- - {source.sourceName} - - - {dependency.module && ( - - {dependency.module} - - )} - - - {dependency.sourceName} - - {`${methodId.context === 'class' ? '.' : '#'}${methodId.name}`} - {methodId.paths.map((methodIdPath) => ( -
- {methodIdPath} -
- ))} -
-
-
-
+ + setActiveTab('sources')} selected={activeTab === 'sources'}> + Sources{data ? ` (${data.sources.length})` : ''} + + setActiveTab('dependencies')} selected={activeTab === 'dependencies'}> + Dependencies{data ? ` (${data.moduleDependencies.length})` : ''} + + setActiveTab('reverseDependencies')} + selected={activeTab === 'reverseDependencies'} + > + Reverse Dependencies{data ? ` (${data.sourceReverseDependencies.length})` : ''} + + setActiveTab('circularDependencies')} + selected={activeTab === 'circularDependencies'} + > + Circular Dependencies + + -
- - Source Reverse Dependencies ({data.sourceReverseDependencies.length}) -
- - - - - - - - - - - {data.sourceReverseDependencies.length === 0 ? ( - - No sources - - ) : ( - - {data.sourceReverseDependencies.map((source) => - source.dependencies.map((dependency) => - dependency.methodIds.map((methodId) => ( - - - - - - - - )), - ), - )} - - )} -
SourceDependency ModuleDependencyMethod IdPath
- - {source.sourceName} - - - {dependency.module && ( - - {dependency.module} - - )} - - - {dependency.sourceName} - - {`${methodId.context === 'class' ? '.' : '#'}${methodId.name}`} - {methodId.paths.map((methodIdPath) => ( -
- {methodIdPath} -
- ))} -
-
-
-
- - ) : ( - - )} + {content}
diff --git a/frontend/pages/Modules/components/CircularDependenciesContent/CircularDependenciesContent.tsx b/frontend/pages/Modules/components/CircularDependenciesContent/CircularDependenciesContent.tsx new file mode 100644 index 0000000..44e0604 --- /dev/null +++ b/frontend/pages/Modules/components/CircularDependenciesContent/CircularDependenciesContent.tsx @@ -0,0 +1,5 @@ +import { FC } from 'react' + +export const CircularDependenciesContent: FC<{}> = () => { + return
+} diff --git a/frontend/pages/Modules/components/CircularDependenciesContent/index.ts b/frontend/pages/Modules/components/CircularDependenciesContent/index.ts new file mode 100644 index 0000000..cbf07eb --- /dev/null +++ b/frontend/pages/Modules/components/CircularDependenciesContent/index.ts @@ -0,0 +1 @@ +export { CircularDependenciesContent } from './CircularDependenciesContent' diff --git a/frontend/pages/Modules/components/DependenciesContent/DependenciesContent.tsx b/frontend/pages/Modules/components/DependenciesContent/DependenciesContent.tsx new file mode 100644 index 0000000..6244844 --- /dev/null +++ b/frontend/pages/Modules/components/DependenciesContent/DependenciesContent.tsx @@ -0,0 +1,40 @@ +import { Link } from '@/components/Link' +import { EmptyTableBody, Section, Stack, Text, Table, Th, Td } from '@/components/ui' +import { path } from '@/constants/path' +import { SpecificModule } from '@/models/module' +import { FC } from 'react' + +export const DependenciesContent: FC<{ moduleDependencies: SpecificModule['moduleDependencies'] }> = ({ moduleDependencies }) => { + return ( +
+ +
+ + + + + + + {moduleDependencies.length === 0 ? ( + + No module dependencies + + ) : ( + + {moduleDependencies.map((module) => ( + + + + ))} + + )} +
Module
+ + {module} + +
+
+
+
+ ) +} diff --git a/frontend/pages/Modules/components/DependenciesContent/index.ts b/frontend/pages/Modules/components/DependenciesContent/index.ts new file mode 100644 index 0000000..918200a --- /dev/null +++ b/frontend/pages/Modules/components/DependenciesContent/index.ts @@ -0,0 +1 @@ +export { DependenciesContent } from './DependenciesContent' diff --git a/frontend/pages/Modules/components/ReverseDependenciesContent/ReverseDependenciesContent.tsx b/frontend/pages/Modules/components/ReverseDependenciesContent/ReverseDependenciesContent.tsx new file mode 100644 index 0000000..634bcd5 --- /dev/null +++ b/frontend/pages/Modules/components/ReverseDependenciesContent/ReverseDependenciesContent.tsx @@ -0,0 +1,106 @@ +import { Link } from '@/components/Link' +import { EmptyTableBody, Heading, Section, Stack, Text, Table, Th, Td } from '@/components/ui' +import { path } from '@/constants/path' +import { SpecificModule } from '@/models/module' +import { FC } from 'react' + +export const ReverseDependenciesContent: FC<{ + modules: SpecificModule['moduleReverseDependencies'] + sources: SpecificModule['sourceReverseDependencies'] +}> = ({ modules, sources }) => { + return ( + +
+ + Modules ({modules.length}) +
+ + + + + + + {modules.length === 0 ? ( + + No module reverse dependencies + + ) : ( + + {modules.map((module) => ( + + + + ))} + + )} +
Module
+ + {module} + +
+
+
+
+ +
+ + Sources ({sources.length}) +
+ + + + + + + + + + + {sources.length === 0 ? ( + + No sources + + ) : ( + + {sources.map((source) => + source.dependencies.map((dependency) => + dependency.methodIds.map((methodId) => ( + + + + + + + + )), + ), + )} + + )} +
SourceDependency ModuleDependencyMethod IdPath
+ + {source.sourceName} + + + {dependency.module && ( + + {dependency.module} + + )} + + + {dependency.sourceName} + + {`${methodId.context === 'class' ? '.' : '#'}${methodId.name}`} + {methodId.paths.map((methodIdPath) => ( +
+ {methodIdPath} +
+ ))} +
+
+
+
+
+ ) +} diff --git a/frontend/pages/Modules/components/ReverseDependenciesContent/index.ts b/frontend/pages/Modules/components/ReverseDependenciesContent/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/frontend/pages/Modules/components/SourcesContent/SourcesContent.tsx b/frontend/pages/Modules/components/SourcesContent/SourcesContent.tsx new file mode 100644 index 0000000..6e6da65 --- /dev/null +++ b/frontend/pages/Modules/components/SourcesContent/SourcesContent.tsx @@ -0,0 +1,69 @@ +import { Link } from '@/components/Link' +import { EmptyTableBody, Heading, Section, Stack, Table, Th, Text, Td } from '@/components/ui' +import { path } from '@/constants/path' +import { SpecificModule } from '@/models/module' +import { FC } from 'react' + +export const SourcesContent: FC<{ sources: SpecificModule['sources'] }> = ({ sources }) => { + return ( +
+ + Sources ({sources.length}) +
+ + + + + + + + + + + {sources.length === 0 ? ( + + No sources + + ) : ( + + {sources.map((source) => + source.dependencies.map((dependency) => + dependency.methodIds.map((methodId) => ( + + + + + + + + )), + ), + )} + + )} +
SourceDependency ModuleDependencyMethod IdPath
+ + {source.sourceName} + + + {dependency.module && ( + + {dependency.module} + + )} + + + {dependency.sourceName} + + {`${methodId.context === 'class' ? '.' : '#'}${methodId.name}`} + {methodId.paths.map((methodIdPath) => ( +
+ {methodIdPath} +
+ ))} +
+
+
+
+ ) +} diff --git a/frontend/pages/Modules/components/SourcesContent/index.ts b/frontend/pages/Modules/components/SourcesContent/index.ts new file mode 100644 index 0000000..732fcd0 --- /dev/null +++ b/frontend/pages/Modules/components/SourcesContent/index.ts @@ -0,0 +1 @@ +export { SourcesContent } from './SourcesContent' From 621c28ee0f3dd79dcbf3e39ba1fbadb8ac1be108 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Mon, 1 Jul 2024 16:31:10 +0900 Subject: [PATCH 2/3] Add sources of module --- frontend/hooks/useSearchParams.ts | 25 ++ frontend/models/module.ts | 24 +- frontend/models/source.ts | 7 +- frontend/pages/Modules/Show.tsx | 49 ++-- .../DependenciesContent.tsx | 5 +- .../ReverseDependenciesContent.tsx | 9 +- .../SourcesContent/SourcesContent.tsx | 232 +++++++++++++----- .../components/StickyThead/StickyThead.tsx | 7 + .../Modules/components/StickyThead/index.ts | 1 + frontend/pages/Sources/List.tsx | 5 +- frontend/utils/sort.ts | 13 + 11 files changed, 274 insertions(+), 103 deletions(-) create mode 100644 frontend/hooks/useSearchParams.ts create mode 100644 frontend/pages/Modules/components/StickyThead/StickyThead.tsx create mode 100644 frontend/pages/Modules/components/StickyThead/index.ts create mode 100644 frontend/utils/sort.ts diff --git a/frontend/hooks/useSearchParams.ts b/frontend/hooks/useSearchParams.ts new file mode 100644 index 0000000..5e9f799 --- /dev/null +++ b/frontend/hooks/useSearchParams.ts @@ -0,0 +1,25 @@ +import { parse, stringify } from '@/utils/queryString' +import { useEffect, useState } from 'react' +import { useSearchParams } from 'react-router-dom' + +type DeserializeSearchParams = { [urlParam in keyof T]: (v: T[urlParam] | null) => T[urlParam] } + +const getInitialState = >( + searchParams: Record, + deserializeSearchParams: DeserializeSearchParams, +): T => { + return Object.entries(deserializeSearchParams).reduce((acc, [key, deserialize]) => { + return { ...acc, [key]: deserialize(searchParams[key]) } + }, {} as T) +} + +export const useSearchParamsState = >(deserializeSearchParams: DeserializeSearchParams) => { + const [searchParams, setSearchParams] = useSearchParams() + const [state, setState] = useState(() => getInitialState(parse(searchParams.toString()), deserializeSearchParams)) + + useEffect(() => { + setSearchParams(new URLSearchParams(stringify(state))) + }, [state, setSearchParams]) + + return [state, setState] as const +} diff --git a/frontend/models/module.ts b/frontend/models/module.ts index 538ff89..73aabad 100644 --- a/frontend/models/module.ts +++ b/frontend/models/module.ts @@ -2,20 +2,24 @@ import { MethodId } from './methodId' export type Module = string +export type SpecificModuleDependency = { + sourceName: string + module: Module | null + methodIds: MethodId[] +} + +export type SpecificModuleSource = { + sourceName: string + module: Module + memo: string + dependencies: SpecificModuleDependency[] +} + export type SpecificModule = { module: Module moduleDependencies: Module[] moduleReverseDependencies: Module[] - sources: Array<{ - sourceName: string - module: Module - memo: string - dependencies: Array<{ - sourceName: string - module: Module | null - methodIds: MethodId[] - }> - }> + sources: SpecificModuleSource[] sourceReverseDependencies: Array<{ sourceName: string module: Module | null diff --git a/frontend/models/source.ts b/frontend/models/source.ts index 16e516f..89cd59c 100644 --- a/frontend/models/source.ts +++ b/frontend/models/source.ts @@ -1,3 +1,4 @@ +import { ascString } from '@/utils/sort' import { MethodId } from './methodId' import { Module } from './module' @@ -40,12 +41,6 @@ export const sortSources = (sources: Source[], key: 'sourceName' | 'module', sor let sorted = [...sources] - const ascString = (a: string, b: string) => { - if (a > b) return 1 - if (a < b) return -1 - return 0 - } - switch (key) { case 'sourceName': { sorted = sorted.sort((a, b) => ascString(a.sourceName, b.sourceName)) diff --git a/frontend/pages/Modules/Show.tsx b/frontend/pages/Modules/Show.tsx index 6a75bf5..5fd6cb0 100644 --- a/frontend/pages/Modules/Show.tsx +++ b/frontend/pages/Modules/Show.tsx @@ -6,26 +6,30 @@ import { Link } from '@/components/Link' import { Loading } from '@/components/Loading' import { Cluster, Heading, Section, Stack, TabBar, TabItem } from '@/components/ui' import { path } from '@/constants/path' -import { spacing } from '@/constants/theme' +import { color, spacing } from '@/constants/theme' import { useModule } from '@/repositories/moduleRepository' import { SourcesContent } from './components/SourcesContent/SourcesContent' import { DependenciesContent } from './components/DependenciesContent' import { ReverseDependenciesContent } from './components/ReverseDependenciesContent/ReverseDependenciesContent' import { CircularDependenciesContent } from './components/CircularDependenciesContent' +import { useSearchParamsState } from '@/hooks/useSearchParams' + +type ValidTab = 'sources' | 'dependencies' | 'reverseDependencies' | 'circularDependencies' +const validTabs: ValidTab[] = ['sources', 'dependencies', 'reverseDependencies', 'circularDependencies'] as const export const Show: React.FC = () => { const pathModule = useParams()['*'] ?? '' const { data, isLoading } = useModule(pathModule) - const [activeTab, setActiveTab] = React.useState<'sources' | 'dependencies' | 'reverseDependencies' | 'circularDependencies'>( - 'sources', - ) + const [params, setParams] = useSearchParamsState<{ tab: ValidTab }>({ + tab: (val: any) => (validTabs.includes(String(val) as ValidTab) ? (String(val) as ValidTab) : 'sources'), + }) const content = useMemo(() => { if (isLoading || data === undefined) { return } - switch (activeTab) { + switch (params.tab) { case 'sources': { return } @@ -39,10 +43,10 @@ export const Show: React.FC = () => { return } default: { - throw new Error(`Invalid tab: ${activeTab}`) + throw new Error(`Invalid tab: ${params.tab}`) } } - }, [data, isLoading, activeTab]) + }, [data, isLoading, params.tab]) return ( @@ -57,28 +61,36 @@ export const Show: React.FC = () => {
- - setActiveTab('sources')} selected={activeTab === 'sources'}> + + setParams((prev) => ({ ...prev, tab: 'sources' }))} + selected={params.tab === 'sources'} + > Sources{data ? ` (${data.sources.length})` : ''} - setActiveTab('dependencies')} selected={activeTab === 'dependencies'}> + setParams((prev) => ({ ...prev, tab: 'dependencies' }))} + selected={params.tab === 'dependencies'} + > Dependencies{data ? ` (${data.moduleDependencies.length})` : ''} setActiveTab('reverseDependencies')} - selected={activeTab === 'reverseDependencies'} + onClick={() => setParams((prev) => ({ ...prev, tab: 'reverseDependencies' }))} + selected={params.tab === 'reverseDependencies'} > Reverse Dependencies{data ? ` (${data.sourceReverseDependencies.length})` : ''} setActiveTab('circularDependencies')} - selected={activeTab === 'circularDependencies'} + onClick={() => setParams((prev) => ({ ...prev, tab: 'circularDependencies' }))} + selected={params.tab === 'circularDependencies'} > Circular Dependencies - + {content} @@ -88,6 +100,13 @@ export const Show: React.FC = () => { ) } +const StickyTabBar = styled(TabBar)` + position: sticky; + top: 0; + z-index: 1; + background: ${color.BACKGROUND}; +` + const StyledSection = styled(Section)` padding: ${spacing.XS}; ` diff --git a/frontend/pages/Modules/components/DependenciesContent/DependenciesContent.tsx b/frontend/pages/Modules/components/DependenciesContent/DependenciesContent.tsx index 6244844..e1b6482 100644 --- a/frontend/pages/Modules/components/DependenciesContent/DependenciesContent.tsx +++ b/frontend/pages/Modules/components/DependenciesContent/DependenciesContent.tsx @@ -3,6 +3,7 @@ import { EmptyTableBody, Section, Stack, Text, Table, Th, Td } from '@/component import { path } from '@/constants/path' import { SpecificModule } from '@/models/module' import { FC } from 'react' +import { StickyThead } from '../StickyThead' export const DependenciesContent: FC<{ moduleDependencies: SpecificModule['moduleDependencies'] }> = ({ moduleDependencies }) => { return ( @@ -10,11 +11,11 @@ export const DependenciesContent: FC<{ moduleDependencies: SpecificModule['modul
- + - + {moduleDependencies.length === 0 ? ( No module dependencies diff --git a/frontend/pages/Modules/components/ReverseDependenciesContent/ReverseDependenciesContent.tsx b/frontend/pages/Modules/components/ReverseDependenciesContent/ReverseDependenciesContent.tsx index 634bcd5..d20de65 100644 --- a/frontend/pages/Modules/components/ReverseDependenciesContent/ReverseDependenciesContent.tsx +++ b/frontend/pages/Modules/components/ReverseDependenciesContent/ReverseDependenciesContent.tsx @@ -3,6 +3,7 @@ import { EmptyTableBody, Heading, Section, Stack, Text, Table, Th, Td } from '@/ import { path } from '@/constants/path' import { SpecificModule } from '@/models/module' import { FC } from 'react' +import { StickyThead } from '../StickyThead' export const ReverseDependenciesContent: FC<{ modules: SpecificModule['moduleReverseDependencies'] @@ -15,11 +16,11 @@ export const ReverseDependenciesContent: FC<{ Modules ({modules.length})
Module
- + - + {modules.length === 0 ? ( No module reverse dependencies @@ -47,7 +48,7 @@ export const ReverseDependenciesContent: FC<{ Sources ({sources.length})
Module
- + @@ -55,7 +56,7 @@ export const ReverseDependenciesContent: FC<{ - + {sources.length === 0 ? ( No sources diff --git a/frontend/pages/Modules/components/SourcesContent/SourcesContent.tsx b/frontend/pages/Modules/components/SourcesContent/SourcesContent.tsx index 6e6da65..d3d28da 100644 --- a/frontend/pages/Modules/components/SourcesContent/SourcesContent.tsx +++ b/frontend/pages/Modules/components/SourcesContent/SourcesContent.tsx @@ -1,69 +1,177 @@ import { Link } from '@/components/Link' -import { EmptyTableBody, Heading, Section, Stack, Table, Th, Text, Td } from '@/components/ui' +import { EmptyTableBody, Table, Th, Text, Td, Button } from '@/components/ui' import { path } from '@/constants/path' -import { SpecificModule } from '@/models/module' -import { FC } from 'react' +import { Module, SpecificModule, SpecificModuleSource } from '@/models/module' +import { FC, useCallback, useMemo, useState } from 'react' +import { StickyThead } from '../StickyThead' +import { SortTypes, ascNumber, ascString, sortTypes } from '@/utils/sort' + +const SourceTr: FC<{ source: SpecificModuleSource }> = ({ source }) => { + const [expanded, setExpanded] = useState(false) + + const modules = useMemo(() => { + const modules = new Set() + + source.dependencies.forEach((dependency) => { + if (dependency.module) { + modules.add(dependency.module) + } + }) + + return [...modules].sort() + }, [source]) + + const dependencies = useMemo(() => { + return source.dependencies.toSorted( + (a, b) => ascString(String(a.module), String(b.module)) || ascString(String(a.sourceName), String(b.sourceName)), + ) + }, [source]) -export const SourcesContent: FC<{ sources: SpecificModule['sources'] }> = ({ sources }) => { return ( -
- - Sources ({sources.length}) -
-
Source Dependency ModuleMethod Id Path
- - - - - - - - - - {sources.length === 0 ? ( - - No sources - - ) : ( - - {sources.map((source) => - source.dependencies.map((dependency) => - dependency.methodIds.map((methodId) => ( - - - - - - - - )), - ), + <> + + + + + + + + + + {expanded && + dependencies.map((dependency) => + dependency.methodIds.map((methodId, index) => ( + + + + - )} -
SourceDependency ModuleDependencyMethod IdPath
- - {source.sourceName} - - - {dependency.module && ( - - {dependency.module} - - )} - - - {dependency.sourceName} - - {`${methodId.context === 'class' ? '.' : '#'}${methodId.name}`} - {methodId.paths.map((methodIdPath) => ( -
- {methodIdPath} -
- ))} -
+ {source.dependencies.length > 0 && ( + + )} + + + {source.sourceName} + + + {modules.map((module) => ( + + {module} + + ))} + {source.dependencies.length}
+ {index === 0 && dependency.module && ( + + {dependency.module} + )} -
-
-
-
+ + + {index === 0 && ( + + {dependency.sourceName} + + )} + + {`${methodId.context === 'class' ? '.' : '#'}${methodId.name}`} + + {methodId.paths.map((methodIdPath) => ( +
+ {methodIdPath} +
+ ))} + + + )), + )} + + ) +} + +const sortSources = ( + sources: SpecificModuleSource[], + key: 'sourceName' | 'dependency', + sort: 'none' | 'asc' | 'desc', +): SpecificModuleSource[] => { + if (sort === 'none') { + return sources + } + + let sorted: SpecificModuleSource[] + + switch (key) { + case 'sourceName': { + sorted = sources.toSorted((a, b) => ascString(a.sourceName, b.sourceName)) + break + } + case 'dependency': { + sorted = sources.toSorted((a, b) => ascNumber(a.dependencies.length, b.dependencies.length)) + } + } + + if (sort === 'desc') { + sorted = sorted.reverse() + } + + return sorted +} + +type SortType = { + key: 'sourceName' | 'dependency' + sort: SortTypes +} + +export const SourcesContent: FC<{ sources: SpecificModule['sources'] }> = ({ sources }) => { + const [sort, setSort] = useState({ key: 'sourceName', sort: 'none' }) + + const sortedSources = useMemo(() => { + return sortSources(sources, sort.key, sort.sort) + }, [sort, sources]) + + const setNextSort = useCallback( + (key: SortType['key']) => { + setSort((prev) => { + if (prev.key === key) { + return { + key, + sort: sortTypes[(sortTypes.indexOf(prev.sort) + 1) % sortTypes.length], + } + } else { + return { key, sort: 'asc' } + } + }) + }, + [setSort], + ) + + return ( + + + + + + + + + + + + {sortedSources.length === 0 ? ( + + No sources + + ) : ( + + {sortedSources.map((source) => ( + + ))} + + )} +
setNextSort('sourceName')}> + Source + Dependency Module setNextSort('dependency')}> + Dependency + Method IdPath
) } diff --git a/frontend/pages/Modules/components/StickyThead/StickyThead.tsx b/frontend/pages/Modules/components/StickyThead/StickyThead.tsx new file mode 100644 index 0000000..cbd914a --- /dev/null +++ b/frontend/pages/Modules/components/StickyThead/StickyThead.tsx @@ -0,0 +1,7 @@ +import styled from 'styled-components' + +export const StickyThead = styled.thead` + &&& { + top: 51px; + } +` diff --git a/frontend/pages/Modules/components/StickyThead/index.ts b/frontend/pages/Modules/components/StickyThead/index.ts new file mode 100644 index 0000000..314e3f9 --- /dev/null +++ b/frontend/pages/Modules/components/StickyThead/index.ts @@ -0,0 +1 @@ +export { StickyThead } from './StickyThead' diff --git a/frontend/pages/Sources/List.tsx b/frontend/pages/Sources/List.tsx index b4eebe6..575871d 100644 --- a/frontend/pages/Sources/List.tsx +++ b/frontend/pages/Sources/List.tsx @@ -27,10 +27,7 @@ import { SourceModuleComboBox } from '@/components/SourceModuleComboBox' import { UpdateSourceModuleButton } from '@/components/UpdateSourceModuleButton' import { SourceMemoInput } from '@/components/SourceMemoInput' import { createSearchParams, useNavigate } from 'react-router-dom' - -const sortTypes = ['asc', 'desc', 'none'] as const - -type SortTypes = (typeof sortTypes)[number] +import { SortTypes, sortTypes } from '@/utils/sort' type SortState = { key: 'sourceName' | 'module' diff --git a/frontend/utils/sort.ts b/frontend/utils/sort.ts new file mode 100644 index 0000000..67bf8a0 --- /dev/null +++ b/frontend/utils/sort.ts @@ -0,0 +1,13 @@ +export const ascString = (a: string, b: string) => { + if (a > b) return 1 + if (a < b) return -1 + return 0 +} + +export const ascNumber = (a: number, b: number) => { + return a - b +} + +export const sortTypes = ['asc', 'desc', 'none'] as const + +export type SortTypes = (typeof sortTypes)[number] From c36cc01761c38b8ea4ca16c17e6fb0b31739c928 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Mon, 1 Jul 2024 23:44:17 +0900 Subject: [PATCH 3/3] Add module dependencies / module reverse dependencies --- frontend/components/ui/index.ts | 1 + frontend/models/module.ts | 13 +- frontend/pages/Modules/Show.tsx | 77 ++++--- .../CircularDependenciesContent.tsx | 5 - .../CircularDependenciesContent/index.ts | 1 - .../DependenciesContent.tsx | 41 ---- .../components/DependenciesContent/index.ts | 1 - .../ModuleDependenciesContent.tsx | 78 +++++++ .../ModuleDependenciesContent/index.ts | 1 + .../ReverseDependenciesContent.tsx | 107 ---------- .../ReverseDependenciesContent/index.ts | 0 .../SourceReverseDependenciesContent.tsx | 195 ++++++++++++++++++ .../SourceReverseDependenciesContent/index.ts | 1 + .../SourcesContent/SourcesContent.tsx | 96 +++++---- 14 files changed, 387 insertions(+), 230 deletions(-) delete mode 100644 frontend/pages/Modules/components/CircularDependenciesContent/CircularDependenciesContent.tsx delete mode 100644 frontend/pages/Modules/components/CircularDependenciesContent/index.ts delete mode 100644 frontend/pages/Modules/components/DependenciesContent/DependenciesContent.tsx delete mode 100644 frontend/pages/Modules/components/DependenciesContent/index.ts create mode 100644 frontend/pages/Modules/components/ModuleDependenciesContent/ModuleDependenciesContent.tsx create mode 100644 frontend/pages/Modules/components/ModuleDependenciesContent/index.ts delete mode 100644 frontend/pages/Modules/components/ReverseDependenciesContent/ReverseDependenciesContent.tsx delete mode 100644 frontend/pages/Modules/components/ReverseDependenciesContent/index.ts create mode 100644 frontend/pages/Modules/components/SourceReverseDependenciesContent/SourceReverseDependenciesContent.tsx create mode 100644 frontend/pages/Modules/components/SourceReverseDependenciesContent/index.ts diff --git a/frontend/components/ui/index.ts b/frontend/components/ui/index.ts index b0ee113..f29a8ca 100644 --- a/frontend/components/ui/index.ts +++ b/frontend/components/ui/index.ts @@ -5,6 +5,7 @@ export { Base, Button, CheckBox, + Chip, Cluster, DefinitionList, EmptyTableBody, diff --git a/frontend/models/module.ts b/frontend/models/module.ts index 73aabad..5ef4de2 100644 --- a/frontend/models/module.ts +++ b/frontend/models/module.ts @@ -10,7 +10,7 @@ export type SpecificModuleDependency = { export type SpecificModuleSource = { sourceName: string - module: Module + module: Module | null memo: string dependencies: SpecificModuleDependency[] } @@ -20,14 +20,5 @@ export type SpecificModule = { moduleDependencies: Module[] moduleReverseDependencies: Module[] sources: SpecificModuleSource[] - sourceReverseDependencies: Array<{ - sourceName: string - module: Module | null - memo: string - dependencies: Array<{ - sourceName: string - module: Module - methodIds: MethodId[] - }> - }> + sourceReverseDependencies: SpecificModuleSource[] } diff --git a/frontend/pages/Modules/Show.tsx b/frontend/pages/Modules/Show.tsx index 5fd6cb0..c1b0481 100644 --- a/frontend/pages/Modules/Show.tsx +++ b/frontend/pages/Modules/Show.tsx @@ -4,24 +4,37 @@ import styled from 'styled-components' import { Link } from '@/components/Link' import { Loading } from '@/components/Loading' -import { Cluster, Heading, Section, Stack, TabBar, TabItem } from '@/components/ui' +import { Chip, Cluster, Heading, Section, Stack, TabBar, TabItem } from '@/components/ui' import { path } from '@/constants/path' import { color, spacing } from '@/constants/theme' import { useModule } from '@/repositories/moduleRepository' import { SourcesContent } from './components/SourcesContent/SourcesContent' -import { DependenciesContent } from './components/DependenciesContent' -import { ReverseDependenciesContent } from './components/ReverseDependenciesContent/ReverseDependenciesContent' -import { CircularDependenciesContent } from './components/CircularDependenciesContent' +import { ModuleDependenciesContent } from './components/ModuleDependenciesContent' +import { SourceReverseDependenciesContent } from './components/SourceReverseDependenciesContent' import { useSearchParamsState } from '@/hooks/useSearchParams' +import { Module } from '@/models/module' -type ValidTab = 'sources' | 'dependencies' | 'reverseDependencies' | 'circularDependencies' -const validTabs: ValidTab[] = ['sources', 'dependencies', 'reverseDependencies', 'circularDependencies'] as const +const validTabs = ['sources', 'sourceReverseDependencies', 'moduleDependencies', 'moduleReverseDependencies'] as const +type ValidTab = (typeof validTabs)[number] + +type Query = { + module: Module | null +} export const Show: React.FC = () => { const pathModule = useParams()['*'] ?? '' const { data, isLoading } = useModule(pathModule) - const [params, setParams] = useSearchParamsState<{ tab: ValidTab }>({ + const [params, setParams] = useSearchParamsState<{ tab: ValidTab; q: Query }>({ tab: (val: any) => (validTabs.includes(String(val) as ValidTab) ? (String(val) as ValidTab) : 'sources'), + q: (val: any) => { + const q: Query = { module: null } + + if (val && val.module) { + q.module = val.module + } + + return q + }, }) const content = useMemo(() => { @@ -31,22 +44,34 @@ export const Show: React.FC = () => { switch (params.tab) { case 'sources': { - return + return } - case 'dependencies': { - return + case 'moduleDependencies': { + return ( + + ) } - case 'reverseDependencies': { - return + case 'moduleReverseDependencies': { + return ( + + ) } - case 'circularDependencies': { - return + case 'sourceReverseDependencies': { + return } default: { throw new Error(`Invalid tab: ${params.tab}`) } } - }, [data, isLoading, params.tab]) + }, [data, pathModule, isLoading, params]) return ( @@ -71,24 +96,24 @@ export const Show: React.FC = () => { setParams((prev) => ({ ...prev, tab: 'dependencies' }))} - selected={params.tab === 'dependencies'} + onClick={() => setParams((prev) => ({ ...prev, tab: 'moduleDependencies' }))} + selected={params.tab === 'moduleDependencies'} > - Dependencies{data ? ` (${data.moduleDependencies.length})` : ''} + Module Dependencies{data ? ` (${data.moduleDependencies.length})` : ''} setParams((prev) => ({ ...prev, tab: 'reverseDependencies' }))} - selected={params.tab === 'reverseDependencies'} + id="tab-module-reverse-dependencies" + onClick={() => setParams((prev) => ({ ...prev, tab: 'moduleReverseDependencies' }))} + selected={params.tab === 'moduleReverseDependencies'} > - Reverse Dependencies{data ? ` (${data.sourceReverseDependencies.length})` : ''} + Module Reverse Dependencies{data ? ` (${data.moduleReverseDependencies.length})` : ''} setParams((prev) => ({ ...prev, tab: 'circularDependencies' }))} - selected={params.tab === 'circularDependencies'} + id="tab-source-reverse-dependencies" + onClick={() => setParams((prev) => ({ ...prev, tab: 'sourceReverseDependencies' }))} + selected={params.tab === 'sourceReverseDependencies'} > - Circular Dependencies + Source Reverse Dependencies{data ? ` (${data.sourceReverseDependencies.length})` : ''} diff --git a/frontend/pages/Modules/components/CircularDependenciesContent/CircularDependenciesContent.tsx b/frontend/pages/Modules/components/CircularDependenciesContent/CircularDependenciesContent.tsx deleted file mode 100644 index 44e0604..0000000 --- a/frontend/pages/Modules/components/CircularDependenciesContent/CircularDependenciesContent.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { FC } from 'react' - -export const CircularDependenciesContent: FC<{}> = () => { - return
-} diff --git a/frontend/pages/Modules/components/CircularDependenciesContent/index.ts b/frontend/pages/Modules/components/CircularDependenciesContent/index.ts deleted file mode 100644 index cbf07eb..0000000 --- a/frontend/pages/Modules/components/CircularDependenciesContent/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { CircularDependenciesContent } from './CircularDependenciesContent' diff --git a/frontend/pages/Modules/components/DependenciesContent/DependenciesContent.tsx b/frontend/pages/Modules/components/DependenciesContent/DependenciesContent.tsx deleted file mode 100644 index e1b6482..0000000 --- a/frontend/pages/Modules/components/DependenciesContent/DependenciesContent.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Link } from '@/components/Link' -import { EmptyTableBody, Section, Stack, Text, Table, Th, Td } from '@/components/ui' -import { path } from '@/constants/path' -import { SpecificModule } from '@/models/module' -import { FC } from 'react' -import { StickyThead } from '../StickyThead' - -export const DependenciesContent: FC<{ moduleDependencies: SpecificModule['moduleDependencies'] }> = ({ moduleDependencies }) => { - return ( -
- -
- - - - - - - {moduleDependencies.length === 0 ? ( - - No module dependencies - - ) : ( - - {moduleDependencies.map((module) => ( - - - - ))} - - )} -
Module
- - {module} - -
-
-
-
- ) -} diff --git a/frontend/pages/Modules/components/DependenciesContent/index.ts b/frontend/pages/Modules/components/DependenciesContent/index.ts deleted file mode 100644 index 918200a..0000000 --- a/frontend/pages/Modules/components/DependenciesContent/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { DependenciesContent } from './DependenciesContent' diff --git a/frontend/pages/Modules/components/ModuleDependenciesContent/ModuleDependenciesContent.tsx b/frontend/pages/Modules/components/ModuleDependenciesContent/ModuleDependenciesContent.tsx new file mode 100644 index 0000000..21c6cb3 --- /dev/null +++ b/frontend/pages/Modules/components/ModuleDependenciesContent/ModuleDependenciesContent.tsx @@ -0,0 +1,78 @@ +import { Link } from '@/components/Link' +import { EmptyTableBody, Section, Stack, Text, Table, Th, Td } from '@/components/ui' +import { path } from '@/constants/path' +import { Module, SpecificModule } from '@/models/module' +import { FC, useMemo } from 'react' +import { StickyThead } from '../StickyThead' +import { stringify } from '@/utils/queryString' + +type Props = { + pathModule: Module + sources: SpecificModule['sources'] + moduleDependencies: SpecificModule['moduleDependencies'] +} + +export const ModuleDependenciesContent: FC = ({ pathModule, sources, moduleDependencies }) => { + const dependenciesMap = useMemo(() => { + const map = new Map>() + + sources.forEach((source) => { + source.dependencies.forEach((dependency) => { + if (dependency.module) { + if (!map.has(dependency.module)) { + map.set(dependency.module, new Set()) + } + + const set = map.get(dependency.module)! + set.add(dependency.sourceName) + } + }) + }) + + return map + }, [sources, moduleDependencies]) + + return ( +
+ +
+ + + + + + + + {moduleDependencies.length === 0 ? ( + + No module dependencies + + ) : ( + + {moduleDependencies.map((module) => ( + + + + + ))} + + )} +
ModuleSources
+ + {module} + + + + + {dependenciesMap.get(module)?.size ?? 0} + + +
+
+
+
+ ) +} diff --git a/frontend/pages/Modules/components/ModuleDependenciesContent/index.ts b/frontend/pages/Modules/components/ModuleDependenciesContent/index.ts new file mode 100644 index 0000000..5068013 --- /dev/null +++ b/frontend/pages/Modules/components/ModuleDependenciesContent/index.ts @@ -0,0 +1 @@ +export { ModuleDependenciesContent } from './ModuleDependenciesContent' diff --git a/frontend/pages/Modules/components/ReverseDependenciesContent/ReverseDependenciesContent.tsx b/frontend/pages/Modules/components/ReverseDependenciesContent/ReverseDependenciesContent.tsx deleted file mode 100644 index d20de65..0000000 --- a/frontend/pages/Modules/components/ReverseDependenciesContent/ReverseDependenciesContent.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { Link } from '@/components/Link' -import { EmptyTableBody, Heading, Section, Stack, Text, Table, Th, Td } from '@/components/ui' -import { path } from '@/constants/path' -import { SpecificModule } from '@/models/module' -import { FC } from 'react' -import { StickyThead } from '../StickyThead' - -export const ReverseDependenciesContent: FC<{ - modules: SpecificModule['moduleReverseDependencies'] - sources: SpecificModule['sourceReverseDependencies'] -}> = ({ modules, sources }) => { - return ( - -
- - Modules ({modules.length}) -
- - - - - - - {modules.length === 0 ? ( - - No module reverse dependencies - - ) : ( - - {modules.map((module) => ( - - - - ))} - - )} -
Module
- - {module} - -
-
-
-
- -
- - Sources ({sources.length}) -
- - - - - - - - - - - {sources.length === 0 ? ( - - No sources - - ) : ( - - {sources.map((source) => - source.dependencies.map((dependency) => - dependency.methodIds.map((methodId) => ( - - - - - - - - )), - ), - )} - - )} -
SourceDependency ModuleDependencyMethod IdPath
- - {source.sourceName} - - - {dependency.module && ( - - {dependency.module} - - )} - - - {dependency.sourceName} - - {`${methodId.context === 'class' ? '.' : '#'}${methodId.name}`} - {methodId.paths.map((methodIdPath) => ( -
- {methodIdPath} -
- ))} -
-
-
-
-
- ) -} diff --git a/frontend/pages/Modules/components/ReverseDependenciesContent/index.ts b/frontend/pages/Modules/components/ReverseDependenciesContent/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/pages/Modules/components/SourceReverseDependenciesContent/SourceReverseDependenciesContent.tsx b/frontend/pages/Modules/components/SourceReverseDependenciesContent/SourceReverseDependenciesContent.tsx new file mode 100644 index 0000000..1d60d7f --- /dev/null +++ b/frontend/pages/Modules/components/SourceReverseDependenciesContent/SourceReverseDependenciesContent.tsx @@ -0,0 +1,195 @@ +import { Link } from '@/components/Link' +import { EmptyTableBody, Table, Th, Text, Td, Button, Cluster, Chip } from '@/components/ui' +import { path } from '@/constants/path' +import { Module, SpecificModule, SpecificModuleSource } from '@/models/module' +import { FC, useCallback, useMemo, useState } from 'react' +import { StickyThead } from '../StickyThead' +import { SortTypes, ascNumber, ascString, sortTypes } from '@/utils/sort' + +const SourceTr: FC<{ source: SpecificModuleSource; filteredModule: Module | null }> = ({ filteredModule, source }) => { + const [expanded, setExpanded] = useState(false) + + const modules = useMemo(() => { + const modules = new Set() + + source.dependencies.forEach((dependency) => { + if (dependency.module && (!filteredModule || dependency.module === filteredModule)) { + modules.add(dependency.module) + } + }) + + return [...modules].sort() + }, [source]) + + const dependencies = useMemo(() => { + return source.dependencies + .filter((dependency) => !filteredModule || dependency.module === filteredModule) + .toSorted((a, b) => ascString(String(a.module), String(b.module)) || ascString(String(a.sourceName), String(b.sourceName))) + }, [source, filteredModule]) + + return ( + <> + + + {dependencies.length > 0 && ( + + )} + + + + {source.sourceName} + + + + {modules.map((module) => ( + + {module} + + ))} + + {dependencies.length} + + + + + {expanded && + dependencies.map((dependency) => + dependency.methodIds.map((methodId, index) => ( + + + + + {index === 0 && dependency.module && ( + + {dependency.module} + + )} + + + {index === 0 && ( + + {dependency.sourceName} + + )} + + {`${methodId.context === 'class' ? '.' : '#'}${methodId.name}`} + + {methodId.paths.map((methodIdPath) => ( +
+ {methodIdPath} +
+ ))} + + + )), + )} + + ) +} + +const sortSources = ( + sources: SpecificModuleSource[], + key: 'sourceName' | 'dependency', + sort: 'none' | 'asc' | 'desc', +): SpecificModuleSource[] => { + if (sort === 'none') { + return sources + } + + let sorted: SpecificModuleSource[] + + switch (key) { + case 'sourceName': { + sorted = sources.toSorted((a, b) => ascString(a.sourceName, b.sourceName)) + break + } + case 'dependency': { + sorted = sources.toSorted((a, b) => ascNumber(a.dependencies.length, b.dependencies.length)) + } + } + + if (sort === 'desc') { + sorted = sorted.reverse() + } + + return sorted +} + +type SortType = { + key: 'sourceName' | 'dependency' + sort: SortTypes +} + +type Props = { + sources: SpecificModule['sourceReverseDependencies'] + filteredModule: Module | null +} + +export const SourceReverseDependenciesContent: FC = ({ filteredModule, sources }) => { + const [sort, setSort] = useState({ key: 'sourceName', sort: 'none' }) + + const sortedSources = useMemo(() => { + let sorted = sortSources(sources, sort.key, sort.sort) + + if (filteredModule) { + sorted = sorted.filter((source) => source.dependencies.some((dependency) => dependency.module === filteredModule)) + } + + return sorted + }, [sort, filteredModule, sources]) + + const setNextSort = useCallback( + (key: SortType['key']) => { + setSort((prev) => { + if (prev.key === key) { + return { + key, + sort: sortTypes[(sortTypes.indexOf(prev.sort) + 1) % sortTypes.length], + } + } else { + return { key, sort: 'asc' } + } + }) + }, + [setSort], + ) + + return ( + <> + {filteredModule && ( + + Filter: {filteredModule} + + )} + + + + + + + + + + + + {sortedSources.length === 0 ? ( + + No sources + + ) : ( + + {sortedSources.map((source) => ( + + ))} + + )} +
setNextSort('sourceName')}> + Source + Dependency Module setNextSort('dependency')}> + Dependency + Method IdPath
+ + ) +} diff --git a/frontend/pages/Modules/components/SourceReverseDependenciesContent/index.ts b/frontend/pages/Modules/components/SourceReverseDependenciesContent/index.ts new file mode 100644 index 0000000..90eadb1 --- /dev/null +++ b/frontend/pages/Modules/components/SourceReverseDependenciesContent/index.ts @@ -0,0 +1 @@ +export { SourceReverseDependenciesContent } from './SourceReverseDependenciesContent' diff --git a/frontend/pages/Modules/components/SourcesContent/SourcesContent.tsx b/frontend/pages/Modules/components/SourcesContent/SourcesContent.tsx index d3d28da..edc0dab 100644 --- a/frontend/pages/Modules/components/SourcesContent/SourcesContent.tsx +++ b/frontend/pages/Modules/components/SourcesContent/SourcesContent.tsx @@ -1,19 +1,19 @@ import { Link } from '@/components/Link' -import { EmptyTableBody, Table, Th, Text, Td, Button } from '@/components/ui' +import { EmptyTableBody, Table, Th, Text, Td, Button, Chip, Cluster } from '@/components/ui' import { path } from '@/constants/path' import { Module, SpecificModule, SpecificModuleSource } from '@/models/module' import { FC, useCallback, useMemo, useState } from 'react' import { StickyThead } from '../StickyThead' import { SortTypes, ascNumber, ascString, sortTypes } from '@/utils/sort' -const SourceTr: FC<{ source: SpecificModuleSource }> = ({ source }) => { +const SourceTr: FC<{ source: SpecificModuleSource; filteredModule: Module | null }> = ({ source, filteredModule }) => { const [expanded, setExpanded] = useState(false) const modules = useMemo(() => { const modules = new Set() source.dependencies.forEach((dependency) => { - if (dependency.module) { + if (dependency.module && (!filteredModule || dependency.module === filteredModule)) { modules.add(dependency.module) } }) @@ -22,16 +22,16 @@ const SourceTr: FC<{ source: SpecificModuleSource }> = ({ source }) => { }, [source]) const dependencies = useMemo(() => { - return source.dependencies.toSorted( - (a, b) => ascString(String(a.module), String(b.module)) || ascString(String(a.sourceName), String(b.sourceName)), - ) - }, [source]) + return source.dependencies + .filter((dependency) => !filteredModule || dependency.module === filteredModule) + .toSorted((a, b) => ascString(String(a.module), String(b.module)) || ascString(String(a.sourceName), String(b.sourceName))) + }, [source, filteredModule]) return ( <> - {source.dependencies.length > 0 && ( + {dependencies.length > 0 && ( @@ -49,7 +49,7 @@ const SourceTr: FC<{ source: SpecificModuleSource }> = ({ source }) => { ))} - {source.dependencies.length} + {dependencies.length} @@ -122,12 +122,23 @@ type SortType = { sort: SortTypes } -export const SourcesContent: FC<{ sources: SpecificModule['sources'] }> = ({ sources }) => { +type Props = { + sources: SpecificModule['sources'] + filteredModule: Module | null +} + +export const SourcesContent: FC = ({ filteredModule, sources }) => { const [sort, setSort] = useState({ key: 'sourceName', sort: 'none' }) const sortedSources = useMemo(() => { - return sortSources(sources, sort.key, sort.sort) - }, [sort, sources]) + let sorted = sortSources(sources, sort.key, sort.sort) + + if (filteredModule) { + sorted = sorted.filter((source) => source.dependencies.some((dependency) => dependency.module === filteredModule)) + } + + return sorted + }, [sort, filteredModule, sources]) const setNextSort = useCallback( (key: SortType['key']) => { @@ -146,32 +157,41 @@ export const SourcesContent: FC<{ sources: SpecificModule['sources'] }> = ({ sou ) return ( - - - - - - - - - - - - {sortedSources.length === 0 ? ( - - No sources - - ) : ( - - {sortedSources.map((source) => ( - - ))} - + <> + {filteredModule && ( + + + Filter: {filteredModule} + + )} -
setNextSort('sourceName')}> - Source - Dependency Module setNextSort('dependency')}> - Dependency - Method IdPath
+ + + + + + + + + + + + {sortedSources.length === 0 ? ( + + No sources + + ) : ( + + {sortedSources.map((source) => ( + + ))} + + )} +
setNextSort('sourceName')}> + Source + Dependency Module setNextSort('dependency')}> + Dependency + Method IdPath
+ ) }