Skip to content

Commit

Permalink
#68 Added Branch step
Browse files Browse the repository at this point in the history
  • Loading branch information
artzub committed Feb 3, 2022
1 parent 7611823 commit 90dab30
Show file tree
Hide file tree
Showing 19 changed files with 603 additions and 144 deletions.
185 changes: 185 additions & 0 deletions src/components/Header/components/BranchStep/Body.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import slice from '@/redux/modules/branches';
import repositoriesSlice from '@/redux/modules/repositories';
import Highlight from '@/shared/components/Highlight';
import LoadingOverlay from '@/shared/components/LoadingOverlay';
import { ScrollBarMixin } from '@/shared/components/ScrollBar';
import { useUIProperty } from '@/shared/hooks';
import { Avatar, ListItem as ListItemOrigin, ListItemAvatar, ListSubheader, TextField } from '@material-ui/core';
import List from '@material-ui/core/List';
import ListItemText from '@material-ui/core/ListItemText';
import debounce from 'lodash.debounce';
import SourceBranchIcon from 'mdi-react/SourceBranchIcon';
import { useDispatch, useSelector } from 'react-redux';
import { useDebounce } from 'react-use';
import { FixedSizeList } from 'react-window';
import styled from 'styled-components';
import Marker from '../shared/Marker';
import Secondary from './Secondary';

const Container = styled.div`
min-height: 100px;
display: flex;
flex-direction: column;
padding: 10px;
box-sizing: border-box;
width: 100%;
`;

const ListItems = styled(FixedSizeList)`
height: auto !important;
max-height: 300px;
${ScrollBarMixin}
`;

const ListItem = styled(ListItemOrigin)`
cursor: pointer;
transition: background 0.3s;
&:hover {
background: rgba(255, 255, 255, 0.1);
}
&:active {
background: rgba(255, 255, 255, 0.2);
}
`;

const NotData = styled(({ className }) => (
<div className={className}>
<div>Branches not found</div>
</div>
))`
display: flex;
justify-content: center;
align-items: center;
`;

const Primary = styled.span`
display: flex;
align-items: center;
`;

const bySearch = (search) => (item) => {
return String(item?.name).toLowerCase().includes(search.toLowerCase());
};

const Body = () => {
const dispatch = useDispatch();
const inputRef = useRef();
const [search, setSearch] = useState('');
const { isFetching, items } = useSelector(slice.selectors.getState);
const [bodyOpen, setBodyOpen] = useUIProperty('bodyOpen');
const [filtered, setFiltered] = useState(items);
const { selected: repository } = useSelector(repositoriesSlice.selectors.getState);
const { default_branch } = repository || {};

const changeSearch = useMemo(
() => debounce(
(value) => setSearch(value),
300,
),
[],
);

const onChange = useCallback(
(event) => {
changeSearch(event.target.value);
},
[changeSearch],
);

const onClick = useCallback(
(item) => () => {
dispatch(slice.actions.setSelected(item));
setBodyOpen(false);
},
[setBodyOpen, dispatch],
);

const ListHeader = useMemo(
() => (
<ListSubheader component="div">
Branches: {filtered.length || 0} of {items.length || 0}
</ListSubheader>
),
[filtered.length, items.length],
);

const Item = useCallback(
({ index, style }) => {
const item = filtered[index];
const isDefault = default_branch === item.name;

return (
<ListItem
alignItems="center"
key={item.name}
onClick={onClick(item)}
style={style}
title={item.name}
>
<ListItemAvatar>
<Avatar>
<SourceBranchIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={(
<Primary>
{isDefault && <Marker>default</Marker>}
<Highlight search={search} text={item.name} />
</Primary>
)}
secondary={<Secondary item={item} />}
/>
</ListItem>
);
},
[filtered, default_branch, onClick, search],
);

useEffect(
() => {
setFiltered(search ? items.filter(bySearch(search)) : items);
},
[items, search, dispatch],
);

useDebounce(
() => {
if (inputRef.current && bodyOpen) {
inputRef.current.querySelector('input').focus();
}
},
100,
[bodyOpen],
);

return (
<Container>
<TextField
label="Branch"
placeholder="Type branch name"
onChange={onChange}
ref={inputRef}
/>
<LoadingOverlay loading={isFetching}>
<List
dense
subheader={ListHeader}
>
{!filtered.length && <NotData />}
<ListItems
itemCount={filtered.length}
itemSize={76}
height={300}
>
{Item}
</ListItems>
</List>
</LoadingOverlay>
</Container>
);
};

export default Body;
45 changes: 45 additions & 0 deletions src/components/Header/components/BranchStep/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import slice from '@/redux/modules/branches';
import HistoryIcon from 'mdi-react/HistoryIcon';
import SourceBranchIcon from 'mdi-react/SourceBranchIcon';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import Container from '../shared/HeaderContainer';
import InfoContainer from '../shared/InfoContainer';
import PropertiesOrigin from '../shared/Properties';
import Property from '../shared/Property';
import PropertyValue from '../shared/PropertyValue';
import Title from '../shared/Title';

