feat(ui): rough out boards UI

This commit is contained in:
psychedelicious 2023-06-15 16:43:50 +10:00
parent 5865ecd530
commit d306a84447
5 changed files with 172 additions and 60 deletions

View File

@ -0,0 +1,36 @@
import { Flex, Icon, Text } from '@chakra-ui/react';
import { FaPlus } from 'react-icons/fa';
const AddBoardButton = () => {
return (
<Flex
sx={{
flexDir: 'column',
justifyContent: 'space-between',
alignItems: 'center',
cursor: 'pointer',
w: 'full',
h: 'full',
gap: 1,
}}
>
<Flex
sx={{
justifyContent: 'center',
alignItems: 'center',
borderWidth: '1px',
borderRadius: 'base',
borderColor: 'base.800',
w: 'full',
h: 'full',
aspectRatio: '1/1',
}}
>
<Icon boxSize={8} color="base.700" as={FaPlus} />
</Flex>
<Text sx={{ color: 'base.200', fontSize: 'xs' }}>New Board</Text>
</Flex>
);
};
export default AddBoardButton;

View File

@ -0,0 +1,53 @@
import { Grid } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { selectBoardsAll } from 'features/gallery/store/boardSlice';
import { memo } from 'react';
import HoverableBoard from './HoverableBoard';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import AddBoardButton from './AddBoardButton';
const selector = createSelector(
selectBoardsAll,
(boards) => {
return { boards };
},
defaultSelectorOptions
);
const BoardsList = () => {
const { boards } = useAppSelector(selector);
return (
<OverlayScrollbarsComponent
defer
style={{ height: '100%', width: '100%' }}
options={{
scrollbars: {
visibility: 'auto',
autoHide: 'move',
autoHideDelay: 1300,
theme: 'os-theme-dark',
},
}}
>
<Grid
className="list-container"
sx={{
gap: 2,
gridTemplateRows: '5rem 5rem',
gridAutoFlow: 'column dense',
gridAutoColumns: '4rem',
}}
>
<AddBoardButton />
{boards.map((board) => (
<HoverableBoard key={board.board_id} board={board} />
))}
</Grid>
</OverlayScrollbarsComponent>
);
};
export default memo(BoardsList);

View File

