Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental feature: Add graph for specific module #60

Merged
merged 13 commits into from
Jun 17, 2024
2 changes: 1 addition & 1 deletion frontend/components/Layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const Header: React.FC = () => {
},
{
children: 'Module List',
current: pathname === path.modules.index() || /^\/modules\//.test(pathname),
current: pathname === path.modules.index() || /^\/modules\//.test(pathname) || /^\/module_definitions\//.test(pathname),
href: path.modules.index(),
onClick: () => navigate(path.modules.index()),
},
Expand Down
6 changes: 6 additions & 0 deletions frontend/constants/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export const path = {
index: () => '/modules',
show: (moduleNames: string[]) => `/modules/${moduleNames.join('/')}`,
},
moduleDefinitions: {
show: (moduleNames: string[]) => `/module_definitions/${moduleNames.join('/')}`,
},
licenses: {
index: () => '/licenses',
},
Expand Down Expand Up @@ -44,5 +47,8 @@ export const path = {
index: () => '/api/modules.json',
show: (moduleNames: string[]) => `/api/modules/${moduleNames.join('/')}.json`,
},
moduleDefinitions: {
show: (moduleNames: string[]) => `/api/module_definitions/${moduleNames.join('/')}.json`,
},
},
}
2 changes: 2 additions & 0 deletions frontend/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Layout } from './components/Layout'
import { path } from './constants/path'
import { NotFound } from './pages/Errors'
import { Show as DefinitionList } from './pages/DefinitionList'
import { Show as ModuleDefinitionShow } from './pages/ModuleDefinitions'
import { Index as LicenseIndex } from './pages/Lincense'
import { Index as ModuleIndex, Show as ModuleShow } from './pages/Modules'
import { Index as SourceIndex, Show as SourceShow } from './pages/Sources'
Expand All @@ -23,6 +24,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
<Route path={path.modules.index()} element={<ModuleIndex />} />
<Route path={path.modules.show(['*'])} element={<ModuleShow />} />
<Route path={path.licenses.index()} element={<LicenseIndex />} />
<Route path={path.moduleDefinitions.show(['*'])} element={<ModuleDefinitionShow />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
Expand Down
6 changes: 6 additions & 0 deletions frontend/models/combinedDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { Module } from './module'
import { Source } from './source'

export type CombinedDefinitionOptions = {
compound: boolean
concentrate: boolean
onlyModule: boolean
}

type BaseDotMetadata = {
id: string
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/pages/DefinitionList/Show.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const Show: React.FC = () => {
data: combinedDefinition,
isLoading,
mutate: mutateCombinedDefinition,
} = useCombinedDefinition(selectedDefinitionIds, graphOptions.compound, graphOptions.concentrate, graphOptions.onlyModule)
} = useCombinedDefinition(selectedDefinitionIds, graphOptions)
const [recentModules, setRecentModules] = useState<Module[]>([])

return (
Expand Down
101 changes: 101 additions & 0 deletions frontend/pages/ModuleDefinitions/Show.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, { useCallback, useMemo, useState } from 'react'
import styled from 'styled-components'

import { Loading } from '@/components/Loading'
import { Aside, Section, Sidebar, Stack } from '@/components/ui'
import { color } from '@/constants/theme'

import { RecentModulesContext } from '@/context/RecentModulesContext'
import { Module } from '@/models/module'
import { useGraphOptions } from '@/hooks/useGraphOptions'
import { DefinitionGraph } from '@/components/DefinitionGraph'
import { DefinitionSources } from '@/components/DefinitionSources'
import { useParams } from 'react-router-dom'
import { useModuleDefinition } from '@/repositories/moduleDefinitionRepository'

export const Show: React.FC = () => {
const pathModules = (useParams()['*'] ?? '').split('/')

const [graphOptions, setGraphOptions] = useGraphOptions()
const {
data: combinedDefinition,
isLoading: isLoadingCombinedDefinition,
mutate: mutateCombinedDefinition,
} = useModuleDefinition(pathModules, graphOptions)
const [recentModules, setRecentModules] = useState<Module[]>([])

return (
<Wrapper>
<RecentModulesContext.Provider value={{ recentModules, setRecentModules }}>
<StyledSidebar contentsMinWidth="0px" gap={0}>
<StyledSection>
{isLoadingCombinedDefinition ? (
<CenterStack>
<Loading text="Loading..." alt="Loading" />
</CenterStack>
) : !combinedDefinition ? (
<CenterStack>
<p>No data</p>
</CenterStack>
) : (
<StyledStack>
<DefinitionGraph
combinedDefinition={combinedDefinition}
mutateCombinedDefinition={mutateCombinedDefinition}
graphOptions={graphOptions}
setGraphOptions={setGraphOptions}
/>
<StyledDefinitionSources
combinedDefinition={combinedDefinition}
mutateCombinedDefinition={mutateCombinedDefinition}
/>
</StyledStack>
)}
</StyledSection>
</StyledSidebar>
</RecentModulesContext.Provider>
</Wrapper>
)
}

const Wrapper = styled.div`
display: flex;
flex-direction: column;
height: calc(100% - 1px); /* 100% - padding-top of layout */
width: 100vw;
`

const StyledSidebar = styled(Sidebar)`
display: flex;
height: 100%;
`

const StyledAside = styled(Aside)`
box-sizing: border-box;
border-top: 1px solid ${color.BORDER};
border-right: 1px solid ${color.BORDER};
background-color: ${color.WHITE};
height: inherit;
`

const StyledSection = styled(Section)`
box-sizing: border-box;
height: inherit;
`

const CenterStack = styled(Stack)`
display: flex;
flex-direction: row;
height: inherit;
justify-content: center;
`

const StyledStack = styled(Stack)`
display: flex;
flex-direction: row;
height: inherit;
`

const StyledDefinitionSources = styled(DefinitionSources)`
flex: 1;
`
1 change: 1 addition & 0 deletions frontend/pages/ModuleDefinitions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Show'
159 changes: 84 additions & 75 deletions frontend/pages/Modules/Show.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,82 +41,91 @@ export const Show: React.FC = () => {
</Heading>

<Section>
{specificModule && !isLoading ? (
<Stack gap={1.5}>
<Section>
<Stack gap={0.5}>
<Heading type="sectionTitle">Sources</Heading>
<div style={{ overflow: 'clip' }}>
<Table fixedHead>
<thead>
<tr>
<Th>Source Name</Th>
<Th>Memo</Th>
</tr>
</thead>
{specificModule.sources.length === 0 ? (
<EmptyTableBody>
<Text>no sources</Text>
</EmptyTableBody>
) : (
<tbody>
{specificModule.sources.map((source) => (
<tr key={source.sourceName}>
<Td>
<Link to={path.sources.show(source.sourceName)}>{source.sourceName}</Link>
</Td>
<Td>
<Text>{source.memo}</Text>
</Td>
</tr>
))}
</tbody>
)}
</Table>
</div>
</Stack>
</Section>
<Stack gap={1.5}>
<Section>
<Stack gap={0.5}>
<Heading type="sectionTitle">Links</Heading>
<Link to={path.moduleDefinitions.show(pathModules)}>Graph</Link>
</Stack>
</Section>

<Section>
<Stack gap={0.5}>
<Cluster>
<Heading type="sectionTitle">Related Definitions</Heading>
<Link to={`${path.home()}?${stringify({ [KEY]: encode(idsToBitId(relatedDefinitionIds)) })}`}>
Select All
</Link>
</Cluster>
<div style={{ overflow: 'clip' }}>
<Table fixedHead>
<thead>
<tr>
<Th>Title</Th>
</tr>
</thead>
{specificModule.relatedDefinitions.length === 0 ? (
<EmptyTableBody>
<Text>no related definitions</Text>
</EmptyTableBody>
) : (
<tbody>
{specificModule.relatedDefinitions.map((relatedDefinition) => (
<tr key={relatedDefinition.id}>
<Td>
<Link to={`${path.home()}?${stringify({ [KEY]: encode(idsToBitId([relatedDefinition.id])) })}`}>
{relatedDefinition.title}
</Link>
</Td>
</tr>
))}
</tbody>
)}
</Table>
</div>
</Stack>
</Section>
</Stack>
) : (
<Loading />
)}
{specificModule && !isLoading ? (
<>
<Section>
<Stack gap={0.5}>
<Heading type="sectionTitle">Sources</Heading>
<div style={{ overflow: 'clip' }}>
<Table fixedHead>
<thead>
<tr>
<Th>Source Name</Th>
<Th>Memo</Th>
</tr>
</thead>
{specificModule.sources.length === 0 ? (
<EmptyTableBody>
<Text>no sources</Text>
</EmptyTableBody>
) : (
<tbody>
{specificModule.sources.map((source) => (
<tr key={source.sourceName}>
<Td>
<Link to={path.sources.show(source.sourceName)}>{source.sourceName}</Link>
</Td>
<Td>
<Text>{source.memo}</Text>
</Td>
</tr>
))}
</tbody>
)}
</Table>
</div>
</Stack>
</Section>

<Section>
<Stack gap={0.5}>
<Cluster>
<Heading type="sectionTitle">Related Definitions</Heading>
<Link to={`${path.home()}?${stringify({ [KEY]: encode(idsToBitId(relatedDefinitionIds)) })}`}>
Select All
</Link>
</Cluster>
<div style={{ overflow: 'clip' }}>
<Table fixedHead>
<thead>
<tr>
<Th>Title</Th>
</tr>
</thead>
{specificModule.relatedDefinitions.length === 0 ? (
<EmptyTableBody>
<Text>no related definitions</Text>
</EmptyTableBody>
) : (
<tbody>
{specificModule.relatedDefinitions.map((relatedDefinition) => (
<tr key={relatedDefinition.id}>
<Td>
<Link to={`${path.home()}?${stringify({ [KEY]: encode(idsToBitId([relatedDefinition.id])) })}`}>
{relatedDefinition.title}
</Link>
</Td>
</tr>
))}
</tbody>
)}
</Table>
</div>
</Stack>
</Section>
</>
) : (
<Loading />
)}
</Stack>
</Section>
</Stack>
</StyledSection>
Expand Down
23 changes: 13 additions & 10 deletions frontend/repositories/combinedDefinitionRepository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import useSWR from 'swr'

import { path } from '@/constants/path'
import { CombinedDefinition, DotMetadata } from '@/models/combinedDefinition'
import { CombinedDefinition, CombinedDefinitionGraphOptions, DotMetadata } from '@/models/combinedDefinition'
import { bitIdToIds } from '@/utils/bitId'
import { stringify } from '@/utils/queryString'

Expand Down Expand Up @@ -93,7 +93,7 @@ const parseDotMetadata = (metadata: DotMetadataResponse): DotMetadata => {
}
}

const fetchDefinitionShow = async (requestPath: string): Promise<CombinedDefinition> => {
export const fetchCombinedDefinition = async (requestPath: string): Promise<CombinedDefinition> => {
const response = await get<CombinedDefinitionReponse>(requestPath)

return {
Expand All @@ -112,17 +112,20 @@ const fetchDefinitionShow = async (requestPath: string): Promise<CombinedDefinit
}
}

const toBooleanFlag = (value: boolean) => (value ? '1' : null)

export const useCombinedDefinition = (ids: number[], compound: boolean, concentrate: boolean, onlyModule: boolean) => {
export const stringifyCombinedDefinitionOptions = (graphOptions: CombinedDefinitionGraphOptions): string => {
const params = {
compound: toBooleanFlag(compound),
concentrate: toBooleanFlag(concentrate),
only_module: toBooleanFlag(onlyModule),
compound: graphOptions.compound,
concentrate: graphOptions.concentrate,
only_module: graphOptions.onlyModule,
}
const requestPath = `${path.api.definitions.show(ids)}?${stringify(params)}`

return stringify(params)
}

export const useCombinedDefinition = (ids: number[], graphOptions: CombinedDefinitionGraphOptions) => {
const requestPath = `${path.api.definitions.show(ids)}?${stringifyCombinedDefinitionOptions(graphOptions)}`
const shouldFetch = ids.length > 0
const { data, isLoading, mutate } = useSWR(shouldFetch ? requestPath : null, fetchDefinitionShow)
const { data, isLoading, mutate } = useSWR(shouldFetch ? requestPath : null, fetchCombinedDefinition)

return { data, isLoading, mutate }
}
12 changes: 12 additions & 0 deletions frontend/repositories/moduleDefinitionRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { path } from '@/constants/path'
import { CombinedDefinitionGraphOptions } from '@/models/combinedDefinition'
import { fetchCombinedDefinition, stringifyCombinedDefinitionOptions } from './combinedDefinitionRepository'
import useSWR from 'swr'

export const useModuleDefinition = (moduleNames: string[], graphOptions: CombinedDefinitionGraphOptions) => {
const requestPath = `${path.api.moduleDefinitions.show(moduleNames)}?${stringifyCombinedDefinitionOptions(graphOptions)}`
const shouldFetch = moduleNames.length > 0
const { data, isLoading, mutate } = useSWR(shouldFetch ? requestPath : null, fetchCombinedDefinition)

return { data, isLoading, mutate }
}
Loading