ソースを参照

11.04.2023 21:30

Volddemar4ik 1 年間 前
コミット
57bf7db3df

+ 2 - 1
js/Project/project/src/components/post/index.js

@@ -20,9 +20,10 @@ const Item = styled(Paper)(() => ({
 }))
 
 
-function Comments({ post = {}, loadPost }) {
+export function Comments({ post = {}, loadPost }) {
     const { postId } = useParams()
     useEffect(() => { loadPost(postId) }, [postId])
+    console.log('post: ', post)
 
     return (
         <React.Fragment>

+ 291 - 0
js/Project/project/src/components/user/collections.js

@@ -0,0 +1,291 @@
+import { url } from '../../App';
+
+import React, { createContext, useEffect, useState } from 'react';
+import {
+    List,
+    ListItem,
+    Divider,
+    ListItemText,
+    ListItemAvatar,
+    Avatar,
+    Typography,
+    IconButton,
+    Backdrop,
+    Box,
+    Modal,
+    Fade,
+    Button,
+    Stack
+} from '@mui/material'
+
+import {
+    AddRounded,
+    CloseRounded
+} from '@mui/icons-material';
+import { useDispatch, useSelector } from 'react-redux';
+import { useHistory, useParams } from 'react-router-dom';
+
+
+
+import {
+    actionFullFindCollections
+    , actionFullFinCollectionOne
+} from '../../redux/thunks';
+
+import { actionFeedFindOne } from '../../redux/action';
+
+import ModalCarousel from './collections_carousel';
+import CreateCollection from './collections_create';
+
+export const CollectionContext = createContext()
+
+
+// стили модального окна карусели коллекции
+const style = {
+    position: 'absolute',
+    top: '50%',
+    left: '50%',
+    transform: 'translate(-50%, -50%)',
+    width: '80%',
+    height: '80%',
+    // bgcolor: 'background.paper',
+    bgcolor: '#FFF',
+    // border: '2px solid #000',
+    // boxShadow: 12,
+    p: 4,
+}
+
+// стили модального окна создания коллекции
+// стили модального окна
+const style2 = {
+    position: 'absolute',
+    top: '50%',
+    left: '50%',
+    transform: 'translate(-50%, -50%)',
+    width: '50%',
+    maxWidth: '500px',
+    maxHeight: '80%',
+    bgcolor: '#FFF',
+    p: 2,
+    bgcolor: 'background.paper',
+    boxShadow: 24,
+    outline: 0,
+    borderRadius: 3,
+    padding: '5px 0'
+};
+
+
+
+
+
+
+export default function Collections({ userData }) {
+    // console.log('userData: ', userData)
+
+    const history = useHistory()
+    const dispatch = useDispatch()
+
+    // отслеживаю мой ид
+    const myId = useSelector(state => state?.auth?.payload?.sub?.id)
+
+    // запуск загрузки всех коллекций юзера при изменении ид
+    useEffect(() => {
+        dispatch(actionFullFindCollections(userData))
+    }, [userData])
+
+    // подписываемся на загрузку коллекций
+    const allCollections = useSelector(state => state?.collection?.FindCollections?.payload)
+    // console.log('allCollections: ', allCollections)
+
+    // управление модальным окном коллекции
+    const [openCarousel, setOpenCarousel] = useState(false)
+    const handleOpenCarousel = () => setOpenCarousel(true)
+    const handleCloseCarousel = () => setOpenCarousel(false)
+
+
+    // управление модальным окном создания коллекции
+    const [openCreateCollection, setOpenCreateCollection] = useState(false)
+    const handleOpenCreateCollection = () => {
+        dispatch(actionFeedFindOne([myId], -1, 100, 'PostListCollection', 'COLLECTIONS', 0)).then(res => {
+            if (res) setOpenCreateCollection(true)
+        })
+
+    }
+    const handleCloseCreateCollection = () => setOpenCreateCollection(false)
+
+
+    function openCollection(id) {
+        dispatch(actionFullFinCollectionOne(id)).then(res => res && handleOpenCarousel())
+    }
+
+
+    return (
+        <CollectionContext.Provider value={[openCreateCollection, setOpenCreateCollection]}>
+            <React.Fragment>
+                <List sx={{ width: '100%', bgcolor: 'background.paper', display: 'flex', flexDirection: 'row', }} className='collection-scroll'>
+                    {/* иконка создания коллекций */}
+                    {userData === myId &&
+                        <ListItem
+                            alignItems="flex-start"
+                            sx={{
+                                flexDirection: 'column',
+                                alignItems: 'center',
+                                width: 'fit-content'
+                            }}
+                        >
+                            <ListItemAvatar
+                                className='cursor-pointer'
+                                onClick={handleOpenCreateCollection}
+                            >
+                                <Avatar sx={{ width: 100, height: 100 }}>
+                                    <AddRounded
+                                        fontSize='large'
+                                        sx={{ color: '#FFF' }}
+                                    />
+                                </Avatar>
+                            </ListItemAvatar>
+
+                            <ListItemText
+                                sx={{
+                                    whiteSpace: 'nowrap',
+                                    overflow: 'hidden',
+                                    textOverflow: 'ellipsis',
+                                    textAlign: 'center',
+                                    width: 90
+                                }}
+                                primary={
+                                    <Typography
+                                        sx={{ display: 'inline' }}
+                                        component="span"
+                                        variant="body2"
+                                        color="text.primary"
+                                    >
+                                        Новая коллекция
+                                    </Typography>
+                                }
+                            />
+
+                            <Modal
+                                aria-labelledby="modal-collection"
+                                aria-describedby="modal-collection"
+                                open={openCreateCollection}
+                                onClose={handleCloseCreateCollection}
+                                closeAfterTransition
+                                slots={{ backdrop: Backdrop }}
+                                slotProps={{
+                                    backdrop: {
+                                        timeout: 500,
+                                    },
+                                }}
+                            >
+                                <Fade in={openCreateCollection}>
+                                    <Box sx={style2}>
+                                        <Stack
+                                            direction="row"
+                                            justifyContent="space-between"
+                                            alignItems="center"
+                                            sx={{
+                                                padding: '0 24px'
+                                            }}
+                                        >
+                                            <Typography
+                                                variant="h6"
+                                                color='text.primary'
+                                                textAlign='center'
+                                                gutterBottom
+                                            >
+                                                Создание коллекции
+                                            </Typography>
+
+                                            <IconButton
+                                                onClick={handleCloseCreateCollection}
+                                                sx={{
+                                                    position: 'absolute',
+                                                    top: '0px',
+                                                    right: '0px',
+                                                    fontSize: '25px',
+                                                    cursor: 'default'
+                                                }}
+                                            >
+                                                <CloseRounded className='cursor-pointer' />
+                                            </IconButton>
+                                        </Stack>
+
+                                        <Divider />
+
+                                        <CreateCollection />
+                                    </Box>
+                                </Fade>
+                            </Modal>
+                        </ListItem>}
+
+                    {/* список всех коллекций пользователя */}
+                    {allCollections?.length > 0 && allCollections?.map(item =>
+                        <ListItem
+                            alignItems="flex-start"
+                            key={item?._id}
+                            title={item?.text}
+                            sx={{
+                                flexDirection: 'column',
+                                alignItems: 'center',
+                                width: 'fit-content'
+                            }}
+                        >
+                            <ListItemAvatar>
+                                <Avatar
+                                    className='cursor-pointer'
+                                    alt={item?.text}
+                                    src={url + item?.posts[0]?.images[0]?.url}
+                                    onClick={() => openCollection(item?._id)}
+                                    sx={{ width: 100, height: 100 }}
+                                />
+                            </ListItemAvatar>
+
+                            <ListItemText
+                                sx={{
+                                    whiteSpace: 'nowrap',
+                                    overflow: 'hidden',
+                                    textOverflow: 'ellipsis',
+                                    textAlign: 'center',
+                                    width: 90
+                                }}
+                                primary={
+                                    <Typography
+                                        sx={{ display: 'inline' }}
+                                        component="span"
+                                        variant="body2"
+                                        color="text.primary"
+                                    >
+                                        {item?.text}
+                                    </Typography>
+                                }
+                            />
+
+                            <Modal
+                                aria-labelledby="modal-collection"
+                                aria-describedby="modal-collection"
+                                open={openCarousel}
+                                onClose={handleCloseCarousel}
+                                closeAfterTransition
+                                slots={{ backdrop: Backdrop }}
+                                slotProps={{
+                                    backdrop: {
+                                        timeout: 500,
+                                    },
+                                }}
+                            >
+                                <Fade in={openCarousel}>
+                                    <Box sx={style}>
+                                        <ModalCarousel />
+                                    </Box>
+                                </Fade>
+                            </Modal>
+                        </ListItem>
+                    )}
+                </List>
+
+                {(userData === myId || allCollections?.length > 0) && <Divider sx={{ marginTop: 1 }} />}
+            </React.Fragment>
+        </CollectionContext.Provider>
+    )
+}

+ 37 - 0
js/Project/project/src/components/user/collections_carousel.js

@@ -0,0 +1,37 @@
+import { useSelector } from 'react-redux';
+
+import Carousel from 'react-material-ui-carousel'
+
+import { Comments } from '../post';
+
+
+export default function ModalCarousel() {
+
+    // отслеживаем изменения в карусели
+    const collection = useSelector(state => state?.collection?.FindCollectionOne?.payload)
+    console.log('collection: ', collection)
+
+
+
+    return (
+        <Carousel
+            autoPlay={false}
+            cycleNavigation={false}
+            animation={"slide"}
+            indicatorContainerProps={{
+                style: {
+                    marginTop: '-50px',
+                    zIndex: 999,
+                    position: 'inherit'
+                }
+            }}
+            className='myCarousel'
+            sx={{
+                width: '100%',
+                height: '100%'
+            }}
+        >
+            тут будет поста
+        </Carousel>
+    )
+}

+ 195 - 0
js/Project/project/src/components/user/collections_create.js

@@ -0,0 +1,195 @@
+import { url } from '../../App';
+
+import { useState, useEffect, useContext } from 'react';
+
+import { useDispatch, useSelector } from 'react-redux';
+
+
+import {
+    Container,
+    Stack,
+    TextField,
+    Typography,
+    ImageList,
+    ImageListItem,
+    Box,
+    Checkbox,
+    FormControlLabel
+} from "@mui/material";
+import Button from '@mui/material-next/Button';
+
+import { CollectionsBookmarkRounded } from '@mui/icons-material'
+
+import {
+    actionFullCreateCollection,
+    actionFullFindCollections
+} from '../../redux/thunks';
+
+import { CollectionContext } from './collections';
+import { dark } from '@mui/material/styles/createPalette';
+
+
+// стили чекбокса
+const iconStyle = {
+    position: 'absolute',
+    right: '-7px',
+    top: '-7px',
+    color: '#6750a4',
+    '&.Mui-checked': {
+        color: '#6750a4',
+    }
+}
+
+export default function CreateCollection() {
+    const dispatch = useDispatch()
+
+    // контекст управления модальным окном создания коллекции
+    const [openCreateCollection, setOpenCreateCollection] = useContext(CollectionContext)
+
+    const userPosts = useSelector(state => state?.collection?.PostListCollection?.payload)
+    // console.log('userPosts: ', userPosts)
+
+    // состояние данных для создания новой коллекции
+    const [collection, setCollection] = useState({ text: '', posts: [] })
+
+    // влючение/отключение кнопки создания коллекции
+    const [collectionButton, setCollectionButton] = useState(true)
+
+    // проверка заполненности данных при создании коллекции
+    useEffect(() => {
+        if (collection.text !== '' && collection.posts.length !== 0) {
+            setCollectionButton(false)
+        } else {
+            setCollectionButton(true)
+        }
+    }, [collection])
+
+    // состояние чекбоксов постов
+    const [checkedItems, setCheckedItems] = useState({});
+
+    // отслеживание чекбокса на поста
+    const handleCheckboxChange = (itemId) => {
+        setCheckedItems(prevState => ({ ...prevState, [itemId]: !prevState[itemId] }))
+    }
+
+    // дбавление поста в список для коллекции
+    useEffect(() => {
+        let newPosts = []
+        for (const [key, value] of Object.entries(checkedItems)) {
+            if (value) {
+                newPosts.push({ _id: key })
+            }
+        }
+        setCollection({ ...collection, posts: [...newPosts] })
+    }, [checkedItems])
+
+
+    // запрос на создание коллекции
+    function createCollection() {
+        if (!collectionButton) {
+            dispatch(actionFullCreateCollection(collection)).then(res => {
+                if (res) {
+                    setOpenCreateCollection(false)
+
+                    dispatch(actionFullFindCollections(res?.owner?._id))
+                }
+            })
+        }
+    }
+
+
+
+    return (
+        <Stack spacing={2}>
+            <Container>
+                <TextField
+                    label='Введите название новой коллекции'
+                    variant='standard'
+                    size='small'
+                    fullWidth
+                    onChange={e => setCollection({ ...collection, text: e.target.value })}
+                />
+            </Container>
+
+            <Container
+                onClick={createCollection}
+                sx={{
+                    margin: '0 auto',
+                    width: 'fit-content'
+                }}
+            >
+                <Button
+                    variant="filledTonal"
+                    size="small"
+                    width='50%'
+                    sx={{
+                        paddingLeft: '50px',
+                        paddingRight: '50px'
+                    }}
+                    disabled={collectionButton}
+                    startIcon={
+                        <CollectionsBookmarkRounded />
+                    }
+                >
+                    Создать коллекцию
+                </Button>
+            </Container>
+
+            <Container
+                className='recommendedBox'
+                sx={{
+                    minHeight: 'fit-content',
+                    maxHeight: 550,
+                    overflowY: 'auto',
+                    overflowX: 'hidden'
+                }}
+            >
+                <ImageList
+                    cols={3}
+                    rowHeight={'auto'}
+                    sx={{
+                        width: "102%",
+                        minHeight: "100%"
+                    }}
+                >
+                    {userPosts && userPosts?.map((item) => (
+                        <ImageListItem
+                            key={item?._id}
+                            sx={{
+                                overflowX: 'hidden',
+                            }}
+                        >
+                            {/* чекбокс для добавления поста в коллекцию */}
+                            <FormControlLabel
+                                control={
+                                    <Checkbox
+                                        checked={!!checkedItems[item?._id]}
+                                        onChange={() => handleCheckboxChange(item?._id)}
+                                        size="small"
+                                        sx={iconStyle}
+                                    />
+                                }
+                            />
+
+                            {/* пост в ленте */}
+                            <Box
+                                sx={{
+                                    width: '100%',
+                                    paddingTop: '100%',
+                                    backgroundSize: 'cover',
+                                    backgroundColor: 'black',
+                                    backgroundImage: `url(/images/noPhoto.png)`,
+                                    backgroundRepeat: 'no-repeat',
+                                }}
+                                style={item?.images && (
+                                    item?.images[0]?.url &&
+                                    { backgroundImage: `url(${url + item?.images[0]?.url})` }
+                                )}
+                            />
+                        </ImageListItem>
+                    ))}
+                </ImageList>
+            </Container>
+        </Stack>
+    )
+}

+ 3 - 1
js/Project/project/src/components/user/index.js

@@ -12,8 +12,8 @@ import {
 import { actionFullUserFindOne } from '../../redux/thunks';
 
 import BasicCard from './userData';
-
 import { СStandardImageList } from './gallery';
+import Collections from './collections';
 
 import './style.scss'
 
@@ -41,6 +41,8 @@ function User({ user = {}, loadUser }) {
 
             <Divider />
 
+            <Collections userData={userId} />
+
             <React.Fragment>
                 <СStandardImageList />
             </React.Fragment>

+ 22 - 0
js/Project/project/src/components/user/style.scss

@@ -10,8 +10,30 @@
     .MuiCardContent-root .user-data-container {
         justify-content: space-between;
     }
+
+}
+
+.collection-scroll {
+    overflow-x: auto;
 }
 
+.collection-scroll::-webkit-scrollbar {
+    height: 8px;
+}
+
+.collection-scroll::-webkit-scrollbar-track {
+    -webkit-box-shadow: 5px 5px 5px -5px rgba(34, 60, 80, 0.2) inset;
+    background-color: #f9f9fd;
+    border-radius: 8px;
+}
+
+.collection-scroll::-webkit-scrollbar-thumb {
+    border-radius: 8px;
+    background-color: #e8def8;
+}
+
+
+
 @media(max-width: 768px) {
     .user-container {
         margin: 10px;

+ 69 - 1
js/Project/project/src/redux/action.js

@@ -179,7 +179,7 @@ export const actionfindPosts = () => actionPromise('PROMISE', 'PostsFind', gql(`
 
 
 // Запрос на поиск ленты постов для юзера
-export const actionFeedFindOne = (arr, sortOne, limitOne, promiseName = 'Feed', skipOne = 0) => actionPromise('FEED', promiseName, gql(`query FeedFindOne ($feedOne: String){
+export const actionFeedFindOne = (arr, sortOne, limitOne, promiseName = 'Feed', promiseType = 'FEED', skipOne = 0) => actionPromise(promiseType, promiseName, gql(`query FeedFindOne ($feedOne: String){
 	PostFind(query: $feedOne){
             _id createdAt title text owner{
               _id login avatar{
@@ -424,4 +424,72 @@ export const actionSearchComment = (params, name) => actionPromise('SEARCH', nam
   }
 }`, {
   findComment: JSON.stringify(params)
+}))
+
+
+
+// запрос на создание коллекцции
+export const actionCreateCollection = params => actionPromise('COLLECTIONS', 'CreateCollection', gql(`mutation CreateCollection($createCollection: CollectionInput){
+  CollectionUpsert(collection: $createCollection){
+    _id text posts {
+      _id
+    }
+    owner {
+      _id login
+    }
+  }
+}`, {
+  createCollection: params
+  // { text: "third collection", posts: [{ _id: "642959c340044d3f605c28c4" }, { _id: "6426f7cd40044d3f605c28a2" }, { _id: "6426f8a540044d3f605c28a7" }] }
+}))
+
+
+// запрос на поиск всех коллекций одного юзера
+export const actionFindCollections = (id, sortOne) => actionPromise('COLLECTIONS', 'FindCollections', gql(`query FindCollections($findCollections: String){
+  CollectionFind(query: $findCollections){
+    _id text owner {
+      _id login
+    }
+posts {
+  _id images {
+    _id url
+  }
+}
+  }
+}`, {
+  findCollections: JSON.stringify([{ ___owner: id }, { sort: [{ _id: sortOne }] }])
+}))
+
+
+// запрос на поиск конкретной коллекции
+export const actionFindCollectionOne = id => actionPromise('COLLECTIONS', 'FindCollectionOne', gql(`query FindCollectionOne($findCollectionOne: String){
+  CollectionFindOne(query:$findCollectionOne){
+    _id text owner {
+      _id login
+    }
+posts {
+  _id createdAt title text likesCount
+    images {
+      url
+    }
+    comments {
+      _id createdAt text answers {
+        _id createdAt text likes {
+          _id
+        }
+        likesCount owner {
+          _id login nick  avatar {
+            _id url
+          }
+        }
+      }
+      likesCount
+    }
+    owner {
+      _id login nick
+    }
+}
+  }
+}`, {
+  findCollectionOne: JSON.stringify([{ _id: id }])
 }))

+ 2 - 1
js/Project/project/src/redux/reducers.js

@@ -77,7 +77,8 @@ export const reducers = {
     auth: localStoredReducer(authReducer, 'auth'),
     promise: localStoredReducer(createReducer('PROMISE'), 'promise'),
     feed: localStoredReducer(createReducer('FEED'), 'feed'),
-    search: localStoredReducer(createReducer('SEARCH'), 'search')
+    search: localStoredReducer(createReducer('SEARCH'), 'search'),
+    collection: localStoredReducer(createReducer('COLLECTIONS'), 'collection')
 }
 
 export const totalReducer = combineReducers(reducers)

+ 31 - 1
js/Project/project/src/redux/thunks.js

@@ -22,7 +22,10 @@ import {
     actionSearchUser,
     actionSearchPost,
     actionSearchComment,
-    actionPassChange
+    actionPassChange,
+    actionCreateCollection,
+    actionFindCollections,
+    actionFindCollectionOne
 } from "./action"
 
 const url = 'http://hipstagram.node.ed.asmer.org.ua/'
@@ -313,5 +316,32 @@ export const actionFullSearch = param =>
         // запускаем все акшоны
         const result = await Promise.all(searchPromises?.map(item => dispatch(item)))
 
+        return result
+    }
+
+
+// запрос на поиск всех коллекций конкретного пользователя
+export const actionFullFindCollections = id =>
+    async dispatch => {
+        const result = await dispatch(actionFindCollections(id, -1))
+
+        return result
+    }
+
+
+// запрос на поиск конкретной коллекции
+export const actionFullFinCollectionOne = id =>
+    async dispatch => {
+        const result = await dispatch(actionFindCollectionOne(id))
+
+        return result
+    }
+
+
+// запрос на создание коллекции
+export const actionFullCreateCollection = params =>
+    async dispatch => {
+        const result = await dispatch(actionCreateCollection(params))
+
         return result
     }