@ -1,7 +1,15 @@
import { Box, Image, MenuItem, MenuList, Text } from '@chakra-ui/react';
import {
Box,
Flex,
Icon,
Image,
MenuItem,
MenuList,
Text,
} from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { memo, useCallback, useState } from 'react';
import { FaImage } from 'react-icons/fa';
import { PropsWithChildren, memo, useCallback, useState } from 'react';
import { FaFolder, FaImage } from 'react-icons/fa';
import { ContextMenu } from 'chakra-ui-contextmenu';
import { useTranslation } from 'react-i18next';
import { ExternalLinkIcon } from '@chakra-ui/icons';
@ -11,10 +19,12 @@ import { EntityId, createSelector } from '@reduxjs/toolkit';
import {
selectFilteredImagesIds,
selectImagesById,
} from '../store/imagesSlice';
import { RootState } from '../../../app/store/store';
import { defaultSelectorOptions } from '../../../app/store/util/defaultMemoizeOptions';
} from '../../store/imagesSlice';
import { RootState } from '../../../../app/store/store';
import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions';
import { useSelector } from 'react-redux';
import { IAIImageFallback } from 'common/components/IAIImageFallback';
import { boardIdSelected } from 'features/gallery/store/boardSlice';
interface HoverableBoardProps {
board: BoardDTO;
@ -26,20 +36,16 @@ interface HoverableBoardProps {
const HoverableBoard = memo(({ board }: HoverableBoardProps) => {
const dispatch = useAppDispatch();
const { board_name, board_id, cover_image_name } = board;
const coverImage = useAppSelector((state) =>
selectImagesById(state, cover_image_name as EntityId)
);
const { board_name, board_id, cover_image_url } = board;
const { t } = useTranslation();
const handleSelectBoard = useCallback(() => {
// dispatch(imageSelected(board_id));
}, []);
dispatch(boardIdSelected(board_id));
}, [board_id, dispatch]);
return (
<Box sx={{ w: 'full', h: 'full', touchAction: 'none' }}>
<Box sx={{ touchAction: 'none' }}>
<ContextMenu<HTMLDivElement>
menuProps={{ size: 'sm', isLazy: true }}
renderMenu={() => (
@ -54,42 +60,50 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => {
)}
>
{(ref) => (
<Box
<Flex
position="relative"
key={board_id}
userSelect="none"
onClick={handleSelectBoard}
ref={ref}
sx={{
display: 'flex',
flexDir: 'column',
justifyContent: 'center',
justifyContent: 'space-between',
alignItems: 'center',
cursor: 'pointer',
w: 'full',
h: 'full',
transition: 'transform 0.2s ease-out',
aspectRatio: '1/1',
cursor: 'pointer',
gap: 1,
}}
>
<Image
loading="lazy"
// objectFit={
// shouldUseSingleGalleryColumn ? 'contain' : galleryImageObjectFit
// }
draggable={false}
rounded="md"
src={coverImage ? coverImage.thumbnail_url : undefined}
fallback={<FaImage />}
<Flex
sx={{
width: '100%',
height: '100%',
maxWidth: '100%',
maxHeight: '100%',
justifyContent: 'center',
alignItems: 'center',
borderWidth: '1px',
borderRadius: 'base',
borderColor: 'base.800',
w: 'full',
h: 'full',
aspectRatio: '1/1',
}}
/>
<Text textAlign="center">{board_name}</Text>
</Box>
>
{cover_image_url ? (
<Image
loading="lazy"
objectFit="cover"
draggable={false}
rounded="md"
src={cover_image_url}
fallback={<IAIImageFallback />}
sx={{}}
/>
) : (
<Icon boxSize={8} color="base.700" as={FaFolder} />
)}
</Flex>
<Text sx={{ color: 'base.200', fontSize: 'xs' }}>{board_name}</Text>
</Flex>
)}
</ContextMenu>
</Box>

View File

@ -57,9 +57,11 @@ import { receivedPageOfImages } from 'services/thunks/image';
import { boardSelector } from '../store/boardSelectors';
import { BoardDTO, ImageDTO } from '../../../services/api';
import { isBoardDTO, isImageDTO } from '../../../services/types/guards';
import HoverableBoard from './HoverableBoard';
import HoverableBoard from './Boards/HoverableBoard';
import IAIInput from '../../../common/components/IAIInput';
import { boardCreated } from '../../../services/thunks/board';
import BoardsList from './Boards/BoardsList';
import { selectBoardsById } from '../store/boardSlice';
const itemSelector = createSelector(
[(state: RootState) => state],
@ -70,24 +72,23 @@ const itemSelector = createSelector(
let areMoreAvailable = false;
let isLoading = true;
if (gallery.galleryView === 'images' || gallery.galleryView === 'assets') {
const { categories } = images;
const { categories } = images;
const allImages = selectImagesAll(state);
items = allImages.filter((i) => categories.includes(i.image_category));
areMoreAvailable = items.length < images.total;
isLoading = images.isLoading;
} else if (gallery.galleryView === 'boards') {
items = Object.values(boards.entities) as BoardDTO[];
areMoreAvailable = items.length < boards.total;
isLoading = boards.isLoading;
}
const allImages = selectImagesAll(state);
items = allImages.filter((i) => categories.includes(i.image_category));
areMoreAvailable = items.length < images.total;
isLoading = images.isLoading;
const selectedBoard = boards.selectedBoardId
? selectBoardsById(state, boards.selectedBoardId)
: undefined;
return {
items,
isLoading,
areMoreAvailable,
categories: images.categories,
selectedBoard,
};
},
defaultSelectorOptions
@ -153,7 +154,7 @@ const ImageGalleryContent = () => {
boards,
} = useAppSelector(mainSelector);
const { items, areMoreAvailable, isLoading, categories } =
const { items, areMoreAvailable, isLoading, categories, selectedBoard } =
useAppSelector(itemSelector);
const handleLoadMoreImages = useCallback(() => {
@ -247,17 +248,14 @@ const ImageGalleryContent = () => {
size="sm"
icon={<FaServer />}
/>
<IAIIconButton
tooltip={t('gallery.boards')}
aria-label={t('gallery.boards')}
onClick={handleClickBoardsView}
isChecked={galleryView === 'boards'}
size="sm"
icon={<FaFolder />}
/>
</ButtonGroup>
{selectedBoard && (
<Flex>
<Text>{selectedBoard.board_name}</Text>
</Flex>
)}
<Flex gap={2}>
<IAIPopover
{/* <IAIPopover
triggerComponent={
<IAIIconButton
tooltip="Add Board"
@ -283,7 +281,7 @@ const ImageGalleryContent = () => {
Create
</IAIButton>
</Flex>
</IAIPopover>
</IAIPopover> */}
<IAIPopover
triggerComponent={
<IAIIconButton
@ -342,6 +340,9 @@ const ImageGalleryContent = () => {
/>
</Flex>
</Flex>
<Box>
<BoardsList />
</Box>
<Flex direction="column" gap={2} h="full">
{items.length || areMoreAvailable ? (
<>

View File

@ -1,4 +1,5 @@
import {
EntityId,
PayloadAction,
Update,
createEntityAdapter,
@ -19,6 +20,7 @@ type AdditionalBoardsState = {
limit: number;
total: number;
isLoading: boolean;
selectedBoardId: EntityId | null;
};
export const initialBoardsState =
@ -27,6 +29,7 @@ export const initialBoardsState =
limit: 0,
total: 0,
isLoading: false,
selectedBoardId: null,
});
export type BoardsState = typeof initialBoardsState;
@ -44,6 +47,9 @@ const boardsSlice = createSlice({
boardRemoved: (state, action: PayloadAction<string>) => {
boardsAdapter.removeOne(state, action.payload);
},
boardIdSelected: (state, action: PayloadAction<string>) => {
state.selectedBoardId = action.payload;
},
},
extraReducers: (builder) => {
builder.addCase(receivedBoards.pending, (state) => {
@ -71,7 +77,9 @@ export const {
selectTotal: selectBoardsTotal,
} = boardsAdapter.getSelectors<RootState>((state) => state.boards);
export const { boardUpserted, boardUpdatedOne, boardRemoved } =
export const { boardUpserted, boardUpdatedOne, boardRemoved, boardIdSelected } =
boardsSlice.actions;
export const boardsSelector = (state: RootState) => state.boards;
export default boardsSlice.reducer;