const Properties = styled(PropertiesOrigin)`
font-size: 1em;
`;

const Header = (props) => {
const { selected } = useSelector(slice.selectors.getState);
const { name, commits } = selected || {};

return (
<Container {...props}>
<SourceBranchIcon size={20} />
<InfoContainer>
{!selected && <div>Choose a branch</div>}
{selected && (
<Properties>
<Property>
<Title title={name}>{name}</Title>
</Property>
<Property title="Commits">
<HistoryIcon size={16} />
<PropertyValue>
{commits}
</PropertyValue>
</Property>
</Properties>
)}
</InfoContainer>
</Container>
);
};

export default Header;
31 changes: 31 additions & 0 deletions src/components/Header/components/BranchStep/Secondary.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import HistoryIcon from 'mdi-react/HistoryIcon';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import Properties from '../shared/Properties';
import Property from '../shared/Property';
import PropertyValue from '../shared/PropertyValue';

const SecondaryContainer = styled.span`
display: flex;
flex-direction: column;
`;

const Secondary = ({ item }) => (
<SecondaryContainer>
<Properties>
<Property title="Commits">
<HistoryIcon size={16} />
<PropertyValue>
{item.commits}
</PropertyValue>
</Property>
</Properties>
</SecondaryContainer>
);

Secondary.propTypes = {
item: PropTypes.shape().isRequired,
};

export default Secondary;
53 changes: 36 additions & 17 deletions src/components/Header/components/RepositoryStep/Body.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import slice from '@/redux/modules/repositories';
import Highlight from "@/shared/components/Highlight";
import LoadingOverlay from "@/shared/components/LoadingOverlay";
import { ScrollBarMixin } from "@/shared/components/ScrollBar";
import { useUIProperty } from "@/shared/hooks";
import Highlight from '@/shared/components/Highlight';
import LoadingOverlay from '@/shared/components/LoadingOverlay';
import { ScrollBarMixin } from '@/shared/components/ScrollBar';
import { useUIProperty } from '@/shared/hooks';
import {
Avatar, ListItem as ListItemOrigin,
ListItemAvatar, ListSubheader, TextField,
} from "@material-ui/core";
import List from "@material-ui/core/List";
import ListItemText from "@material-ui/core/ListItemText";
} from '@material-ui/core';
import List from '@material-ui/core/List';
import ListItemText from '@material-ui/core/ListItemText';
import debounce from 'lodash.debounce';
import SourceRepositoryIcon from "mdi-react/SourceRepositoryIcon";
import { useDispatch, useSelector } from "react-redux";
import { useDebounce } from "react-use";
import BookIcon from 'mdi-react/BookIcon';
import BookLockIcon from 'mdi-react/BookLockIcon';
import SourceRepositoryIcon from 'mdi-react/SourceRepositoryIcon';
import { useDispatch, useSelector } from 'react-redux';
import { useDebounce } from 'react-use';
import { FixedSizeList } from 'react-window';
import styled from "styled-components";
import Secondary from "./Secondary";
import styled from 'styled-components';
import Marker from '../shared/Marker';
import Secondary from './Secondary';

const Container = styled.div`
min-height: 100px;
Expand All @@ -28,7 +31,8 @@ const Container = styled.div`
`;

const ListItems = styled(FixedSizeList)`
height: 300px !important;
height: auto !important;
max-height: 300px;
${ScrollBarMixin}
`;
Expand All @@ -54,8 +58,13 @@ const NotData = styled(({ className }) => (
align-items: center;
`;

const Primary = styled.span`
display: flex;
align-items: center;
`;

const bySearch = (search) => (item) => {
return item?.name?.includes(search);
return String(item?.name).toLowerCase().includes(search.toLowerCase());
};

const Body = () => {
Expand Down Expand Up @@ -83,7 +92,7 @@ const Body = () => {

const onClick = useCallback(
(item) => () => {
dispatch(slice.actions.setRepository(item));
dispatch(slice.actions.setSelected(item));
setBodyOpen(false);
},
[setBodyOpen, dispatch],
Expand All @@ -101,21 +110,31 @@ const Body = () => {
const Item = useCallback(
({ index, style }) => {
const item = filtered[index];
const title = (item.private ? 'Private' : item.fork ? 'Fork' : 'Public');

return (
<ListItem
alignItems="center"
key={item.name}
onClick={onClick(item)}
style={style}
title={`${title} | ${item.name}`}
>
<ListItemAvatar>
<Avatar>
<SourceRepositoryIcon />
{item.private ? <BookLockIcon /> : (
item.fork ? <SourceRepositoryIcon /> : <BookIcon />
)}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={<Highlight search={search} text={item.name} />}
primary={(
<Primary>
{item.private && <Marker>private</Marker>}
{item.fork && <Marker>fork</Marker>}
<Highlight search={search} text={item.name} />
</Primary>
)}
secondary={<Secondary item={item} />}
/>
</ListItem>
Expand Down
Loading

0 comments on commit 90dab30

Please sign in to comment.