mirror of
https://github.com/harness/drone.git
synced 2025-05-19 10:29:55 +08:00
228 lines
8.0 KiB
TypeScript
228 lines
8.0 KiB
TypeScript
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
import {
|
|
Button,
|
|
ButtonVariation,
|
|
Container,
|
|
Heading,
|
|
Layout,
|
|
NoDataCard,
|
|
TableV2 as Table,
|
|
Text
|
|
} from '@harnessio/uicore'
|
|
import { Icon } from '@harnessio/icons'
|
|
import { Color, FontVariation } from '@harnessio/design-system'
|
|
import cx from 'classnames'
|
|
import Keywords from 'react-keywords'
|
|
import { useGet } from 'restful-react'
|
|
import type { CellProps, Column } from 'react-table'
|
|
import { useHistory } from 'react-router-dom'
|
|
import { Classes, Popover, Position } from '@blueprintjs/core'
|
|
import { useStrings } from 'framework/strings'
|
|
import { ButtonRoleProps, voidFn } from 'utils/Utils'
|
|
import { useShowRequestError } from 'hooks/useShowRequestError'
|
|
import { TypesSpace, useGetSpace } from 'services/code'
|
|
import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
|
|
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
|
import { NewSpaceModalButton } from 'components/NewSpaceModalButton/NewSpaceModalButton'
|
|
import { useAppContext } from 'AppContext'
|
|
// import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
|
|
|
// import { usePageIndex } from 'hooks/usePageIndex'
|
|
import css from './SpaceSelector.module.scss'
|
|
|
|
interface SpaceSelectorProps {
|
|
onSelect: (space: TypesSpace, isUserAction: boolean) => void
|
|
}
|
|
|
|
export const SpaceSelector: React.FC<SpaceSelectorProps> = ({ onSelect }) => {
|
|
const { routes } = useAppContext()
|
|
const { getString } = useStrings()
|
|
const history = useHistory()
|
|
const [selectedSpace, setSelectedSpace] = useState<TypesSpace | undefined>()
|
|
const space = useGetSpaceParam()
|
|
const [opened, setOpened] = React.useState(false)
|
|
const [searchTerm, setSearchTerm] = useState('')
|
|
// const [page, setPage] = usePageIndex(1)
|
|
const { data, error } = useGetSpace({ space_ref: encodeURIComponent(space), lazy: !space })
|
|
|
|
const {
|
|
data: spaces,
|
|
refetch,
|
|
response
|
|
} = useGet({
|
|
path: '/api/v1/user/memberships',
|
|
queryParams: { query: searchTerm },
|
|
debounce: 500
|
|
})
|
|
|
|
const selectSpace = useCallback(
|
|
(_space: TypesSpace, isUserAction: boolean) => {
|
|
setSelectedSpace(_space)
|
|
onSelect(_space, isUserAction)
|
|
},
|
|
[onSelect]
|
|
)
|
|
|
|
useEffect(() => {
|
|
//space is used in the api call to get data, so it'll always be the same
|
|
if (space && data) {
|
|
selectSpace(data, false)
|
|
refetch()
|
|
}
|
|
}, [data])
|
|
|
|
useEffect(() => {
|
|
if (space && !selectedSpace && data) {
|
|
selectSpace(data, false)
|
|
}
|
|
}, [space, selectedSpace, data, onSelect, selectSpace])
|
|
|
|
useEffect(() => {
|
|
if (response?.status === 403) {
|
|
history.push(routes.toSignIn())
|
|
}
|
|
}, [response, history])
|
|
|
|
useShowRequestError(error)
|
|
const NewSpaceButton = (
|
|
<NewSpaceModalButton
|
|
space={space}
|
|
modalTitle={getString('createSpace')}
|
|
text={getString('newSpace')}
|
|
variation={ButtonVariation.PRIMARY}
|
|
icon="plus"
|
|
onRefetch={voidFn(refetch)}
|
|
/>
|
|
)
|
|
|
|
const columns: Column<{ space: TypesSpace }>[] = useMemo(
|
|
() => [
|
|
{
|
|
Header: getString('spaces'),
|
|
width: 'calc(100% - 180px)',
|
|
Cell: ({ row }: CellProps<{ space: TypesSpace }>) => {
|
|
const record = row.original
|
|
return (
|
|
<Container className={css.nameContainer}>
|
|
<Layout.Horizontal spacing="small" style={{ flexGrow: 1 }}>
|
|
<Icon
|
|
name={'nav-project'}
|
|
size={22}
|
|
className={css.iconContainer}
|
|
padding={{ bottom: 'small', left: 'small', right: 'medium' }}
|
|
/>
|
|
<Layout.Vertical flex className={css.name}>
|
|
<Text className={css.repoName} lineClamp={2}>
|
|
<Keywords value={searchTerm}>{record.space.uid}</Keywords>
|
|
</Text>
|
|
{record.space.description && (
|
|
<Text className={css.desc} lineClamp={1}>
|
|
{record.space.description}
|
|
</Text>
|
|
)}
|
|
</Layout.Vertical>
|
|
</Layout.Horizontal>
|
|
</Container>
|
|
)
|
|
}
|
|
}
|
|
],
|
|
[getString, searchTerm]
|
|
)
|
|
|
|
return (
|
|
<Popover
|
|
portalClassName={css.popoverPortal}
|
|
targetClassName={css.popoverTarget}
|
|
popoverClassName={css.popoverContent}
|
|
position={Position.RIGHT}
|
|
usePortal={false}
|
|
transitionDuration={0}
|
|
captureDismiss
|
|
onInteraction={setOpened}
|
|
isOpen={opened}>
|
|
<Container
|
|
className={cx(css.spaceSelector, { [css.selected]: opened })}
|
|
{...ButtonRoleProps}
|
|
onClick={() => setOpened(!opened)}>
|
|
<Layout.Horizontal>
|
|
<Container className={css.label}>
|
|
<Layout.Vertical>
|
|
<Container>
|
|
<Text className={css.spaceLabel} icon="nav-project" iconProps={{ size: 12 }}>
|
|
{getString('space').toUpperCase()}
|
|
</Text>
|
|
</Container>
|
|
</Layout.Vertical>
|
|
<Text className={css.spaceName} lineClamp={1}>
|
|
{selectedSpace ? selectedSpace.uid : getString('selectSpace')}
|
|
</Text>
|
|
</Container>
|
|
<Container className={css.icon}>
|
|
<Icon name="main-chevron-right" size={10} />
|
|
</Container>
|
|
</Layout.Horizontal>
|
|
</Container>
|
|
|
|
<Container padding="large">
|
|
<Heading level={2} padding={{ left: 'small' }} color={Color.BLACK}>
|
|
<Layout.Horizontal flex={{ justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<Text font={{ variation: FontVariation.H5 }}>{getString('selectSpaceText')}</Text>
|
|
{!!spaces?.length && (
|
|
<Layout.Horizontal flex={{ justifyContent: 'space-between' }}>
|
|
<SearchInputWithSpinner
|
|
loading={false}
|
|
spinnerPosition="left"
|
|
query={searchTerm}
|
|
setQuery={setSearchTerm}
|
|
/>
|
|
<Container padding={{ left: 'small', right: 'small' }}></Container>
|
|
{NewSpaceButton}
|
|
<Button icon={'small-cross'} variation={ButtonVariation.ICON} className={Classes.POPOVER_DISMISS} />
|
|
</Layout.Horizontal>
|
|
)}
|
|
{spaces?.length === 0 && (
|
|
<Container flex={{ alignItems: 'self-end' }}>
|
|
<Button icon={'small-cross'} variation={ButtonVariation.ICON} className={Classes.POPOVER_DISMISS} />
|
|
</Container>
|
|
)}
|
|
</Layout.Horizontal>
|
|
</Heading>
|
|
<Container padding={{ left: 'small' }}>
|
|
<Layout.Vertical padding={{ top: 'xxlarge' }} spacing="small">
|
|
{!!spaces?.length && (
|
|
<Table<{ space: TypesSpace }>
|
|
hideHeaders
|
|
className={cx(css.table, css.tableContainer)}
|
|
columns={columns}
|
|
data={spaces || []}
|
|
onRowClick={spaceData => {
|
|
setOpened(false)
|
|
selectSpace({ uid: spaceData?.space?.uid, path: spaceData?.space?.path }, true)
|
|
}}
|
|
getRowClassName={row => cx(css.row, !row.original.space.description && css.noDesc)}
|
|
/>
|
|
)}
|
|
{spaces?.length === 0 && (
|
|
<NoDataCard
|
|
button={
|
|
<NewSpaceModalButton
|
|
space={space}
|
|
modalTitle={getString('createSpace')}
|
|
text={getString('createSpace')}
|
|
variation={ButtonVariation.PRIMARY}
|
|
icon="plus"
|
|
onRefetch={voidFn(refetch)}
|
|
/>
|
|
}
|
|
message={<Text font={{ variation: FontVariation.H4 }}> {getString('emptySpaceText')}</Text>}
|
|
/>
|
|
)}
|
|
{/* <ResourceListingPagination response={response} page={page} setPage={setPage} /> */}
|
|
</Layout.Vertical>
|
|
</Container>
|
|
</Container>
|
|
</Popover>
|
|
)
|
|
}
|