Skip to content

Commit

Permalink
Merge pull request #60 from alpaca-tc/module_graph
Browse files Browse the repository at this point in the history
Experimental feature: Add graph for specific module
  • Loading branch information
alpaca-tc authored Jun 17, 2024
2 parents 6aa330b + 64800c1 commit c45c057
Show file tree
Hide file tree
Showing 30 changed files with 971 additions and 154 deletions.
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

0 comments on commit c45c057

Please sign in to comment.