Skip to content

Commit

Permalink
Merge pull request #51 from alpaca-tc/add-module-form-to-source-list
Browse files Browse the repository at this point in the history
Add module form to source list
  • Loading branch information
alpaca-tc authored May 24, 2024
2 parents ab9d8b4 + 269d3e9 commit f69f294
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import { Button, Cluster, FaXmarkIcon, FormControl, Input, SingleComboBox, Text
import { Module } from '@/models/module'
import { useModules } from '@/repositories/moduleRepository'
import { useSourceModules } from '@/repositories/sourceModulesRepository'
import { RecentModulesContext } from '@/context/RecentModulesContext'

type Item = ComboBoxItem<Module[]>

type Props = {
sourceName: string
initialModules: Module[]
onClose: () => void
onUpdate: () => void
onUpdate: (modules: Module[]) => void
}

const equalModules = (a: Module[], b: Module[]) => a.every((module, index) => module.moduleName === (b[index]?.moduleName ?? ''))
Expand All @@ -27,7 +26,6 @@ const convertModulesToItem = (modules: Module[]): Item => ({
const DELIMITER_RE = /\s*\/\s*/

export const SourceModulesComboBox: FC<Props> = ({ sourceName, initialModules, onClose, onUpdate }) => {
const { setRecentModules } = useContext(RecentModulesContext)
const { data, isLoading, mutate } = useModules()
const { trigger } = useSourceModules(sourceName)
const defaultItems: Item[] = useMemo(() => (data ?? []).map((modules) => convertModulesToItem(modules)), [data])
Expand Down Expand Up @@ -66,9 +64,8 @@ export const SourceModulesComboBox: FC<Props> = ({ sourceName, initialModules, o
const handleUpdate = useCallback(async () => {
const modules = selectedItem?.data ?? []
await trigger({ modules })
setRecentModules(modules)
mutate()
onUpdate()
onUpdate(modules)
}, [mutate, trigger, selectedItem, onUpdate])

const items = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { FC, useCallback } from 'react'
import { Button, FaCopyIcon, Tooltip } from '@/components/ui'
import { Module } from '@/models/module'
import { useSourceModules } from '@/repositories/sourceModulesRepository'

type Props = {
sourceName: string
newModules: Module[]
onSaved: () => void
}

export const UpdateSourceModulesButton: FC<Props> = ({ sourceName, newModules, onSaved }) => {
const { trigger } = useSourceModules(sourceName)

const updateSourceModules = useCallback(async () => {
await trigger({ modules: newModules })
onSaved()
}, [newModules, onSaved, trigger])

if (newModules.length === 0) {
return (
<Button square={true} variant="primary" disabled onClick={() => {}} size="s">
<Tooltip
message={`Once you update source's modules, you can save it with the same modules.`}
horizontal="center"
vertical="bottom"
>
<FaCopyIcon />
</Tooltip>
</Button>
)
} else {
return (
<Button square={true} variant="primary" onClick={updateSourceModules} size="s">
<Tooltip message={`Save "${newModules.map((mod) => mod.moduleName).join('/')}"`} horizontal="center" vertical="bottom">
<FaCopyIcon />
</Tooltip>
</Button>
)
}
}
1 change: 1 addition & 0 deletions frontend/components/UpdateSourceModulesButton/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { UpdateSourceModulesButton } from './UpdateSourceModulesButton'
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ import { path } from '@/constants/path'
import { color } from '@/constants/theme'
import { CombinedDefinition } from '@/models/combinedDefinition'

import { SourceModulesComboBox } from '../SourceModulesComboBox'
import { Source, sortSources } from '@/models/source'
import { SourceMemoInput } from '../SourceMemoInput'
import { RecentModulesContext } from '@/context/RecentModulesContext'
import { useSourceModules } from '@/repositories/sourceModulesRepository'
import { Module } from '@/models/module'
import { SourceModulesComboBox } from '@/components/SourceModulesComboBox'
import { UpdateSourceModulesButton } from '@/components/UpdateSourceModulesButton'
import { SourceMemoInput } from '@/components/SourceMemoInput'

const sortTypes = ['asc', 'desc', 'none'] as const

Expand All @@ -43,15 +42,9 @@ type DefinitionSourceTrProps = {
}

const DefinitionSourceTr: FC<DefinitionSourceTrProps> = ({ source, mutateCombinedDefinition }) => {
const { recentModules } = useContext(RecentModulesContext)
const { recentModules, setRecentModules } = useContext(RecentModulesContext)
const [editingMemo, setEditingMemo] = useState<boolean>(false)
const [editingModules, setEditingModules] = useState<boolean>(false)
const { trigger } = useSourceModules(source.sourceName)

const updateSourceModules = useCallback(async () => {
await trigger({ modules: recentModules })
mutateCombinedDefinition()
}, [recentModules])

return (
<tr>
Expand Down Expand Up @@ -95,7 +88,8 @@ const DefinitionSourceTr: FC<DefinitionSourceTrProps> = ({ source, mutateCombine
<SourceModulesComboBox
sourceName={source.sourceName}
initialModules={source.modules}
onUpdate={() => {
onUpdate={(modules) => {
setRecentModules(modules)
setEditingModules(false)
mutateCombinedDefinition()
}}
Expand All @@ -122,17 +116,13 @@ const DefinitionSourceTr: FC<DefinitionSourceTrProps> = ({ source, mutateCombine
</Button>
</div>

{source.modules.length === 0 && recentModules.length > 0 && (
{source.modules.length === 0 && (
<div>
<Button square={true} variant="primary" onClick={updateSourceModules} size="s">
<Tooltip
message={`Save "${recentModules.map((mod) => mod.moduleName).join('/')}"`}
horizontal="center"
vertical="bottom"
>
<FaCopyIcon />
</Tooltip>
</Button>
<UpdateSourceModulesButton
sourceName={source.sourceName}
newModules={recentModules}
onSaved={mutateCombinedDefinition}
/>
</div>
)}
</Cluster>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComponentProps, FC, useMemo, useState } from 'react'
import { ComponentProps, FC, useContext, useMemo, useState } from 'react'
import styled from 'styled-components'

import { Link } from '@/components/Link'
Expand All @@ -19,9 +19,10 @@ import { path } from '@/constants/path'
import { spacing } from '@/constants/theme'
import { DotDependencyMetadata, DotMetadata, DotModuleMetadata, DotSourceMetadata } from '@/models/combinedDefinition'

import { SourceModulesComboBox } from '../SourceModulesComboBox'
import { DialogProps } from '../dialog'
import { SourceMemoInput } from '../SourceMemoInput'
import { SourceModulesComboBox } from '@/components/SourceModulesComboBox'
import { RecentModulesContext } from '@/context/RecentModulesContext'
import { SourceMemoInput } from '@/components/SourceMemoInput'

type Props = {
dotMetadata: DotMetadata | null
Expand All @@ -37,6 +38,7 @@ const SourceDotMetadataContent: FC<{ metadata: DotSourceMetadata } & Pick<Props,
metadata,
mutateCombinedDefinition,
}) => {
const { setRecentModules } = useContext(RecentModulesContext)
const [editingModules, setEditingModules] = useState<boolean>(false)
const [editingMemo, setEditingMemo] = useState<boolean>(false)
const items: ComponentProps<typeof DefinitionList>['items'] = [
Expand Down Expand Up @@ -85,7 +87,8 @@ const SourceDotMetadataContent: FC<{ metadata: DotSourceMetadata } & Pick<Props,
<SourceModulesComboBox
sourceName={metadata.sourceName}
initialModules={metadata.modules}
onUpdate={() => {
onUpdate={(modules) => {
setRecentModules(modules)
setEditingModules(false)
mutateCombinedDefinition()
}}
Expand Down
118 changes: 100 additions & 18 deletions frontend/pages/Sources/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import styled from 'styled-components'

import { Link } from '@/components/Link'
import { Loading } from '@/components/Loading'
import { EmptyTableBody, Heading, Section, Stack, Table, Td, Text, Th } from '@/components/ui'
import { Button, Cluster, EmptyTableBody, FaPencilIcon, Heading, Section, Stack, Table, Td, Text, Th } from '@/components/ui'
import { path } from '@/constants/path'
import { spacing } from '@/constants/theme'
import { useSources } from '@/repositories/sourceRepository'
import { Sources, sortSources } from '@/models/source'
import { Source, Sources, sortSources } from '@/models/source'
import { Module } from '@/models/module'
import { SourceModulesComboBox } from '@/components/SourceModulesComboBox'
import { UpdateSourceModulesButton } from '@/components/UpdateSourceModulesButton'
import { SourceMemoInput } from '@/components/SourceMemoInput'

const sortTypes = ['asc', 'desc', 'none'] as const

Expand All @@ -18,9 +22,93 @@ type SortState = {
sort: SortTypes
}

type RowProps = {
source: Source
recentModules: Module[]
onUpdated: () => void
setRecentModules: React.Dispatch<React.SetStateAction<Module[]>>
}

const Row: FC<RowProps> = ({ source, recentModules, onUpdated, setRecentModules }) => {
const [editingMemo, setEditingMemo] = useState<boolean>(false)
const [editingModules, setEditingModules] = useState<boolean>(false)

return (
<tr>
<Td>
<Link to={path.sources.show(source.sourceName)}>{source.sourceName}</Link>
</Td>
<Td>
{editingMemo ? (
<SourceMemoInput
sourceName={source.sourceName}
initialMemo={source.memo}
onUpdate={() => {
setEditingMemo(false)
onUpdated()
}}
onClose={() => {
setEditingMemo(false)
}}
/>
) : (
<Cluster>
<Text>{source.memo}</Text>
<Button square={true} onClick={() => setEditingMemo(true)} size="s">
<FaPencilIcon alt="Edit" />
</Button>
</Cluster>
)}
</Td>
{!editingMemo && editingModules ? (
<Td>
<SourceModulesComboBox
sourceName={source.sourceName}
initialModules={source.modules}
onUpdate={(modules) => {
setRecentModules(modules)
setEditingModules(false)
onUpdated()
}}
onClose={() => {
setEditingModules(false)
}}
/>
</Td>
) : (
<Td>
<Cluster align="bottom">
<div>
{source.modules.map((module, index) => (
<Text key={index} as="div" whiteSpace="nowrap">
<Link to={path.modules.show(source.modules.slice(0, index + 1).map((mod) => mod.moduleName))}>
{module.moduleName}
</Link>
</Text>
))}
</div>
<div>
<Button square={true} onClick={() => setEditingModules(true)} size="s">
<FaPencilIcon alt="Edit" />
</Button>
</div>

{source.modules.length === 0 && (
<div>
<UpdateSourceModulesButton sourceName={source.sourceName} newModules={recentModules} onSaved={onUpdated} />
</div>
)}
</Cluster>
</Td>
)}
</tr>
)
}

export const List: FC = () => {
const { data, isLoading } = useSources()
const { data, mutate, isLoading } = useSources()
const [sortState, setSortState] = useState<SortState>({ key: 'sourceName', sort: 'asc' })
const [recentModules, setRecentModules] = useState<Module[]>([])

const setNextSortType = useCallback(
(key: SortState['key']) => {
Expand Down Expand Up @@ -63,28 +151,22 @@ export const List: FC = () => {
<Th sort={sortState.key === 'sourceName' ? sortState.sort : 'none'} onSort={() => setNextSortType('sourceName')}>
Source name
</Th>
<Th fixed sort={sortState.key === 'modules' ? sortState.sort : 'none'} onSort={() => setNextSortType('modules')}>
<Th>Memo</Th>
<Th sort={sortState.key === 'modules' ? sortState.sort : 'none'} onSort={() => setNextSortType('modules')}>
Modules
</Th>
</tr>
</thead>
{sources && sources.length > 0 ? (
<tbody>
{sources.map((source) => (
<tr key={source.sourceName}>
<Td>
<Link to={path.sources.show(source.sourceName)}>{source.sourceName}</Link>
</Td>
<Td>
{source.modules.map((module, index) => (
<Text key={index} as="div" whiteSpace="nowrap">
<Link to={path.modules.show(source.modules.slice(0, index + 1).map((mod) => mod.moduleName))}>
{module.moduleName}
</Link>
</Text>
))}
</Td>
</tr>
<Row
key={source.sourceName}
source={source}
recentModules={recentModules}
onUpdated={mutate}
setRecentModules={setRecentModules}
/>
))}
</tbody>
) : (
Expand Down
4 changes: 2 additions & 2 deletions frontend/repositories/sourceRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type SourcesReponse = {
}

export const useSources = () => {
const { data, isLoading } = useSWR<Sources>(path.api.sources.index(), async () => {
const { data, mutate, isLoading } = useSWR<Sources>(path.api.sources.index(), async () => {
const response = await get<SourcesReponse>(path.api.sources.index())
return {
sources: response.sources.map((source) => ({
Expand All @@ -29,7 +29,7 @@ export const useSources = () => {
}
})

return { data, isLoading }
return { data, mutate, isLoading }
}

type SpecificSourceResponse = {
Expand Down

0 comments on commit f69f294

Please sign in to comment.