@@ -1,10 +1,23 @@ -import { useEffect, useMemo, useState } from 'react' +import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react' import { ImageWithState } from './types' +import { Message } from '@/src/shared/lib/types/model' -export const useImagesLibrary = (images: string[], current: string | null, reverse: boolean) => { +export const useImagesLibrary = ( + images: string[], + current: string | null, + reverse: boolean, + setModal: Dispatch> +) => { const [imagesWithState, setImagesWithState] = useState(getImagesWithState(images)) const [initialCount, setInitialCount] = useState(0) + const esc = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault() + setModal(false) + } + } + function getImagesWithState(images: string[]) { const imagesWithState = images.map((image) => ({ image, @@ -42,6 +55,11 @@ export const useImagesLibrary = (images: string[], current: string | null, rever else setImagesWithState([...getImagesWithState(filteredMessages), ...imagesWithState]) }, [images]) + useEffect(() => { + window.addEventListener('keydown', esc) + return () => window.removeEventListener('keydown', esc) + }, []) + return { imagesWithState, currentImageIndex, @@ -6,6 +6,7 @@ import { ImageWithState } from './types' export const useLibrarySwiper = (onSlideFalse: ((...args: any) => any) | undefined, reverse: boolean) => { const [swiper, setSwiper] = useState(null) + const keydown = (e: KeyboardEvent) => { if (e.key === 'ArrowRight') { e.preventDefault() @@ -1,15 +1,17 @@ -import React, { Dispatch, SetStateAction, useEffect, useState } from 'react' +import React, { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react' import Image from 'next/image' import styles from './modal-styles.module.scss' import { ArrowDropDown } from '@mui/icons-material' -import { c, Loader } from '@/src/shared' +import { c, Loader, TooltipCustom } from '@/src/shared' import { useImagesLibrary } from '../model' import { Swiper, SwiperSlide } from 'swiper/react' import 'swiper/css' -import { Swiper as SwiperCore } from 'swiper' import { useLibrarySwiper } from '../model/use-swiper' +import { ImageIcons } from '@/src/widgets/messages' +import { Message } from '@/src/shared/lib/types/model' +import { Typography } from '@mui/material' interface IProps { modal: boolean @@ -18,14 +20,17 @@ interface IProps { setCurrent?: (value: string | null) => void onSlideFalse?: (...args: any) => any reverse?: boolean - images: string[] + images: Message[] } export default function FullScreenModal({ modal, setModal, current, images, onSlideFalse, reverse = false }: IProps) { + const filteredMessages = useMemo(() => images.map((message) => message.file as string), [images]) + const { imagesWithState, updateImageState, currentImageIndex, initialCount } = useImagesLibrary( - images, + filteredMessages, current, - reverse + reverse, + setModal ) const { swiper, setSwiper, slideNext, slidePrev } = useLibrarySwiper(onSlideFalse, reverse) @@ -96,28 +101,49 @@ export default function FullScreenModal({ modal, setModal, current, images, onSl {imagesWithState.map(({ image, state }, index) => ( {!state && } - {!image.includes('.svg') ? ( - e.stopPropagation()} - onLoadingComplete={() => { - updateImageState(image, true) - }} - loading='lazy' - src={image} - width={'1500'} - height={'1500'} - alt='К сожалению, изображение не загрузилось' - className={styles.image_style} - /> - ) : ( - e.stopPropagation()} - onLoad={() => updateImageState(image, true)} - alt='К сожалению, изображение не загрузилось' - className={styles.image_style} +
+ - )} + {!image.includes('.svg') ? ( + e.stopPropagation()} + onLoadingComplete={() => { + updateImageState(image, true) + }} + loading='lazy' + src={image} + width={'1500'} + height={'1500'} + alt='К сожалению, изображение не загрузилось' + /> + ) : ( + e.stopPropagation()} + onLoad={() => updateImageState(image, true)} + alt='К сожалению, изображение не загрузилось' + className={styles.image_style} + /> + )} + 30 ? images[index].content : ''} + > +
+ + {images[index].content.length < 30 + ? images[index].content + : images[index].content.slice(0, 30) + '...'} + +
+
+
))} @@ -43,6 +43,29 @@ } } +.tooltip { + z-index: 1000; + position: absolute; + bottom: 10px; + left: 50%; + transform: translateX(-50%); + + max-width: 100%; + + &__content { + padding: 5px 10px; + border-radius: 10px; + background-color: rgba(0, 0, 0, 0.5); + } + + &__text { + width: max-content; + text-align: center; + font-size: 14px; + color: white !important; + } +} + .image_block { position: absolute; padding: 0 20px; @@ -67,12 +90,18 @@ width: auto; max-width: 70vw; height: 100%; - object-fit: contain; + max-height: max-content; + position: relative; + user-select: none; + img { + width: 100%; + height: 100%; + object-fit: contain; + user-select: none; - @media screen and (max-width: 1024px) { - width: auto; - max-width: 70vw; - height: auto; + @media (max-width: 1000px) { + height: max-content; + } } } @@ -5,7 +5,7 @@ import axios from 'axios' import { UniqInput } from '@/src/main/components/uniq_input' import { useAppSelector } from '@/src/main/store/store' import { ChatProps } from '@/src/shared/lib/types/model' -import { ChatMessagesList } from '@/src/widgets/messages/chat-messages-list' +import { ChatMessagesList } from '@/src/widgets/messages/ui/chat-messages-list' import 'intro.js/introjs.css' @@ -64,7 +64,13 @@ function Chat({ spacing={0} sx={{ borderRadius: desktop ? '15px' : '13px', - backgroundColor: desktop ? (theme === 'light' ? 'white' : '#151518') : theme === 'light' ? 'white' : '#151518', + backgroundColor: desktop + ? theme === 'light' + ? 'white' + : '#151518' + : theme === 'light' + ? 'white' + : '#151518', padding: desktop ? 4 : 1.5, paddingTop: 0, paddingBottom: 2, @@ -25,7 +25,7 @@ import { getTypeDevice } from '@/src/shared/lib/helpers' import { getDeviceOs } from '@/src/shared/lib/helpers/get-type-device' import { useShowData } from '@/src/shared/lib/hooks' import { ArrowDownScroll } from '@/src/shared/ui/icon-components/scroll-down-arrow' -import { ImageMessagesList } from '@/src/widgets/messages/image-messages-list' +import { ImageMessagesList } from '@/src/widgets/messages/ui/image-messages-list' export async function getServerSideProps(context: any): Promise<{ props: any }> { const deviceType = getTypeDevice(context) @@ -3,10 +3,11 @@ import { Tooltip } from '@mui/material' import { useAppSelector } from '@/src/main/store/store' -import styles from './styles.module.scss' interface Props { title?: string children: React.ReactNode + className?: string + maxWidth?: string placement?: | 'bottom-end' | 'bottom-start' @@ -21,11 +22,17 @@ interface Props { | 'top-start' | 'top' } -export const TooltipCustom: React.FC = ({ children, title, placement }) => { +export const TooltipCustom: React.FC = ({ children, title, placement, className, maxWidth }) => { const theme = useAppSelector((state) => state.theme.theme) return ( e.stopPropagation()} + PopperProps={{ + onClick(e) { + e.stopPropagation() + }, + }} componentsProps={{ tooltip: { sx: { @@ -33,6 +40,7 @@ export const TooltipCustom: React.FC = ({ children, title, placement }) = color: '#7F7DF3', fontSize: 15, borderRadius: '10px', + maxWidth: maxWidth || 'auto', padding: '10px 13px', '& .MuiTooltip-arrow': { color: theme === 'light' ? '#E8E8FA' : '#4B4B4B', @@ -41,6 +49,7 @@ export const TooltipCustom: React.FC = ({ children, title, placement }) = }, }, }} + className={className} title={title} arrow placement={placement} @@ -0,0 +1 @@ +export * from './use-image-icons' \ No newline at end of file @@ -0,0 +1,39 @@ +import { useShowData } from '@/src/shared' +import { useState } from 'react' + +export function useImageIcons() { + const [iconsMenu, setIconsMenu] = useState('') + const { error, showError, isError } = useShowData() + + const toggleMenu = (uid: string) => { + if (iconsMenu === uid) { + setIconsMenu('') + } else { + setIconsMenu(uid) + } + } + + const downloadFile = (url: string | null, content: string | undefined) => { + if (url) { + fetch(url) + .then((response) => response.blob()) + .then((blob) => { + const url = window.URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + // const image_name = '123' + const image_name = content?.replaceAll(' ', '_').substring(0, 25) + link.setAttribute('download', `${image_name}`) + document.body.appendChild(link) + link.click() + }) + .catch((error) => { + showError('Что-то пошло не так', true) + }) + } else { + showError('Изображение не найдено', true) + } + } + + return { iconsMenu, toggleMenu, downloadFile } +} @@ -7,13 +7,13 @@ export const useMessages = (messages: Message[]) => { const [loaded, setLoaded] = useState(false) // типизация ну супер кривая))) - const computedLibraryImages = useMemo(() => { + const computedLibraryImages = useMemo(() => { return messages .map((el) => { if (el.file && (el.file as any).includes('.zip')) return null - return el.file as string + return el }) - .filter((el) => el !== null) as string[] + .filter((el) => el !== null) }, [messages]) return { @@ -4,7 +4,7 @@ import { useSession } from 'next-auth/react' import { Message } from '@/src/shared/lib/types/model' import { ArrowDownScroll } from '@/src/shared/ui/icon-components/scroll-down-arrow' -import { IsNextDay } from '@/src/widgets/messages/is-next-day' +import { IsNextDay } from '@/src/widgets/messages/ui/is-next-day' import { PreviewView } from '@/src/widgets/messages/message-components/preview-view' import { ImageModal } from '@/src/features/image-modal' import { ClientOnly } from '@/src/shared' @@ -0,0 +1,105 @@ +import { useAppSelector } from '@/src/main/store/store' +import { TooltipCustom, useShowData } from '@/src/shared' +import { Grow, Box } from '@mui/material' +import { useImageIcons } from '../model' + +export interface ImageIconsProps { + uid: string + url: string | null + content?: string +} + +export const ImageIcons = ({ uid, url, content }: ImageIconsProps) => { + const theme = useAppSelector((state) => state.theme.theme) + + const { toggleMenu, downloadFile, iconsMenu } = useImageIcons() + + return ( + <> + { + e.stopPropagation() + toggleMenu(uid) + }} + width='35' + height='35' + viewBox='0 0 35 35' + fill='none' + xmlns='http://www.w3.org/2000/svg' + > + + + + + + + + { + window.open(`${url ? url : ''}`, '_blank') + }} + width='15' + height='15' + viewBox='0 0 15 15' + fill='none' + xmlns='http://www.w3.org/2000/svg' + > + + + + + { + downloadFile(url, content) + }} + width='15' + height='15' + viewBox='0 0 15 15' + fill='none' + xmlns='http://www.w3.org/2000/svg' + > + + + + + + + ) +} @@ -12,7 +12,8 @@ import { Message } from '@/src/shared/lib/types/model' import styles from './image-messages-list.module.scss' import { createPortal } from 'react-dom' -import { useMessages } from './model/use-messages' +import { useMessages } from '../model/use-messages' +import { ImageIcons } from './image-icons' interface MessagesList { device: 'mobile' | 'desktop' @@ -25,128 +26,10 @@ export const ImageMessagesList: React.FC = memo(({ device, images, const { error, showError, isError } = useShowData() const [modal, setModal] = useState(false) const theme = useAppSelector((state) => state.theme.theme) - const [iconsMenu, setIconsMenu] = React.useState('') + const { chosenImage, setChosenImage, loaded, setLoaded, computedLibraryImages } = useMessages(images) - const downloadFile = (url: string | null, content: string | undefined) => { - if (url) { - fetch(url) - .then((response) => response.blob()) - .then((blob) => { - const url = window.URL.createObjectURL(blob) - const link = document.createElement('a') - link.href = url - // const image_name = '123' - const image_name = content?.replaceAll(' ', '_').substring(0, 25) - link.setAttribute('download', `${image_name}`) - document.body.appendChild(link) - link.click() - }) - .catch((error) => { - showError('Что-то пошло не так', true) - }) - } else { - showError('Изображение не найдено', true) - } - } - - const toggleMenu = (uid: string) => { - if (iconsMenu === uid) { - setIconsMenu('') - } else { - setIconsMenu(uid) - } - } - - const ImageIcons = ({ uid, url, content }: { uid: string; url: string | null; content: string | undefined }) => { - return ( - <> - toggleMenu(uid)} - width='35' - height='35' - viewBox='0 0 35 35' - fill='none' - xmlns='http://www.w3.org/2000/svg' - > - - - - - - - - { - window.open(`${url ? url : ''}`, '_blank') - }} - width='15' - height='15' - viewBox='0 0 15 15' - fill='none' - xmlns='http://www.w3.org/2000/svg' - > - - - - - { - downloadFile(url, content) - }} - width='15' - height='15' - viewBox='0 0 15 15' - fill='none' - xmlns='http://www.w3.org/2000/svg' - > - - - - - - - ) - } - return ( <> @@ -155,7 +38,7 @@ export const ImageMessagesList: React.FC = memo(({ device, images, images={computedLibraryImages} modal={modal} onSlideFalse={() => getMessagesPagination && getMessagesPagination()} - setModal={setModal} + setModal={setModal} reverse={device === 'desktop'} current={chosenImage} />, @@ -0,0 +1 @@ +export * from './image-icons' \ No newline at end of file @@ -0,0 +1,2 @@ +export * from './ui' +export * from './model' \ No newline at end of file