From aefc9b525cf5f6dabb647a03e4fde50c8cdeaa5d Mon Sep 17 00:00:00 2001 From: dougfabris Date: Mon, 9 Oct 2023 13:20:41 -0300 Subject: [PATCH 01/21] wip --- apps/meteor/app/theme/client/main.css | 20 ++++ .../ui/client/views/app/photoswipeContent.ts | 2 +- .../meteor/client/components/ImageGallery.tsx | 107 ++++++++++++++++++ .../client/providers/MeteorProvider.tsx | 2 + apps/meteor/package.json | 1 + yarn.lock | 17 +++ 6 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 apps/meteor/client/components/ImageGallery.tsx diff --git a/apps/meteor/app/theme/client/main.css b/apps/meteor/app/theme/client/main.css index 6c0a20d844bb..534b03578006 100644 --- a/apps/meteor/app/theme/client/main.css +++ b/apps/meteor/app/theme/client/main.css @@ -26,3 +26,23 @@ @import './rocketchat.font.css'; @import './mentionLink.css'; @import '../../../node_modules/@rocket.chat/fuselage/dist/fuselage.css'; + +.swiper-container { + position: absolute; + z-index: 99; + top: 0; + + width: 100%; + height: 100%; + + background: rgba(0, 0, 0, 0.5); +} + +.swiper { + width: 100%; + height: 100%; +} + +.swiper-slide { + overflow: hidden; +} diff --git a/apps/meteor/app/ui/client/views/app/photoswipeContent.ts b/apps/meteor/app/ui/client/views/app/photoswipeContent.ts index 5c76a6b15d18..642a8e53b986 100644 --- a/apps/meteor/app/ui/client/views/app/photoswipeContent.ts +++ b/apps/meteor/app/ui/client/views/app/photoswipeContent.ts @@ -199,5 +199,5 @@ const createEventListenerFor = }; Meteor.startup(() => { - $(document).on('click', '.gallery-item', createEventListenerFor('.gallery-item')); + // $(document).on('click', '.gallery-item', createEventListenerFor('.gallery-item')); }); diff --git a/apps/meteor/client/components/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery.tsx new file mode 100644 index 000000000000..cddadc9769c1 --- /dev/null +++ b/apps/meteor/client/components/ImageGallery.tsx @@ -0,0 +1,107 @@ +// Import Swiper React components + +import React, { useEffect, useState } from 'react'; +import { Navigation, Zoom } from 'swiper'; +import { Swiper, SwiperSlide } from 'swiper/react'; + +// Import Swiper styles +import 'swiper/swiper.css'; +import 'swiper/modules/navigation/navigation.min.css'; +import 'swiper/modules/zoom/zoom.min.css'; +// import './ImageGallery.css'; + +const ImageGallery = () => { + const [isOpen, setIsOpen] = useState(false); + const [images, setImages] = useState([]); + const [swiperInst, setSwiperInst] = useState(null); + + useEffect(() => { + document.addEventListener('click', (event) => { + const nodes = document.querySelectorAll('.gallery-item'); + console.log(nodes); + if (event.target.classList.contains('gallery-item')) { + console.log('isEvent'); + setIsOpen(true); + const sources = [...nodes].map((node) => node.src); + setImages(sources); + console.log(swiperInst); + // swiperInst.update(); + } + // console.log(event.target.classList.contains('gallery-item')); + }); + }, [swiperInst]); + + console.log(images); + + return ( + isOpen && ( +
+ console.log('slide change')} + onSwiper={(swiper) => console.log(swiper)} + modules={[Navigation, Zoom]} + onInit={(swiper) => { + console.log(swiper); + setSwiperInst(swiper); + console.log('init'); + const nodes = document.querySelectorAll('.gallery-item'); + console.log(nodes); + }} + > + {images.map((image) => ( + + ))} + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+ ) + ); +}; + +export default ImageGallery; \ No newline at end of file diff --git a/apps/meteor/client/providers/MeteorProvider.tsx b/apps/meteor/client/providers/MeteorProvider.tsx index dadb47caead4..aee47374eac0 100644 --- a/apps/meteor/client/providers/MeteorProvider.tsx +++ b/apps/meteor/client/providers/MeteorProvider.tsx @@ -1,6 +1,7 @@ import type { FC } from 'react'; import React from 'react'; +import ImageGallery from '../components/ImageGallery'; import { OmnichannelRoomIconProvider } from '../components/RoomIcon/OmnichannelRoomIcon/provider/OmnichannelRoomIconProvider'; import ActionManagerProvider from './ActionManagerProvider'; import AuthorizationProvider from './AuthorizationProvider'; @@ -33,6 +34,7 @@ const MeteorProvider: FC = ({ children }) => ( + diff --git a/apps/meteor/package.json b/apps/meteor/package.json index c1014a749158..edbd08bf3242 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -418,6 +418,7 @@ "strict-uri-encode": "^2.0.0", "string-strip-html": "^7.0.3", "suretype": "~2.4.1", + "swiper": "^9.3.2", "tar-stream": "^1.6.2", "textarea-caret": "^3.1.0", "tinykeys": "^1.4.0", diff --git a/yarn.lock b/yarn.lock index 7d75c388dc29..8408fa1f2e27 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9019,6 +9019,7 @@ __metadata: stylelint-order: ^5.0.0 supertest: ^6.2.3 suretype: ~2.4.1 + swiper: ^9.3.2 tar-stream: ^1.6.2 template-file: ^6.0.1 textarea-caret: ^3.1.0 @@ -35740,6 +35741,13 @@ __metadata: languageName: node linkType: hard +"ssr-window@npm:^4.0.2": + version: 4.0.2 + resolution: "ssr-window@npm:4.0.2" + checksum: df182600927f4f3225224cf8c02338ea637c9750519505bbfb9a9236741a2a7ec088386fb948bca7b447b8303d9109e7dc7672e3de041c79ac2a0e03665af7d2 + languageName: node + linkType: hard + "ssri@npm:^6.0.1": version: 6.0.2 resolution: "ssri@npm:6.0.2" @@ -36686,6 +36694,15 @@ __metadata: languageName: node linkType: hard +"swiper@npm:^9.3.2": + version: 9.3.2 + resolution: "swiper@npm:9.3.2" + dependencies: + ssr-window: ^4.0.2 + checksum: 9ccb6a0ef67d71ac780d7d1f3b2c2daf6f1846d3df8810aff6dcb8501c87b7769540a869cdd83eb902e5278396e5379cb32d9ed397234f7700ef55f0209f4592 + languageName: node + linkType: hard + "symbol-tree@npm:^3.2.4": version: 3.2.4 resolution: "symbol-tree@npm:3.2.4" From 66afd5012be5c13ec889e7509224e8375a8202f2 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 11 Oct 2023 12:02:04 -0300 Subject: [PATCH 02/21] feat: image gallery with swipper --- apps/meteor/app/theme/client/main.css | 1 + .../meteor/client/components/ImageGallery.tsx | 139 +++++++----------- .../client/providers/MeteorProvider.tsx | 2 - .../views/room/providers/RoomProvider.tsx | 14 +- 4 files changed, 65 insertions(+), 91 deletions(-) diff --git a/apps/meteor/app/theme/client/main.css b/apps/meteor/app/theme/client/main.css index 534b03578006..2ebace9f5bf5 100644 --- a/apps/meteor/app/theme/client/main.css +++ b/apps/meteor/app/theme/client/main.css @@ -36,6 +36,7 @@ height: 100%; background: rgba(0, 0, 0, 0.5); + overflow: hidden; } .swiper { diff --git a/apps/meteor/client/components/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery.tsx index cddadc9769c1..29b08753279c 100644 --- a/apps/meteor/client/components/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery.tsx @@ -1,107 +1,70 @@ // Import Swiper React components -import React, { useEffect, useState } from 'react'; -import { Navigation, Zoom } from 'swiper'; -import { Swiper, SwiperSlide } from 'swiper/react'; +import { IconButton, Throbber } from '@rocket.chat/fuselage'; +import React, { useEffect, useMemo, useState } from 'react'; +import { Keyboard, Navigation, Zoom, A11y } from 'swiper'; +import { type SwiperClass, Swiper, SwiperSlide } from 'swiper/react'; // Import Swiper styles import 'swiper/swiper.css'; import 'swiper/modules/navigation/navigation.min.css'; +import 'swiper/modules/keyboard/keyboard.min.css'; import 'swiper/modules/zoom/zoom.min.css'; -// import './ImageGallery.css'; +import { useRecordList } from '../hooks/lists/useRecordList'; +import { useRoom } from '../views/room/contexts/RoomContext'; +import { useFilesList } from '../views/room/contextualBar/RoomFiles/hooks/useFilesList'; -const ImageGallery = () => { - const [isOpen, setIsOpen] = useState(false); - const [images, setImages] = useState([]); - const [swiperInst, setSwiperInst] = useState(null); +const ImageGallery = ({ url, onClose }: { url: string; onClose: () => void }) => { + const room = useRoom(); + + const [images, setImages] = useState(); + const [swiperInst, setSwiperInst] = useState(); + const [currentSlide, setCurrentSlide] = useState(); + + const { filesList } = useFilesList(useMemo(() => ({ rid: room._id, type: 'image', text: '' }), [room._id])); + const { phase, items: filesItems } = useRecordList(filesList); useEffect(() => { - document.addEventListener('click', (event) => { - const nodes = document.querySelectorAll('.gallery-item'); - console.log(nodes); - if (event.target.classList.contains('gallery-item')) { - console.log('isEvent'); - setIsOpen(true); - const sources = [...nodes].map((node) => node.src); - setImages(sources); - console.log(swiperInst); - // swiperInst.update(); - } - // console.log(event.target.classList.contains('gallery-item')); - }); - }, [swiperInst]); + const list = [...filesItems].reverse(); - console.log(images); + if (phase === 'resolved') { + setImages(list.map((item) => item.url || '').filter(Boolean)); + setCurrentSlide(list.findIndex((item) => url.includes(item._id))); + } + return () => swiperInst?.update(); + }, [filesItems, phase, swiperInst, url]); + + if (phase === 'loading' || currentSlide === undefined) { + return null; + } return ( - isOpen && ( -
- console.log('slide change')} - onSwiper={(swiper) => console.log(swiper)} - modules={[Navigation, Zoom]} - onInit={(swiper) => { - console.log(swiper); - setSwiperInst(swiper); - console.log('init'); - const nodes = document.querySelectorAll('.gallery-item'); - console.log(nodes); - }} - > - {images.map((image) => ( - - ))} - -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
- +
+ + String(keyCode) === '27' && onClose()} + modules={[Navigation, Zoom, Keyboard, A11y]} + onInit={(swiper) => setSwiperInst(swiper)} + > + {images?.map((image, index) => ( +
- + +
+ +
-
-
- ) + ))} +
+
); }; -export default ImageGallery; \ No newline at end of file +export default ImageGallery; diff --git a/apps/meteor/client/providers/MeteorProvider.tsx b/apps/meteor/client/providers/MeteorProvider.tsx index aee47374eac0..dadb47caead4 100644 --- a/apps/meteor/client/providers/MeteorProvider.tsx +++ b/apps/meteor/client/providers/MeteorProvider.tsx @@ -1,7 +1,6 @@ import type { FC } from 'react'; import React from 'react'; -import ImageGallery from '../components/ImageGallery'; import { OmnichannelRoomIconProvider } from '../components/RoomIcon/OmnichannelRoomIcon/provider/OmnichannelRoomIconProvider'; import ActionManagerProvider from './ActionManagerProvider'; import AuthorizationProvider from './AuthorizationProvider'; @@ -34,7 +33,6 @@ const MeteorProvider: FC = ({ children }) => ( - diff --git a/apps/meteor/client/views/room/providers/RoomProvider.tsx b/apps/meteor/client/views/room/providers/RoomProvider.tsx index e19fa8136f59..1a164bca5a8f 100644 --- a/apps/meteor/client/views/room/providers/RoomProvider.tsx +++ b/apps/meteor/client/views/room/providers/RoomProvider.tsx @@ -2,11 +2,12 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { usePermission, useStream, useUserId, useRouter } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import type { ReactNode, ContextType, ReactElement } from 'react'; -import React, { useMemo, memo, useEffect, useCallback } from 'react'; +import React, { useState, useMemo, memo, useEffect, useCallback } from 'react'; import { ChatRoom, ChatSubscription } from '../../../../app/models/client'; import { RoomHistoryManager } from '../../../../app/ui-utils/client'; import { UserAction } from '../../../../app/ui/client/lib/UserAction'; +import ImageGallery from '../../../components/ImageGallery'; import { useReactiveQuery } from '../../../hooks/useReactiveQuery'; import { useReactiveValue } from '../../../hooks/useReactiveValue'; import { RoomManager } from '../../../lib/RoomManager'; @@ -36,6 +37,8 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { const isLivechatAdmin = usePermission('view-livechat-rooms'); const { t: roomType } = room ?? {}; + const [imageUrl, setImageUrl] = useState(); + // TODO: move this to omnichannel context only useEffect(() => { if (roomType !== 'l') { @@ -71,6 +74,14 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { } }, [isLivechatAdmin, queryClient, userId, rid, roomType, servedById]); + useEffect(() => { + document.addEventListener('click', (event) => { + const target = event?.target as HTMLImageElement; + if (target?.classList.contains('gallery-item')) { + setImageUrl(target?.dataset?.src); + } + }); + }, []); const subscriptionQuery = useReactiveQuery(['subscriptions', { rid }], () => ChatSubscription.findOne({ rid }) ?? null); const pseudoRoom = useMemo(() => { @@ -144,6 +155,7 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { return ( + {imageUrl && setImageUrl(null)} />} {children} From aa2c6392c91a81e65e1a624cab99f5f2fcf3204f Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 11 Oct 2023 16:14:31 -0300 Subject: [PATCH 03/21] feat: apply IconButton to navigation items --- .../meteor/client/components/ImageGallery.tsx | 22 +++++-- .../components/ImageGallery/ImageGallery.css | 57 +++++++++++++++++++ 2 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 apps/meteor/client/components/ImageGallery/ImageGallery.css diff --git a/apps/meteor/client/components/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery.tsx index 29b08753279c..bec1814b0660 100644 --- a/apps/meteor/client/components/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery.tsx @@ -1,8 +1,8 @@ -// Import Swiper React components - import { IconButton, Throbber } from '@rocket.chat/fuselage'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; import { Keyboard, Navigation, Zoom, A11y } from 'swiper'; +import type { SwiperRef } from 'swiper/react'; import { type SwiperClass, Swiper, SwiperSlide } from 'swiper/react'; // Import Swiper styles @@ -10,12 +10,15 @@ import 'swiper/swiper.css'; import 'swiper/modules/navigation/navigation.min.css'; import 'swiper/modules/keyboard/keyboard.min.css'; import 'swiper/modules/zoom/zoom.min.css'; +import './ImageGallery/ImageGallery.css'; + import { useRecordList } from '../hooks/lists/useRecordList'; import { useRoom } from '../views/room/contexts/RoomContext'; import { useFilesList } from '../views/room/contextualBar/RoomFiles/hooks/useFilesList'; const ImageGallery = ({ url, onClose }: { url: string; onClose: () => void }) => { const room = useRoom(); + const swiperRef = useRef(null); const [images, setImages] = useState(); const [swiperInst, setSwiperInst] = useState(); @@ -38,11 +41,17 @@ const ImageGallery = ({ url, onClose }: { url: string; onClose: () => void }) => return null; } - return ( + const swiperContainer = (
- + + swiperRef?.current?.swiper.slidePrev()} /> + swiperRef?.current?.swiper.slideNext()} /> void }) =>
); + return createPortal(swiperContainer, document.body); }; export default ImageGallery; diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.css b/apps/meteor/client/components/ImageGallery/ImageGallery.css new file mode 100644 index 000000000000..c584523ea04a --- /dev/null +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.css @@ -0,0 +1,57 @@ +:root { + --swiper-navigation-size: 44px; +} + +.swiper { + background-color: var(--rcx-color-surface-overlay, rgba(0, 0, 0, 0.6)); +} +.rcx-swiper-close-button, .rcx-swiper-prev-button, .rcx-swiper-next-button { + color: var(--rcx-color-font-pure-white, #fff) !important; +} +.rcx-swiper-close-button { + position: absolute; + z-index: 10; + top: 10px; + right: 10px; +} +.rcx-swiper-prev-button, +.rcx-swiper-next-button { + position: absolute; + top: 50%; + z-index: 10; + cursor: pointer; +} +.rcx-swiper-prev-button.swiper-button-disabled, +.rcx-swiper-next-button.swiper-button-disabled { + opacity: 0.35; + cursor: auto; + pointer-events: none; +} +.rcx-swiper-prev-button.swiper-button-hidden, +.rcx-swiper-next-button.swiper-button-hidden { + opacity: 0; + cursor: auto; + pointer-events: none; +} +.swiper-navigation-disabled .rcx-swiper-prev-button, +.swiper-navigation-disabled .rcx-swiper-next-button { + display: none !important; +} +.rcx-swiper-prev-button, +.swiper-rtl .rcx-swiper-next-button { + left: 10px; + right: auto; +} +.rcx-swiper-prev-button:after, +.swiper-rtl .rcx-swiper-next-button:after { + content: 'prev'; +} +.rcx-swiper-next-button, +.swiper-rtl .rcx-swiper-prev-button { + right: 10px; + left: auto; +} +.rcx-swiper-next-button:after, +.swiper-rtl .rcx-swiper-prev-button:after { + content: 'next'; +} From 32b1f3eb7691d1931e27eb24419a3ff622e8aa71 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 11 Oct 2023 17:59:42 -0300 Subject: [PATCH 04/21] refactor: move ImageGallery.tsx file and add loading state --- ...ImageGallery.css => ImageGallery.styles.css} | 2 +- .../{ => ImageGallery}/ImageGallery.tsx | 17 +++++++++++------ .../client/components/ImageGallery/index.ts | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) rename apps/meteor/client/components/ImageGallery/{ImageGallery.css => ImageGallery.styles.css} (98%) rename apps/meteor/client/components/{ => ImageGallery}/ImageGallery.tsx (83%) create mode 100644 apps/meteor/client/components/ImageGallery/index.ts diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.css b/apps/meteor/client/components/ImageGallery/ImageGallery.styles.css similarity index 98% rename from apps/meteor/client/components/ImageGallery/ImageGallery.css rename to apps/meteor/client/components/ImageGallery/ImageGallery.styles.css index c584523ea04a..cbe835f5ba3b 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.css +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.styles.css @@ -2,7 +2,7 @@ --swiper-navigation-size: 44px; } -.swiper { +.swiper-container { background-color: var(--rcx-color-surface-overlay, rgba(0, 0, 0, 0.6)); } .rcx-swiper-close-button, .rcx-swiper-prev-button, .rcx-swiper-next-button { diff --git a/apps/meteor/client/components/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx similarity index 83% rename from apps/meteor/client/components/ImageGallery.tsx rename to apps/meteor/client/components/ImageGallery/ImageGallery.tsx index bec1814b0660..7b2dd1483b71 100644 --- a/apps/meteor/client/components/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx @@ -1,4 +1,4 @@ -import { IconButton, Throbber } from '@rocket.chat/fuselage'; +import { IconButton, ModalBackdrop, Throbber } from '@rocket.chat/fuselage'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { Keyboard, Navigation, Zoom, A11y } from 'swiper'; @@ -10,11 +10,11 @@ import 'swiper/swiper.css'; import 'swiper/modules/navigation/navigation.min.css'; import 'swiper/modules/keyboard/keyboard.min.css'; import 'swiper/modules/zoom/zoom.min.css'; -import './ImageGallery/ImageGallery.css'; +import './ImageGallery.styles.css'; -import { useRecordList } from '../hooks/lists/useRecordList'; -import { useRoom } from '../views/room/contexts/RoomContext'; -import { useFilesList } from '../views/room/contextualBar/RoomFiles/hooks/useFilesList'; +import { useRecordList } from '../../hooks/lists/useRecordList'; +import { useRoom } from '../../views/room/contexts/RoomContext'; +import { useFilesList } from '../../views/room/contextualBar/RoomFiles/hooks/useFilesList'; const ImageGallery = ({ url, onClose }: { url: string; onClose: () => void }) => { const room = useRoom(); @@ -37,8 +37,13 @@ const ImageGallery = ({ url, onClose }: { url: string; onClose: () => void }) => return () => swiperInst?.update(); }, [filesItems, phase, swiperInst, url]); + const swiperLoader = ( + + + + ); if (phase === 'loading' || currentSlide === undefined) { - return null; + return createPortal(swiperLoader, document.body); } const swiperContainer = ( diff --git a/apps/meteor/client/components/ImageGallery/index.ts b/apps/meteor/client/components/ImageGallery/index.ts new file mode 100644 index 000000000000..db657797badb --- /dev/null +++ b/apps/meteor/client/components/ImageGallery/index.ts @@ -0,0 +1 @@ +export { default } from './ImageGallery'; From d3df5d4b37979af3a58483d4ea92384f6208e5df Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 11 Oct 2023 18:14:12 -0300 Subject: [PATCH 05/21] fix: remove photoswipeContent --- apps/meteor/app/ui/client/index.ts | 1 - .../ui/client/views/app/photoswipeContent.ts | 203 ------------------ apps/meteor/client/importPackages.ts | 1 - 3 files changed, 205 deletions(-) delete mode 100644 apps/meteor/app/ui/client/index.ts delete mode 100644 apps/meteor/app/ui/client/views/app/photoswipeContent.ts diff --git a/apps/meteor/app/ui/client/index.ts b/apps/meteor/app/ui/client/index.ts deleted file mode 100644 index 7a2b4aaa89b7..000000000000 --- a/apps/meteor/app/ui/client/index.ts +++ /dev/null @@ -1 +0,0 @@ -import './views/app/photoswipeContent.ts'; // without the *.ts extension, *.html gets loaded first diff --git a/apps/meteor/app/ui/client/views/app/photoswipeContent.ts b/apps/meteor/app/ui/client/views/app/photoswipeContent.ts deleted file mode 100644 index 642a8e53b986..000000000000 --- a/apps/meteor/app/ui/client/views/app/photoswipeContent.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { escapeHTML } from '@rocket.chat/string-helpers'; -import { Meteor } from 'meteor/meteor'; -import type PhotoSwipe from 'photoswipe'; -import PhotoSwipeUIDefault from 'photoswipe/dist/photoswipe-ui-default'; - -import { createAnchor } from '../../../../../client/lib/utils/createAnchor'; - -const parseLength = (x: unknown): number | undefined => { - const length = typeof x === 'string' ? parseInt(x, 10) : undefined; - return Number.isFinite(length) ? length : undefined; -}; - -const getImageSize = (src: string): Promise<[w: number, h: number]> => - new Promise((resolve, reject) => { - const img = new Image(); - - img.addEventListener('load', () => { - resolve([img.naturalWidth, img.naturalHeight]); - }); - - img.addEventListener('error', (error) => { - reject(error.error); - }); - - img.src = src; - }); - -type Slide = PhotoSwipe.Item & { description?: string; title?: string }; - -const fromElementToSlide = async (element: Element): Promise => { - if (!(element instanceof HTMLElement)) { - return null; - } - - const title = element.dataset.title || element.title; - const { description } = element.dataset; - - if (element instanceof HTMLAnchorElement) { - const src = element.dataset.src || element.href; - let w = parseLength(element.dataset.width); - let h = parseLength(element.dataset.height); - - if (w === undefined || h === undefined) { - [w, h] = await getImageSize(src); - } - - return { src, w, h, title, description }; - } - - if (element instanceof HTMLImageElement) { - let msrc: string | undefined; - let { src } = element; - let w: number | undefined = element.naturalWidth; - let h: number | undefined = element.naturalHeight; - - if (element.dataset.src) { - msrc = element.src; - src = element.dataset.src; - w = parseLength(element.dataset.width); - h = parseLength(element.dataset.height); - - if (w === undefined || h === undefined) { - [w, h] = await getImageSize(src); - } - } - - return { msrc, src, w, h, title, description }; - } - - return null; -}; - -let currentGallery: PhotoSwipe | null = null; - -const initGallery = async (items: Slide[], options: PhotoSwipe.Options): Promise => { - const anchor = createAnchor('photoswipe-root'); - - anchor.innerHTML = ``; - const [{ default: PhotoSwipe }] = await Promise.all([import('photoswipe'), import('photoswipe/dist/photoswipe.css')]); - - if (!currentGallery) { - const container = document.getElementById('pswp'); - - if (!container) { - throw new Error('Photoswipe container element not found'); - } - - currentGallery = new PhotoSwipe(container, PhotoSwipeUIDefault, items, options); - - currentGallery.listen('destroy', () => { - anchor.innerHTML = ''; - currentGallery = null; - }); - - currentGallery.init(); - } -}; - -const defaultGalleryOptions = { - bgOpacity: 0.7, - counterEl: false, - index: 0, - wheelToZoom: true, - padding: { top: 20, bottom: 40, left: 100, right: 100 }, - addCaptionHTMLFn(item: Slide, captionEl: HTMLElement): boolean { - captionEl.children[0].innerHTML = ` - ${escapeHTML(item.title ?? '')}
- ${escapeHTML(item.description ?? '')} - `; - return true; - }, -}; - -const createEventListenerFor = - (className: string) => - (event: Event): void => { - event.preventDefault(); - event.stopPropagation(); - - const { currentTarget } = event; - - const galleryItems = Array.from(document.querySelectorAll(className)); - - const sortedElements = galleryItems.sort((a, b) => { - if (a === currentTarget) { - return -1; - } - - if (b === currentTarget) { - return 1; - } - - return 0; - }); - - const slidePromises = sortedElements.map((element) => fromElementToSlide(element)); - - let hasOpenedGallery = false; - - void slidePromises.reduce( - (p, curr) => - p - .then(() => curr) - .then(async (slide) => { - if (!slide) { - return; - } - - if (!currentGallery) { - // If the gallery doesn't exist and has been opened this run the user closed it before all promises ran. - // This means it shouldn't be opened again. - if (hasOpenedGallery) { - return; - } - hasOpenedGallery = true; - return initGallery([slide], defaultGalleryOptions); - } - - currentGallery.items.push(slide); - currentGallery.invalidateCurrItems(); - currentGallery.updateSize(true); - }), - Promise.resolve(), - ); - }; - -Meteor.startup(() => { - // $(document).on('click', '.gallery-item', createEventListenerFor('.gallery-item')); -}); diff --git a/apps/meteor/client/importPackages.ts b/apps/meteor/client/importPackages.ts index 60446388ed9f..ec10c6dd014c 100644 --- a/apps/meteor/client/importPackages.ts +++ b/apps/meteor/client/importPackages.ts @@ -38,7 +38,6 @@ import '../app/slashcommands-open/client'; import '../app/slashcommands-topic/client'; import '../app/slashcommands-unarchiveroom/client'; import '../app/tokenpass/client'; -import '../app/ui/client'; import '../app/webdav/client'; import '../app/webrtc/client'; import '../app/wordpress/client'; From ee6dd483019963ec61af5db2aa9f5f94aa8284e7 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 11 Oct 2023 18:25:56 -0300 Subject: [PATCH 06/21] fis: stylelint:fix --- apps/meteor/app/theme/client/main.css | 3 +- .../ImageGallery/ImageGallery.styles.css | 35 +++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/apps/meteor/app/theme/client/main.css b/apps/meteor/app/theme/client/main.css index 2ebace9f5bf5..50bcc2fef625 100644 --- a/apps/meteor/app/theme/client/main.css +++ b/apps/meteor/app/theme/client/main.css @@ -32,11 +32,12 @@ z-index: 99; top: 0; + overflow: hidden; + width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); - overflow: hidden; } .swiper { diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.styles.css b/apps/meteor/client/components/ImageGallery/ImageGallery.styles.css index cbe835f5ba3b..5891362140e3 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.styles.css +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.styles.css @@ -5,53 +5,68 @@ .swiper-container { background-color: var(--rcx-color-surface-overlay, rgba(0, 0, 0, 0.6)); } -.rcx-swiper-close-button, .rcx-swiper-prev-button, .rcx-swiper-next-button { - color: var(--rcx-color-font-pure-white, #fff) !important; + +.rcx-swiper-close-button, +.rcx-swiper-prev-button, +.rcx-swiper-next-button { + color: var(--rcx-color-font-pure-white, #ffffff) !important; } + .rcx-swiper-close-button { position: absolute; z-index: 10; top: 10px; right: 10px; } + .rcx-swiper-prev-button, .rcx-swiper-next-button { position: absolute; - top: 50%; z-index: 10; + top: 50%; + cursor: pointer; } + .rcx-swiper-prev-button.swiper-button-disabled, .rcx-swiper-next-button.swiper-button-disabled { - opacity: 0.35; cursor: auto; pointer-events: none; + + opacity: 0.35; } + .rcx-swiper-prev-button.swiper-button-hidden, .rcx-swiper-next-button.swiper-button-hidden { - opacity: 0; cursor: auto; pointer-events: none; + + opacity: 0; } + .swiper-navigation-disabled .rcx-swiper-prev-button, .swiper-navigation-disabled .rcx-swiper-next-button { display: none !important; } + .rcx-swiper-prev-button, .swiper-rtl .rcx-swiper-next-button { - left: 10px; right: auto; + left: 10px; } -.rcx-swiper-prev-button:after, -.swiper-rtl .rcx-swiper-next-button:after { + +.rcx-swiper-prev-button::after, +.swiper-rtl .rcx-swiper-next-button::after { content: 'prev'; } + .rcx-swiper-next-button, .swiper-rtl .rcx-swiper-prev-button { right: 10px; left: auto; } -.rcx-swiper-next-button:after, -.swiper-rtl .rcx-swiper-prev-button:after { + +.rcx-swiper-next-button::after, +.swiper-rtl .rcx-swiper-prev-button::after { content: 'next'; } From cf68502cb86639291d6d055abfc1c071b4b4eeb9 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 18 Oct 2023 20:09:38 -0300 Subject: [PATCH 07/21] wip: hook and Files list gallery --- apps/meteor/client/hooks/useImageGallery.ts | 17 +++++ .../RoomFiles/RoomFilesWithData.js | 34 ++++++---- .../RoomFiles/components/FileItem.js | 64 ++++++++++++------- .../views/room/providers/RoomProvider.tsx | 17 ++--- 4 files changed, 82 insertions(+), 50 deletions(-) create mode 100644 apps/meteor/client/hooks/useImageGallery.ts diff --git a/apps/meteor/client/hooks/useImageGallery.ts b/apps/meteor/client/hooks/useImageGallery.ts new file mode 100644 index 000000000000..967c8bae2911 --- /dev/null +++ b/apps/meteor/client/hooks/useImageGallery.ts @@ -0,0 +1,17 @@ +import { useState, useEffect } from 'react'; + +export const useImageGallery = (selector?: string, containerSelector?: string) => { + const [imageUrl, setImageUrl] = useState(); + + useEffect(() => { + const container = containerSelector && document.querySelector(containerSelector); + (container || document).addEventListener('click', (event: Event) => { + const target = event?.target as HTMLElement | null; + if (target?.classList.contains(selector || 'gallery-item')) { + target.dataset.src ? setImageUrl(target.dataset.src) : setImageUrl((target as HTMLImageElement)?.src); + } + }); + }, [containerSelector, selector]); + + return { imageUrl, onClose: () => setImageUrl(undefined) }; +}; diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js index c30c4aeca41e..dde6eb1ddc1e 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js @@ -3,8 +3,10 @@ import { useSetModal, useToastMessageDispatch, useUserId, useMethod, useTranslat import React, { useState, useCallback, useMemo } from 'react'; import GenericModal from '../../../../components/GenericModal'; +import ImageGallery from '../../../../components/ImageGallery/ImageGallery'; import { useRecordList } from '../../../../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../hooks/useAsyncState'; +import { useImageGallery } from '../../../../hooks/useImageGallery'; import { useRoom } from '../../contexts/RoomContext'; import { useRoomToolbox } from '../../contexts/RoomToolboxContext'; import RoomFiles from './RoomFiles'; @@ -20,6 +22,7 @@ const RoomFilesWithData = () => { const closeModal = useMutableCallback(() => setModal()); const dispatchToastMessage = useToastMessageDispatch(); const deleteFile = useMethod('deleteFileMessage'); + const { imageUrl, onClose } = useImageGallery('rcx-avatar__element', 'rcx-verticalbar__content'); const [type, setType] = useLocalStorage('file-list-type', 'all'); const [text, setText] = useState(''); @@ -53,20 +56,23 @@ const RoomFilesWithData = () => { const isDeletionAllowed = useMessageDeletionIsAllowed(room._id, uid); return ( - + <> + {imageUrl && } + + ); }; diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js index a8ae3e8e26a8..5122994ec92b 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js @@ -19,33 +19,49 @@ const FileItem = ({ fileData, isDeletionAllowed, onClickDelete }) => { const { _id, name, url, uploadedAt, ts, type, typeGroup, style, className, user } = fileData; return ( - - - {typeGroup === 'image' ? : } - - - {name} + + {typeGroup === 'image' ? ( + + + + + {name} + + + @{user?.username} + + + {format(uploadedAt)} + - - @{user?.username} - - - {format(uploadedAt)} + + ) : ( + + + + + {name} + + + @{user?.username} + + + {format(uploadedAt)} + - + )} { useRoomRolesManagement(rid); + const { imageUrl, onClose } = useImageGallery(); const { data: room, isSuccess } = useRoomQuery(rid); - const [imageUrl, setImageUrl] = useState(); + // TODO: the following effect is a workaround while we don't have a general and definitive solution for it const router = useRouter(); useEffect(() => { @@ -37,15 +39,6 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { } }, [isSuccess, room, router]); - useEffect(() => { - document.addEventListener('click', (event) => { - const target = event?.target as HTMLImageElement; - if (target?.classList.contains('gallery-item')) { - setImageUrl(target?.dataset?.src); - } - }); - }, []); - const subscriptionQuery = useReactiveQuery(['subscriptions', { rid }], () => ChatSubscription.findOne({ rid }) ?? null); const pseudoRoom = useMemo(() => { @@ -119,7 +112,7 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { return ( - {imageUrl && setImageUrl(null)} />} + {imageUrl && } {children} From 76a0282576c3699006440bc9584f08916989d7e5 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 25 Oct 2023 11:57:41 -0300 Subject: [PATCH 08/21] feat: sort by recent --- apps/meteor/client/components/ImageGallery/ImageGallery.tsx | 6 +++--- .../views/room/contextualBar/RoomFiles/RoomFilesWithData.js | 2 +- .../room/contextualBar/RoomFiles/hooks/useFilesList.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx index 7b2dd1483b71..84df2d43348e 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx @@ -16,7 +16,7 @@ import { useRecordList } from '../../hooks/lists/useRecordList'; import { useRoom } from '../../views/room/contexts/RoomContext'; import { useFilesList } from '../../views/room/contextualBar/RoomFiles/hooks/useFilesList'; -const ImageGallery = ({ url, onClose }: { url: string; onClose: () => void }) => { +const ImageGallery = ({ url, onClose, sortByRecent }: { url: string; onClose: () => void; sortByRecent?: boolean }) => { const room = useRoom(); const swiperRef = useRef(null); @@ -28,14 +28,14 @@ const ImageGallery = ({ url, onClose }: { url: string; onClose: () => void }) => const { phase, items: filesItems } = useRecordList(filesList); useEffect(() => { - const list = [...filesItems].reverse(); + const list = sortByRecent ? [...filesItems] : [...filesItems].reverse(); if (phase === 'resolved') { setImages(list.map((item) => item.url || '').filter(Boolean)); setCurrentSlide(list.findIndex((item) => url.includes(item._id))); } return () => swiperInst?.update(); - }, [filesItems, phase, swiperInst, url]); + }, [filesItems, phase, sortByRecent, swiperInst, url]); const swiperLoader = ( diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js index dde6eb1ddc1e..6a3c6928e831 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js @@ -57,7 +57,7 @@ const RoomFilesWithData = () => { return ( <> - {imageUrl && } + {imageUrl && } { const [filesList, setFilesList] = useState(() => new FilesList(options)); const reload = useCallback(() => setFilesList(new FilesList(options)), [options]); - const room = useUserRoom(options.rid as string); + const room = useUserRoom(options.rid); const uid = useUserId(); useComponentDidUpdate(() => { From 696eb487750bba854f41e0718192600010fbc33b Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 25 Oct 2023 17:01:40 -0300 Subject: [PATCH 09/21] feat: load more images --- .../components/ImageGallery/ImageGallery.tsx | 7 +- .../ImageGallery/hooks/useGalleryList.ts | 92 +++++++++++++++++++ 2 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 apps/meteor/client/components/ImageGallery/hooks/useGalleryList.ts diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx index 84df2d43348e..ac550981d9b0 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx @@ -16,15 +16,15 @@ import { useRecordList } from '../../hooks/lists/useRecordList'; import { useRoom } from '../../views/room/contexts/RoomContext'; import { useFilesList } from '../../views/room/contextualBar/RoomFiles/hooks/useFilesList'; -const ImageGallery = ({ url, onClose, sortByRecent }: { url: string; onClose: () => void; sortByRecent?: boolean }) => { +const ImageGallery = ({ url, onClose, sortByRecent }: { url: string; onClose: () => void; sortByRecent?: boolean; name?: string }) => { const room = useRoom(); const swiperRef = useRef(null); - const [images, setImages] = useState(); + const [images, setImages] = useState([]); const [swiperInst, setSwiperInst] = useState(); const [currentSlide, setCurrentSlide] = useState(); - const { filesList } = useFilesList(useMemo(() => ({ rid: room._id, type: 'image', text: '' }), [room._id])); + const { filesList, loadMoreItems } = useFilesList(useMemo(() => ({ rid: room._id, type: 'image', text: '' }), [room._id])); const { phase, items: filesItems } = useRecordList(filesList); useEffect(() => { @@ -65,6 +65,7 @@ const ImageGallery = ({ url, onClose, sortByRecent }: { url: string; onClose: () onKeyPress={(_, keyCode) => String(keyCode) === '27' && onClose()} modules={[Navigation, Zoom, Keyboard, A11y]} onInit={(swiper) => setSwiperInst(swiper)} + onReachEnd={() => loadMoreItems(images.length - currentSlide, images.length + 1)} > {images?.map((image, index) => ( diff --git a/apps/meteor/client/components/ImageGallery/hooks/useGalleryList.ts b/apps/meteor/client/components/ImageGallery/hooks/useGalleryList.ts new file mode 100644 index 000000000000..6b75b5c86db6 --- /dev/null +++ b/apps/meteor/client/components/ImageGallery/hooks/useGalleryList.ts @@ -0,0 +1,92 @@ +import { useUserRoom, useUserId, useEndpoint } from '@rocket.chat/ui-contexts'; +import { useCallback, useEffect, useMemo, useState } from 'react'; + +import { useScrollableRecordList } from '../../../hooks/lists/useScrollableRecordList'; +import { useStreamUpdatesForMessageList } from '../../../hooks/lists/useStreamUpdatesForMessageList'; +import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; +// import type { FilesListOptions } from '../../../lib/lists/FilesList'; +import { FilesList } from '../../../lib/lists/FilesList'; +import type { MessageList } from '../../../lib/lists/MessageList'; +// import { getConfig } from '../../../lib/utils/getConfig'; + +export const useGalleryList = ({ + rid, + name, + url, +}: any): { + filesList: FilesList; + initialItemCount: number; + reload: () => void; + loadMoreItems: (start: number, end: number) => void; +} => { + const options = useMemo(() => ({ rid, type: 'image', text: '' }), [rid]); + + const [filesList, setFilesList] = useState(() => new FilesList(options)); + const reload = useCallback(() => setFilesList(new FilesList(options)), [options]); + const room = useUserRoom(options.rid); + const uid = useUserId(); + + useComponentDidUpdate(() => { + options && reload(); + }, [options, reload]); + + useEffect(() => { + if (filesList.options !== options) { + filesList.updateFilters(options); + } + }, [filesList, options]); + + const roomTypes = { + c: '/v1/channels.files', + l: '/v1/channels.files', + v: '/v1/channels.files', + d: '/v1/im.files', + p: '/v1/groups.files', + } as const; + + const apiEndPoint = room ? roomTypes[room.t] : '/v1/channels.files'; + const getFiles = useEndpoint('GET', apiEndPoint); + + const fetchMessages = useCallback( + async (start, end) => { + console.log(start, end, url); + const { files, total } = await getFiles({ + roomId: options.rid, + offset: start, + count: end, + sort: JSON.stringify({ uploadedAt: -1 }), + query: JSON.stringify({ + name: { $regex: options.text || '', $options: 'i' }, + ...(options.type !== 'all' && { + typeGroup: options.type, + }), + }), + }); + // http://localhost:3000/ufs/GridFS:Uploads/65243e6f0ceb6de1166812a6/Clipboard%20-%20October%209,%202023%2014:54.png + // /ufs/GridFS:Uploads/65243e6f0ceb6de1166812a6/Clipboard%20-%20October%209,%202023%2014:54.png + + console.log('files', files); + return { + items: files.map((file) => ({ + ...file, + uploadedAt: file.uploadedAt ? new Date(file.uploadedAt) : undefined, + modifiedAt: file.modifiedAt ? new Date(file.modifiedAt) : undefined, + })), + itemCount: total, + }; + }, + [url, getFiles, options.rid, options.text, options.type], + ); + + const { loadMoreItems, initialItemCount } = useScrollableRecordList(filesList, fetchMessages, 10); + + // TODO: chapter day : frontend create useStreamUpdatesForUploadList + useStreamUpdatesForMessageList(filesList as unknown as MessageList, uid, options.rid || null); + + return { + reload, + filesList, + loadMoreItems, + initialItemCount, + }; +}; From 3ae6cf89f61fbddb12c8461328630b6fb4d9e6b9 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Mon, 30 Oct 2023 16:16:44 -0300 Subject: [PATCH 10/21] refactor: css-in-js --- .../ImageGallery/ImageGallery.styles.css | 72 ---------- .../components/ImageGallery/ImageGallery.tsx | 136 +++++++++++++----- .../ImageGallery/hooks/useGalleryList.ts | 92 ------------ .../RoomFiles/RoomFilesWithData.js | 2 +- 4 files changed, 99 insertions(+), 203 deletions(-) delete mode 100644 apps/meteor/client/components/ImageGallery/ImageGallery.styles.css delete mode 100644 apps/meteor/client/components/ImageGallery/hooks/useGalleryList.ts diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.styles.css b/apps/meteor/client/components/ImageGallery/ImageGallery.styles.css deleted file mode 100644 index 5891362140e3..000000000000 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.styles.css +++ /dev/null @@ -1,72 +0,0 @@ -:root { - --swiper-navigation-size: 44px; -} - -.swiper-container { - background-color: var(--rcx-color-surface-overlay, rgba(0, 0, 0, 0.6)); -} - -.rcx-swiper-close-button, -.rcx-swiper-prev-button, -.rcx-swiper-next-button { - color: var(--rcx-color-font-pure-white, #ffffff) !important; -} - -.rcx-swiper-close-button { - position: absolute; - z-index: 10; - top: 10px; - right: 10px; -} - -.rcx-swiper-prev-button, -.rcx-swiper-next-button { - position: absolute; - z-index: 10; - top: 50%; - - cursor: pointer; -} - -.rcx-swiper-prev-button.swiper-button-disabled, -.rcx-swiper-next-button.swiper-button-disabled { - cursor: auto; - pointer-events: none; - - opacity: 0.35; -} - -.rcx-swiper-prev-button.swiper-button-hidden, -.rcx-swiper-next-button.swiper-button-hidden { - cursor: auto; - pointer-events: none; - - opacity: 0; -} - -.swiper-navigation-disabled .rcx-swiper-prev-button, -.swiper-navigation-disabled .rcx-swiper-next-button { - display: none !important; -} - -.rcx-swiper-prev-button, -.swiper-rtl .rcx-swiper-next-button { - right: auto; - left: 10px; -} - -.rcx-swiper-prev-button::after, -.swiper-rtl .rcx-swiper-next-button::after { - content: 'prev'; -} - -.rcx-swiper-next-button, -.swiper-rtl .rcx-swiper-prev-button { - right: 10px; - left: auto; -} - -.rcx-swiper-next-button::after, -.swiper-rtl .rcx-swiper-prev-button::after { - content: 'next'; -} diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx index ac550981d9b0..1c679c6fdec6 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx @@ -1,4 +1,5 @@ -import { IconButton, ModalBackdrop, Throbber } from '@rocket.chat/fuselage'; +import { css } from '@rocket.chat/css-in-js'; +import { Box, IconButton, ModalBackdrop, Throbber } from '@rocket.chat/fuselage'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { Keyboard, Navigation, Zoom, A11y } from 'swiper'; @@ -10,32 +11,85 @@ import 'swiper/swiper.css'; import 'swiper/modules/navigation/navigation.min.css'; import 'swiper/modules/keyboard/keyboard.min.css'; import 'swiper/modules/zoom/zoom.min.css'; -import './ImageGallery.styles.css'; import { useRecordList } from '../../hooks/lists/useRecordList'; import { useRoom } from '../../views/room/contexts/RoomContext'; import { useFilesList } from '../../views/room/contextualBar/RoomFiles/hooks/useFilesList'; -const ImageGallery = ({ url, onClose, sortByRecent }: { url: string; onClose: () => void; sortByRecent?: boolean; name?: string }) => { +const swiperStyle = css` + .swiper-container { + background-color: var(--rcx-color-surface-overlay, rgba(0, 0, 0, 0.6)); + } + + .rcx-swiper-close-button, + .rcx-swiper-prev-button, + .rcx-swiper-next-button { + color: var(--rcx-color-font-pure-white, #ffffff) !important; + } + + .rcx-swiper-close-button { + position: absolute; + z-index: 10; + top: 10px; + right: 10px; + } + + .rcx-swiper-prev-button, + .rcx-swiper-next-button { + position: absolute; + z-index: 10; + top: 50%; + + cursor: pointer; + } + + .rcx-swiper-prev-button.swiper-button-disabled, + .rcx-swiper-next-button.swiper-button-disabled { + cursor: auto; + pointer-events: none; + + opacity: 0.35; + } + + .rcx-swiper-prev-button.swiper-button-hidden, + .rcx-swiper-next-button.swiper-button-hidden { + cursor: auto; + pointer-events: none; + + opacity: 0; + } + + .rcx-swiper-prev-button, + .swiper-rtl .rcx-swiper-next-button { + right: auto; + left: 10px; + } + + .rcx-swiper-next-button, + .swiper-rtl .rcx-swiper-prev-button { + right: 10px; + left: auto; + } +`; +const ImageGallery = ({ url, onClose }: { url: string; onClose: () => void }) => { const room = useRoom(); const swiperRef = useRef(null); const [images, setImages] = useState([]); - const [swiperInst, setSwiperInst] = useState(); + const [, setSwiperInst] = useState(); const [currentSlide, setCurrentSlide] = useState(); const { filesList, loadMoreItems } = useFilesList(useMemo(() => ({ rid: room._id, type: 'image', text: '' }), [room._id])); const { phase, items: filesItems } = useRecordList(filesList); useEffect(() => { - const list = sortByRecent ? [...filesItems] : [...filesItems].reverse(); + const list = [...filesItems]; if (phase === 'resolved') { setImages(list.map((item) => item.url || '').filter(Boolean)); setCurrentSlide(list.findIndex((item) => url.includes(item._id))); } - return () => swiperInst?.update(); - }, [filesItems, phase, sortByRecent, swiperInst, url]); + }, [filesItems, phase, url]); const swiperLoader = ( @@ -46,39 +100,45 @@ const ImageGallery = ({ url, onClose, sortByRecent }: { url: string; onClose: () return createPortal(swiperLoader, document.body); } + const handleLoadMore = () => { + loadMoreItems(images.length - currentSlide, images.length + 1); + }; + const swiperContainer = ( -
- - swiperRef?.current?.swiper.slidePrev()} /> - swiperRef?.current?.swiper.slideNext()} /> - String(keyCode) === '27' && onClose()} - modules={[Navigation, Zoom, Keyboard, A11y]} - onInit={(swiper) => setSwiperInst(swiper)} - onReachEnd={() => loadMoreItems(images.length - currentSlide, images.length + 1)} - > - {images?.map((image, index) => ( - -
- -
- + +
+ + swiperRef?.current?.swiper.slidePrev()} /> + swiperRef?.current?.swiper.slideNext()} /> + String(keyCode) === '27' && onClose()} + modules={[Navigation, Zoom, Keyboard, A11y]} + onInit={(swiper) => setSwiperInst(swiper)} + onReachEnd={handleLoadMore} + > + {images?.map((image, index) => ( + +
+ +
+ +
-
- - ))} - -
+ + ))} + +
+ ); return createPortal(swiperContainer, document.body); }; diff --git a/apps/meteor/client/components/ImageGallery/hooks/useGalleryList.ts b/apps/meteor/client/components/ImageGallery/hooks/useGalleryList.ts deleted file mode 100644 index 6b75b5c86db6..000000000000 --- a/apps/meteor/client/components/ImageGallery/hooks/useGalleryList.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { useUserRoom, useUserId, useEndpoint } from '@rocket.chat/ui-contexts'; -import { useCallback, useEffect, useMemo, useState } from 'react'; - -import { useScrollableRecordList } from '../../../hooks/lists/useScrollableRecordList'; -import { useStreamUpdatesForMessageList } from '../../../hooks/lists/useStreamUpdatesForMessageList'; -import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; -// import type { FilesListOptions } from '../../../lib/lists/FilesList'; -import { FilesList } from '../../../lib/lists/FilesList'; -import type { MessageList } from '../../../lib/lists/MessageList'; -// import { getConfig } from '../../../lib/utils/getConfig'; - -export const useGalleryList = ({ - rid, - name, - url, -}: any): { - filesList: FilesList; - initialItemCount: number; - reload: () => void; - loadMoreItems: (start: number, end: number) => void; -} => { - const options = useMemo(() => ({ rid, type: 'image', text: '' }), [rid]); - - const [filesList, setFilesList] = useState(() => new FilesList(options)); - const reload = useCallback(() => setFilesList(new FilesList(options)), [options]); - const room = useUserRoom(options.rid); - const uid = useUserId(); - - useComponentDidUpdate(() => { - options && reload(); - }, [options, reload]); - - useEffect(() => { - if (filesList.options !== options) { - filesList.updateFilters(options); - } - }, [filesList, options]); - - const roomTypes = { - c: '/v1/channels.files', - l: '/v1/channels.files', - v: '/v1/channels.files', - d: '/v1/im.files', - p: '/v1/groups.files', - } as const; - - const apiEndPoint = room ? roomTypes[room.t] : '/v1/channels.files'; - const getFiles = useEndpoint('GET', apiEndPoint); - - const fetchMessages = useCallback( - async (start, end) => { - console.log(start, end, url); - const { files, total } = await getFiles({ - roomId: options.rid, - offset: start, - count: end, - sort: JSON.stringify({ uploadedAt: -1 }), - query: JSON.stringify({ - name: { $regex: options.text || '', $options: 'i' }, - ...(options.type !== 'all' && { - typeGroup: options.type, - }), - }), - }); - // http://localhost:3000/ufs/GridFS:Uploads/65243e6f0ceb6de1166812a6/Clipboard%20-%20October%209,%202023%2014:54.png - // /ufs/GridFS:Uploads/65243e6f0ceb6de1166812a6/Clipboard%20-%20October%209,%202023%2014:54.png - - console.log('files', files); - return { - items: files.map((file) => ({ - ...file, - uploadedAt: file.uploadedAt ? new Date(file.uploadedAt) : undefined, - modifiedAt: file.modifiedAt ? new Date(file.modifiedAt) : undefined, - })), - itemCount: total, - }; - }, - [url, getFiles, options.rid, options.text, options.type], - ); - - const { loadMoreItems, initialItemCount } = useScrollableRecordList(filesList, fetchMessages, 10); - - // TODO: chapter day : frontend create useStreamUpdatesForUploadList - useStreamUpdatesForMessageList(filesList as unknown as MessageList, uid, options.rid || null); - - return { - reload, - filesList, - loadMoreItems, - initialItemCount, - }; -}; diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js index 6a3c6928e831..dde6eb1ddc1e 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js @@ -57,7 +57,7 @@ const RoomFilesWithData = () => { return ( <> - {imageUrl && } + {imageUrl && } Date: Mon, 30 Oct 2023 16:51:11 -0300 Subject: [PATCH 11/21] refactor: `FIleItem` split ImageItem into new component --- .../components/ImageGallery/ImageGallery.tsx | 1 + .../RoomFiles/components/FileItem.js | 18 ++-------- .../RoomFiles/components/ImageItem.tsx | 33 +++++++++++++++++++ 3 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 apps/meteor/client/views/room/contextualBar/RoomFiles/components/ImageItem.tsx diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx index 1c679c6fdec6..a5a53deb7c54 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx @@ -71,6 +71,7 @@ const swiperStyle = css` left: auto; } `; + const ImageGallery = ({ url, onClose }: { url: string; onClose: () => void }) => { const room = useRoom(); const swiperRef = useRef(null); diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js index 5122994ec92b..6e17bd1a7459 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js @@ -1,9 +1,10 @@ import { css } from '@rocket.chat/css-in-js'; -import { Box, Avatar, Palette } from '@rocket.chat/fuselage'; +import { Box, Palette } from '@rocket.chat/fuselage'; import React from 'react'; import { useFormatDateAndTime } from '../../../../../hooks/useFormatDateAndTime'; import FileItemIcon from './FileItemIcon'; +import ImageItem from './ImageItem'; import MenuItem from './MenuItem'; const hoverClass = css` @@ -21,20 +22,7 @@ const FileItem = ({ fileData, isDeletionAllowed, onClickDelete }) => { return ( {typeGroup === 'image' ? ( - - - - - {name} - - - @{user?.username} - - - {format(uploadedAt)} - - - + ) : ( { + return ( + + {url && } + + {name && ( + + {name} + + )} + {username && ( + + @{username} + + )} + + {timestamp} + + + + ); +}; + +export default ImageItem; From f340ffd2b31a3b7961cc48b819ce7a48643325da Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Mon, 30 Oct 2023 18:00:12 -0300 Subject: [PATCH 12/21] refactor: replace main css content --- apps/meteor/app/theme/client/main.css | 22 ------------------- .../components/ImageGallery/ImageGallery.tsx | 13 +++++++++++ 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/apps/meteor/app/theme/client/main.css b/apps/meteor/app/theme/client/main.css index 50bcc2fef625..6c0a20d844bb 100644 --- a/apps/meteor/app/theme/client/main.css +++ b/apps/meteor/app/theme/client/main.css @@ -26,25 +26,3 @@ @import './rocketchat.font.css'; @import './mentionLink.css'; @import '../../../node_modules/@rocket.chat/fuselage/dist/fuselage.css'; - -.swiper-container { - position: absolute; - z-index: 99; - top: 0; - - overflow: hidden; - - width: 100%; - height: 100%; - - background: rgba(0, 0, 0, 0.5); -} - -.swiper { - width: 100%; - height: 100%; -} - -.swiper-slide { - overflow: hidden; -} diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx index a5a53deb7c54..01db8468f687 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx @@ -17,7 +17,20 @@ import { useRoom } from '../../views/room/contexts/RoomContext'; import { useFilesList } from '../../views/room/contextualBar/RoomFiles/hooks/useFilesList'; const swiperStyle = css` + .swiper { + width: 100%; + height: 100%; + } .swiper-container { + position: absolute; + z-index: 99; + top: 0; + + overflow: hidden; + + width: 100%; + height: 100%; + background-color: var(--rcx-color-surface-overlay, rgba(0, 0, 0, 0.6)); } From c5bc97c7bccc1c4ca781e19355d23188768f7766 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Mon, 30 Oct 2023 19:02:24 -0300 Subject: [PATCH 13/21] refactor: `useImageGallery` --- .../components/ImageGallery/ImageGallery.tsx | 54 ++++++------------- .../ImageGallery/ImageGalleryLoader.tsx | 13 +++++ .../ImageGallery/hooks/useImageGallery.ts | 40 ++++++++++++++ apps/meteor/client/hooks/useImageGallery.ts | 17 ------ .../RoomFiles/RoomFilesWithData.js | 6 +-- .../views/room/providers/RoomProvider.tsx | 8 +-- 6 files changed, 78 insertions(+), 60 deletions(-) create mode 100644 apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx create mode 100644 apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts delete mode 100644 apps/meteor/client/hooks/useImageGallery.ts diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx index 01db8468f687..6c1238812b8f 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx @@ -1,6 +1,6 @@ import { css } from '@rocket.chat/css-in-js'; -import { Box, IconButton, ModalBackdrop, Throbber } from '@rocket.chat/fuselage'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { Box, IconButton, Throbber } from '@rocket.chat/fuselage'; +import React, { useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { Keyboard, Navigation, Zoom, A11y } from 'swiper'; import type { SwiperRef } from 'swiper/react'; @@ -12,9 +12,7 @@ import 'swiper/modules/navigation/navigation.min.css'; import 'swiper/modules/keyboard/keyboard.min.css'; import 'swiper/modules/zoom/zoom.min.css'; -import { useRecordList } from '../../hooks/lists/useRecordList'; -import { useRoom } from '../../views/room/contexts/RoomContext'; -import { useFilesList } from '../../views/room/contextualBar/RoomFiles/hooks/useFilesList'; +import ImageGalleryLoader from './ImageGalleryLoader'; const swiperStyle = css` .swiper { @@ -85,40 +83,22 @@ const swiperStyle = css` } `; -const ImageGallery = ({ url, onClose }: { url: string; onClose: () => void }) => { - const room = useRoom(); +type ImageGalleryProps = { + images: string[]; + isLoading: boolean; + loadMore: () => void; + currentSlide: number | undefined; + onClose: () => void; +}; +const ImageGallery = ({ images, isLoading, loadMore, currentSlide, onClose }: ImageGalleryProps) => { const swiperRef = useRef(null); - - const [images, setImages] = useState([]); const [, setSwiperInst] = useState(); - const [currentSlide, setCurrentSlide] = useState(); - - const { filesList, loadMoreItems } = useFilesList(useMemo(() => ({ rid: room._id, type: 'image', text: '' }), [room._id])); - const { phase, items: filesItems } = useRecordList(filesList); - - useEffect(() => { - const list = [...filesItems]; - if (phase === 'resolved') { - setImages(list.map((item) => item.url || '').filter(Boolean)); - setCurrentSlide(list.findIndex((item) => url.includes(item._id))); - } - }, [filesItems, phase, url]); - - const swiperLoader = ( - - - - ); - if (phase === 'loading' || currentSlide === undefined) { - return createPortal(swiperLoader, document.body); + if (isLoading) { + return ; } - const handleLoadMore = () => { - loadMoreItems(images.length - currentSlide, images.length + 1); - }; - - const swiperContainer = ( + return createPortal(
@@ -138,7 +118,7 @@ const ImageGallery = ({ url, onClose }: { url: string; onClose: () => void }) => onKeyPress={(_, keyCode) => String(keyCode) === '27' && onClose()} modules={[Navigation, Zoom, Keyboard, A11y]} onInit={(swiper) => setSwiperInst(swiper)} - onReachEnd={handleLoadMore} + onReachEnd={loadMore} > {images?.map((image, index) => ( @@ -152,9 +132,9 @@ const ImageGallery = ({ url, onClose }: { url: string; onClose: () => void }) => ))}
-
+
, + document.body, ); - return createPortal(swiperContainer, document.body); }; export default ImageGallery; diff --git a/apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx b/apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx new file mode 100644 index 000000000000..588276d17d83 --- /dev/null +++ b/apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx @@ -0,0 +1,13 @@ +import { ModalBackdrop, Throbber } from '@rocket.chat/fuselage'; +import React from 'react'; +import { createPortal } from 'react-dom'; + +const ImageGalleryLoader = () => + createPortal( + + + , + document.body, + ); + +export default ImageGalleryLoader; diff --git a/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts b/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts new file mode 100644 index 000000000000..fb8a7032106c --- /dev/null +++ b/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts @@ -0,0 +1,40 @@ +import { useState, useEffect, useMemo } from 'react'; + +// import { useRoom } from '../views/room/contexts/RoomContext'; +import { useRecordList } from '../../../hooks/lists/useRecordList'; +import { useFilesList } from '../../../views/room/contextualBar/RoomFiles/hooks/useFilesList'; + +export const useImageGallery = (rid: string, imageSelector?: string, containerSelector?: string) => { + const [images, setImages] = useState([]); + const [currentSlide, setCurrentSlide] = useState(); + const [imageUrl, setImageUrl] = useState(); + + const { filesList, loadMoreItems } = useFilesList(useMemo(() => ({ rid, type: 'image', text: '' }), [rid])); + const { phase, items: filesItems } = useRecordList(filesList); + + useEffect(() => { + const container = containerSelector && document.querySelector(containerSelector); + (container || document).addEventListener('click', (event: Event) => { + const target = event?.target as HTMLElement | null; + if (target?.classList.contains(imageSelector || 'gallery-item')) { + target.dataset.src ? setImageUrl(target.dataset.src) : setImageUrl((target as HTMLImageElement)?.src); + } + }); + }, [containerSelector, imageSelector]); + + useEffect(() => { + if (phase === 'resolved') { + setImages(filesItems.map((item) => item.url || '').filter(Boolean)); + setCurrentSlide(filesItems.findIndex((item) => imageUrl?.includes(item._id))); + } + }, [filesItems, phase, imageUrl]); + + return { + isOpen: !!imageUrl, + images, + isLoading: phase === 'loading' || currentSlide === undefined, + loadMore: () => loadMoreItems(filesItems.length - (currentSlide || 0), filesItems.length + 5), + currentSlide, + onClose: () => setImageUrl(undefined), + }; +}; diff --git a/apps/meteor/client/hooks/useImageGallery.ts b/apps/meteor/client/hooks/useImageGallery.ts deleted file mode 100644 index 967c8bae2911..000000000000 --- a/apps/meteor/client/hooks/useImageGallery.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useState, useEffect } from 'react'; - -export const useImageGallery = (selector?: string, containerSelector?: string) => { - const [imageUrl, setImageUrl] = useState(); - - useEffect(() => { - const container = containerSelector && document.querySelector(containerSelector); - (container || document).addEventListener('click', (event: Event) => { - const target = event?.target as HTMLElement | null; - if (target?.classList.contains(selector || 'gallery-item')) { - target.dataset.src ? setImageUrl(target.dataset.src) : setImageUrl((target as HTMLImageElement)?.src); - } - }); - }, [containerSelector, selector]); - - return { imageUrl, onClose: () => setImageUrl(undefined) }; -}; diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js index dde6eb1ddc1e..5cda35e33836 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js @@ -4,9 +4,9 @@ import React, { useState, useCallback, useMemo } from 'react'; import GenericModal from '../../../../components/GenericModal'; import ImageGallery from '../../../../components/ImageGallery/ImageGallery'; +import { useImageGallery } from '../../../../components/ImageGallery/hooks/useImageGallery'; import { useRecordList } from '../../../../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../hooks/useAsyncState'; -import { useImageGallery } from '../../../../hooks/useImageGallery'; import { useRoom } from '../../contexts/RoomContext'; import { useRoomToolbox } from '../../contexts/RoomToolboxContext'; import RoomFiles from './RoomFiles'; @@ -22,7 +22,7 @@ const RoomFilesWithData = () => { const closeModal = useMutableCallback(() => setModal()); const dispatchToastMessage = useToastMessageDispatch(); const deleteFile = useMethod('deleteFileMessage'); - const { imageUrl, onClose } = useImageGallery('rcx-avatar__element', 'rcx-verticalbar__content'); + const { isOpen, ...imageGalleryProps } = useImageGallery(room._id, 'rcx-avatar__element', 'rcx-verticalbar__content'); const [type, setType] = useLocalStorage('file-list-type', 'all'); const [text, setText] = useState(''); @@ -57,7 +57,7 @@ const RoomFilesWithData = () => { return ( <> - {imageUrl && } + {isOpen && } { useRoomRolesManagement(rid); - const { imageUrl, onClose } = useImageGallery(); const { data: room, isSuccess } = useRoomQuery(rid); @@ -105,6 +104,9 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { }; }, [rid, subscribed]); + const { isOpen, ...imageGalleryProps } = useImageGallery(rid); + + console.log(isOpen, imageGalleryProps); if (!pseudoRoom) { return isSuccess && !room ? : ; } @@ -112,7 +114,7 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { return ( - {imageUrl && } + {isOpen && } {children} From 38a7e85685c794f8b1bbd28893ea7d24579af074 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Mon, 30 Oct 2023 19:04:56 -0300 Subject: [PATCH 14/21] ops --- apps/meteor/client/views/room/providers/RoomProvider.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/meteor/client/views/room/providers/RoomProvider.tsx b/apps/meteor/client/views/room/providers/RoomProvider.tsx index 4a6347c8d12f..8c5d215a045f 100644 --- a/apps/meteor/client/views/room/providers/RoomProvider.tsx +++ b/apps/meteor/client/views/room/providers/RoomProvider.tsx @@ -106,7 +106,6 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { const { isOpen, ...imageGalleryProps } = useImageGallery(rid); - console.log(isOpen, imageGalleryProps); if (!pseudoRoom) { return isSuccess && !room ? : ; } From bd4fed4bce3b4b6ce6faf66238b0f3fd4328bae3 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Mon, 30 Oct 2023 20:56:23 -0300 Subject: [PATCH 15/21] feat: ImageGallery provider --- .../components/ImageGallery/ImageGallery.tsx | 14 ++---- .../ImageGallery/ImageGalleryLoader.tsx | 13 ++++- .../ImageGallery/hooks/useImageGallery.ts | 23 +++------ .../client/contexts/ImageGalleryContext.ts | 13 +++++ .../client/providers/ImageGalleryProvider.tsx | 47 +++++++++++++++++++ .../RoomFiles/RoomFilesWithData.js | 34 ++++++-------- .../RoomFiles/components/FileItem.js | 2 +- .../RoomFiles/components/ImageItem.tsx | 4 +- .../views/room/providers/RoomProvider.tsx | 10 ++-- 9 files changed, 104 insertions(+), 56 deletions(-) create mode 100644 apps/meteor/client/contexts/ImageGalleryContext.ts create mode 100644 apps/meteor/client/providers/ImageGalleryProvider.tsx diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx index 6c1238812b8f..1fb42876784c 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx @@ -13,6 +13,7 @@ import 'swiper/modules/keyboard/keyboard.min.css'; import 'swiper/modules/zoom/zoom.min.css'; import ImageGalleryLoader from './ImageGalleryLoader'; +import { useImageGallery } from './hooks/useImageGallery'; const swiperStyle = css` .swiper { @@ -83,19 +84,14 @@ const swiperStyle = css` } `; -type ImageGalleryProps = { - images: string[]; - isLoading: boolean; - loadMore: () => void; - currentSlide: number | undefined; - onClose: () => void; -}; -const ImageGallery = ({ images, isLoading, loadMore, currentSlide, onClose }: ImageGalleryProps) => { +const ImageGallery = () => { const swiperRef = useRef(null); const [, setSwiperInst] = useState(); + const { isLoading, currentSlide, loadMore, images, onClose } = useImageGallery(); + if (isLoading) { - return ; + return ; } return createPortal( diff --git a/apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx b/apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx index 588276d17d83..a495f345194e 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx @@ -1,10 +1,19 @@ -import { ModalBackdrop, Throbber } from '@rocket.chat/fuselage'; +import { css } from '@rocket.chat/css-in-js'; +import { IconButton, ModalBackdrop, Throbber } from '@rocket.chat/fuselage'; import React from 'react'; import { createPortal } from 'react-dom'; -const ImageGalleryLoader = () => +const closeButtonStyle = css` + position: absolute; + z-index: 10; + top: 10px; + right: 10px; +`; + +const ImageGalleryLoader = ({ onClose }: { onClose: () => void }) => createPortal( + , document.body, diff --git a/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts b/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts index fb8a7032106c..d7ba82dc9fc5 100644 --- a/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts +++ b/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts @@ -1,27 +1,19 @@ -import { useState, useEffect, useMemo } from 'react'; +import { useState, useEffect, useMemo, useContext } from 'react'; -// import { useRoom } from '../views/room/contexts/RoomContext'; +import { ImageGalleryContext } from '../../../contexts/ImageGalleryContext'; import { useRecordList } from '../../../hooks/lists/useRecordList'; +import { useRoom } from '../../../views/room/contexts/RoomContext'; import { useFilesList } from '../../../views/room/contextualBar/RoomFiles/hooks/useFilesList'; -export const useImageGallery = (rid: string, imageSelector?: string, containerSelector?: string) => { +export const useImageGallery = () => { + const { _id: rid } = useRoom(); + const { imageUrl, onClose } = useContext(ImageGalleryContext); const [images, setImages] = useState([]); const [currentSlide, setCurrentSlide] = useState(); - const [imageUrl, setImageUrl] = useState(); const { filesList, loadMoreItems } = useFilesList(useMemo(() => ({ rid, type: 'image', text: '' }), [rid])); const { phase, items: filesItems } = useRecordList(filesList); - useEffect(() => { - const container = containerSelector && document.querySelector(containerSelector); - (container || document).addEventListener('click', (event: Event) => { - const target = event?.target as HTMLElement | null; - if (target?.classList.contains(imageSelector || 'gallery-item')) { - target.dataset.src ? setImageUrl(target.dataset.src) : setImageUrl((target as HTMLImageElement)?.src); - } - }); - }, [containerSelector, imageSelector]); - useEffect(() => { if (phase === 'resolved') { setImages(filesItems.map((item) => item.url || '').filter(Boolean)); @@ -30,11 +22,10 @@ export const useImageGallery = (rid: string, imageSelector?: string, containerSe }, [filesItems, phase, imageUrl]); return { - isOpen: !!imageUrl, images, isLoading: phase === 'loading' || currentSlide === undefined, loadMore: () => loadMoreItems(filesItems.length - (currentSlide || 0), filesItems.length + 5), currentSlide, - onClose: () => setImageUrl(undefined), + onClose, }; }; diff --git a/apps/meteor/client/contexts/ImageGalleryContext.ts b/apps/meteor/client/contexts/ImageGalleryContext.ts new file mode 100644 index 000000000000..38f53c3f975b --- /dev/null +++ b/apps/meteor/client/contexts/ImageGalleryContext.ts @@ -0,0 +1,13 @@ +import { createContext } from 'react'; + +export type ImageGalleryContextValue = { + imageUrl: string; + isOpen: boolean; + onClose: () => void; +}; + +export const ImageGalleryContext = createContext({ + imageUrl: '', + isOpen: false, + onClose: () => undefined, +}); diff --git a/apps/meteor/client/providers/ImageGalleryProvider.tsx b/apps/meteor/client/providers/ImageGalleryProvider.tsx new file mode 100644 index 000000000000..98e0811010a5 --- /dev/null +++ b/apps/meteor/client/providers/ImageGalleryProvider.tsx @@ -0,0 +1,47 @@ +import React, { type ReactNode, useEffect, useState } from 'react'; + +import ImageGallery from '../components/ImageGallery/ImageGallery'; +import { ImageGalleryContext } from '../contexts/ImageGalleryContext'; + +type ImageGalleryProviderProps = { + children: ReactNode; + imageSelector?: string; + containerSelector?: string; +}; + +const ImageGalleryProvider = ({ children, imageSelector, containerSelector }: ImageGalleryProviderProps) => { + const [imageUrl, setImageUrl] = useState(); + + useEffect(() => { + document.addEventListener('click', (event: Event) => { + const target = event?.target as HTMLElement | null; + + if (target?.classList.contains('gallery-item')) { + return setImageUrl(target.dataset.src || target?.parentElement?.parentElement?.querySelector('img')?.src); + } + + if (target?.classList.contains('gallery-item-container')) { + return setImageUrl((target.querySelector('img.rcx-avatar__element') as HTMLImageElement | null)?.src); + } + if ( + target?.classList.contains('gallery-item') && + target?.parentElement?.parentElement?.classList.contains('gallery-item-container') + ) { + return setImageUrl((target.parentElement.parentElement.querySelector('img.rcx-avatar__element') as HTMLImageElement | null)?.src); + } + + if (target?.classList.contains('rcx-avatar__element') && target?.parentElement?.classList.contains('gallery-item')) { + return setImageUrl((target as HTMLImageElement).src); + } + }); + }, [containerSelector, imageSelector]); + + return ( + setImageUrl(undefined) }}> + {children} + {!!imageUrl && } + + ); +}; + +export default ImageGalleryProvider; diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js index 5cda35e33836..c30c4aeca41e 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js @@ -3,8 +3,6 @@ import { useSetModal, useToastMessageDispatch, useUserId, useMethod, useTranslat import React, { useState, useCallback, useMemo } from 'react'; import GenericModal from '../../../../components/GenericModal'; -import ImageGallery from '../../../../components/ImageGallery/ImageGallery'; -import { useImageGallery } from '../../../../components/ImageGallery/hooks/useImageGallery'; import { useRecordList } from '../../../../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../hooks/useAsyncState'; import { useRoom } from '../../contexts/RoomContext'; @@ -22,7 +20,6 @@ const RoomFilesWithData = () => { const closeModal = useMutableCallback(() => setModal()); const dispatchToastMessage = useToastMessageDispatch(); const deleteFile = useMethod('deleteFileMessage'); - const { isOpen, ...imageGalleryProps } = useImageGallery(room._id, 'rcx-avatar__element', 'rcx-verticalbar__content'); const [type, setType] = useLocalStorage('file-list-type', 'all'); const [text, setText] = useState(''); @@ -56,23 +53,20 @@ const RoomFilesWithData = () => { const isDeletionAllowed = useMessageDeletionIsAllowed(room._id, uid); return ( - <> - {isOpen && } - - + ); }; diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js index 6e17bd1a7459..cfed9fabe4b1 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js @@ -20,7 +20,7 @@ const FileItem = ({ fileData, isDeletionAllowed, onClickDelete }) => { const { _id, name, url, uploadedAt, ts, type, typeGroup, style, className, user } = fileData; return ( - + {typeGroup === 'image' ? ( ) : ( diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/ImageItem.tsx b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/ImageItem.tsx index c62b50fa3696..cd9222171c30 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/ImageItem.tsx +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/ImageItem.tsx @@ -9,9 +9,9 @@ type ImageItemProps = { }; const ImageItem = ({ url, name, timestamp, username }: ImageItemProps) => { return ( - + {url && } - + {name && ( {name} diff --git a/apps/meteor/client/views/room/providers/RoomProvider.tsx b/apps/meteor/client/views/room/providers/RoomProvider.tsx index 8c5d215a045f..47d65562460a 100644 --- a/apps/meteor/client/views/room/providers/RoomProvider.tsx +++ b/apps/meteor/client/views/room/providers/RoomProvider.tsx @@ -6,12 +6,11 @@ import React, { useMemo, memo, useEffect, useCallback } from 'react'; import { ChatSubscription } from '../../../../app/models/client'; import { RoomHistoryManager } from '../../../../app/ui-utils/client'; import { UserAction } from '../../../../app/ui/client/lib/UserAction'; -import ImageGallery from '../../../components/ImageGallery'; -import { useImageGallery } from '../../../components/ImageGallery/hooks/useImageGallery'; import { useReactiveQuery } from '../../../hooks/useReactiveQuery'; import { useReactiveValue } from '../../../hooks/useReactiveValue'; import { RoomManager } from '../../../lib/RoomManager'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; +import ImageGalleryProvider from '../../../providers/ImageGalleryProvider'; import RoomNotFound from '../RoomNotFound'; import RoomSkeleton from '../RoomSkeleton'; import { useRoomRolesManagement } from '../body/hooks/useRoomRolesManagement'; @@ -104,8 +103,6 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { }; }, [rid, subscribed]); - const { isOpen, ...imageGalleryProps } = useImageGallery(rid); - if (!pseudoRoom) { return isSuccess && !room ? : ; } @@ -113,8 +110,9 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { return ( - {isOpen && } - {children} + + {children} + ); From d43b6cb4fa8c00f62ed170c105230cc12084efad Mon Sep 17 00:00:00 2001 From: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Wed, 1 Nov 2023 17:25:53 -0300 Subject: [PATCH 16/21] feat: add `channels.images` endpoint (#30822) --- apps/meteor/app/api/server/v1/channels.ts | 45 ++++++++++++++++++- apps/meteor/server/models/raw/Uploads.ts | 25 ++++++++++- .../model-typings/src/models/IUploadsModel.ts | 10 ++++- .../src/v1/channels/ChannelsImagesProps.ts | 14 ++++++ .../rest-typings/src/v1/channels/channels.ts | 6 +++ .../rest-typings/src/v1/channels/index.ts | 1 + 6 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 packages/rest-typings/src/v1/channels/ChannelsImagesProps.ts diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts index 8e0541b8040b..1c84926edb63 100644 --- a/apps/meteor/app/api/server/v1/channels.ts +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -1,5 +1,5 @@ import { Team, Room } from '@rocket.chat/core-services'; -import type { IRoom, ISubscription, IUser, RoomType } from '@rocket.chat/core-typings'; +import type { IRoom, ISubscription, IUser, RoomType, IUpload } from '@rocket.chat/core-typings'; import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models'; import { isChannelsAddAllProps, @@ -18,6 +18,7 @@ import { isChannelsConvertToTeamProps, isChannelsSetReadOnlyProps, isChannelsDeleteProps, + isChannelsImagesProps, } from '@rocket.chat/rest-typings'; import { Meteor } from 'meteor/meteor'; @@ -803,6 +804,48 @@ API.v1.addRoute( }, ); +API.v1.addRoute( + 'channels.images', + { authRequired: true, validateParams: isChannelsImagesProps }, + { + async get() { + const room = await Rooms.findOneById>(this.queryParams.roomId, { + projection: { t: 1, teamId: 1, prid: 1 }, + }); + + if (!room || !(await canAccessRoomAsync(room, { _id: this.userId }))) { + return API.v1.unauthorized(); + } + + let initialImage: IUpload | null = null; + if (this.queryParams.startingFromId) { + initialImage = await Uploads.findOneById(this.queryParams.startingFromId); + } + + const { offset, count } = await getPaginationItems(this.queryParams); + + const { cursor, totalCount } = Uploads.findImagesByRoomId(room._id, initialImage?.uploadedAt, { + skip: offset, + limit: count, + }); + + const [files, total] = await Promise.all([cursor.toArray(), totalCount]); + + // If the initial image was not returned in the query, insert it as the first element of the list + if (initialImage && !files.find(({ _id }) => _id === (initialImage as IUpload)._id)) { + files.splice(0, 0, initialImage); + } + + return API.v1.success({ + files, + count, + offset, + total, + }); + }, + }, +); + API.v1.addRoute( 'channels.getIntegrations', { authRequired: true }, diff --git a/apps/meteor/server/models/raw/Uploads.ts b/apps/meteor/server/models/raw/Uploads.ts index 1c02fffab2a8..f2dd26113eaa 100644 --- a/apps/meteor/server/models/raw/Uploads.ts +++ b/apps/meteor/server/models/raw/Uploads.ts @@ -1,5 +1,5 @@ // TODO: Lib imports should not exists inside the raw models -import type { IUpload, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; +import type { IUpload, RocketChatRecordDeleted, IRoom } from '@rocket.chat/core-typings'; import type { FindPaginated, IUploadsModel } from '@rocket.chat/model-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import type { Collection, FindCursor, Db, IndexDescription, WithId, Filter, FindOptions } from 'mongodb'; @@ -57,4 +57,27 @@ export class UploadsRaw extends BaseUploadModelRaw implements IUploadsModel { options, ); } + + findImagesByRoomId( + rid: IRoom['_id'], + uploadedAt?: Date, + options: Omit, 'sort'> = {}, + ): FindPaginated>> { + return this.findPaginated( + { + rid, + _hidden: { $ne: true }, + typeGroup: 'image', + ...(Boolean(uploadedAt) && { + uploadedAt: { + $lte: uploadedAt, + }, + }), + }, + { + ...options, + sort: { uploadedAt: -1 }, + }, + ); + } } diff --git a/packages/model-typings/src/models/IUploadsModel.ts b/packages/model-typings/src/models/IUploadsModel.ts index d5be641c9c37..1e80fcfe39b5 100644 --- a/packages/model-typings/src/models/IUploadsModel.ts +++ b/packages/model-typings/src/models/IUploadsModel.ts @@ -1,5 +1,5 @@ -import type { IUpload } from '@rocket.chat/core-typings'; -import type { FindCursor, WithId, Filter } from 'mongodb'; +import type { IRoom, IUpload } from '@rocket.chat/core-typings'; +import type { FindCursor, WithId, Filter, FindOptions } from 'mongodb'; import type { FindPaginated } from './IBaseModel'; import type { IBaseUploadsModel } from './IBaseUploadsModel'; @@ -8,4 +8,10 @@ export interface IUploadsModel extends IBaseUploadsModel { findNotHiddenFilesOfRoom(roomId: string, searchText: string, fileType: string, limit: number): FindCursor; findPaginatedWithoutThumbs(query: Filter, options?: any): FindPaginated>>; + + findImagesByRoomId( + rid: IRoom['_id'], + uploadedAt?: Date, + options?: Omit, 'sort'>, + ): FindPaginated>>; } diff --git a/packages/rest-typings/src/v1/channels/ChannelsImagesProps.ts b/packages/rest-typings/src/v1/channels/ChannelsImagesProps.ts new file mode 100644 index 000000000000..52c065b7c393 --- /dev/null +++ b/packages/rest-typings/src/v1/channels/ChannelsImagesProps.ts @@ -0,0 +1,14 @@ +import Ajv from 'ajv'; + +const ajv = new Ajv({ + coerceTypes: true, +}); + +export type ChannelsImagesProps = { + roomId: string; + startingFromId: string; + count?: number; + offset?: number; +}; +const channelsImagesPropsSchema = {}; +export const isChannelsImagesProps = ajv.compile(channelsImagesPropsSchema); diff --git a/packages/rest-typings/src/v1/channels/channels.ts b/packages/rest-typings/src/v1/channels/channels.ts index e5ae6175ffdf..eccdf3499847 100644 --- a/packages/rest-typings/src/v1/channels/channels.ts +++ b/packages/rest-typings/src/v1/channels/channels.ts @@ -10,6 +10,7 @@ import type { ChannelsDeleteProps } from './ChannelsDeleteProps'; import type { ChannelsGetAllUserMentionsByChannelProps } from './ChannelsGetAllUserMentionsByChannelProps'; import type { ChannelsGetIntegrationsProps } from './ChannelsGetIntegrationsProps'; import type { ChannelsHistoryProps } from './ChannelsHistoryProps'; +import type { ChannelsImagesProps } from './ChannelsImagesProps'; import type { ChannelsInviteProps } from './ChannelsInviteProps'; import type { ChannelsJoinProps } from './ChannelsJoinProps'; import type { ChannelsKickProps } from './ChannelsKickProps'; @@ -38,6 +39,11 @@ export type ChannelsEndpoints = { files: IUpload[]; }>; }; + '/v1/channels.images': { + GET: (params: ChannelsImagesProps) => PaginatedResult<{ + files: IUpload[]; + }>; + }; '/v1/channels.members': { GET: ( params: PaginatedRequest< diff --git a/packages/rest-typings/src/v1/channels/index.ts b/packages/rest-typings/src/v1/channels/index.ts index 981296e244fe..f0cf81ba622d 100644 --- a/packages/rest-typings/src/v1/channels/index.ts +++ b/packages/rest-typings/src/v1/channels/index.ts @@ -7,6 +7,7 @@ export * from './ChannelsCreateProps'; export * from './ChannelsDeleteProps'; export * from './ChannelsGetAllUserMentionsByChannelProps'; export * from './ChannelsHistoryProps'; +export * from './ChannelsImagesProps'; export * from './ChannelsJoinProps'; export * from './ChannelsKickProps'; export * from './ChannelsLeaveProps'; From cb8739317e958cd00644f00dc23df2cdef965a19 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 1 Nov 2023 19:22:10 -0300 Subject: [PATCH 17/21] feat: add `FocusScope` --- .../components/ImageGallery/ImageGallery.tsx | 69 ++++++++++--------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx index 1fb42876784c..cf222ba24a00 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx @@ -1,6 +1,7 @@ import { css } from '@rocket.chat/css-in-js'; import { Box, IconButton, Throbber } from '@rocket.chat/fuselage'; import React, { useRef, useState } from 'react'; +import { FocusScope } from 'react-aria'; import { createPortal } from 'react-dom'; import { Keyboard, Navigation, Zoom, A11y } from 'swiper'; import type { SwiperRef } from 'swiper/react'; @@ -95,40 +96,42 @@ const ImageGallery = () => { } return createPortal( - -
- - swiperRef?.current?.swiper.slidePrev()} /> - swiperRef?.current?.swiper.slideNext()} /> - String(keyCode) === '27' && onClose()} - modules={[Navigation, Zoom, Keyboard, A11y]} - onInit={(swiper) => setSwiperInst(swiper)} - onReachEnd={loadMore} - > - {images?.map((image, index) => ( - -
- -
- + + +
+ + swiperRef?.current?.swiper.slidePrev()} /> + swiperRef?.current?.swiper.slideNext()} /> + String(keyCode) === '27' && onClose()} + modules={[Navigation, Zoom, Keyboard, A11y]} + onInit={(swiper) => setSwiperInst(swiper)} + onReachEnd={loadMore} + > + {images?.map((image, index) => ( + +
+ +
+ +
-
- - ))} - -
- , + + ))} + +
+ + , document.body, ); }; From 34c3250510c1dbb3931d1078435ed06d4b24d782 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 1 Nov 2023 19:22:47 -0300 Subject: [PATCH 18/21] wip channels.images endpoint implementation --- .../components/ImageGallery/ImageGallery.tsx | 13 ++-- .../ImageGallery/hooks/useImageGallery.ts | 24 ++----- .../ImageGallery/hooks/useImagesList.ts | 69 +++++++++++++++++++ .../message/content/Attachments.tsx | 5 +- .../content/attachments/AttachmentsItem.tsx | 5 +- .../content/attachments/FileAttachment.tsx | 2 +- .../attachments/file/ImageAttachment.tsx | 4 +- .../attachments/structure/AttachmentImage.tsx | 12 +++- .../variants/room/RoomMessageContent.tsx | 2 +- .../client/contexts/ImageGalleryContext.ts | 4 +- apps/meteor/client/lib/lists/ImagesList.ts | 39 +++++++++++ .../client/providers/ImageGalleryProvider.tsx | 21 +++--- .../moderation/helpers/ContextMessage.tsx | 2 +- .../RoomFiles/components/FileItem.js | 2 +- .../RoomFiles/components/ImageItem.tsx | 5 +- 15 files changed, 158 insertions(+), 51 deletions(-) create mode 100644 apps/meteor/client/components/ImageGallery/hooks/useImagesList.ts create mode 100644 apps/meteor/client/lib/lists/ImagesList.ts diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx index cf222ba24a00..2946e3e692a2 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx @@ -89,7 +89,7 @@ const ImageGallery = () => { const swiperRef = useRef(null); const [, setSwiperInst] = useState(); - const { isLoading, currentSlide, loadMore, images, onClose } = useImageGallery(); + const { isLoading, loadMore, images, onClose } = useImageGallery(); if (isLoading) { return ; @@ -100,8 +100,8 @@ const ImageGallery = () => {
- swiperRef?.current?.swiper.slidePrev()} /> - swiperRef?.current?.swiper.slideNext()} /> + + { zoom lazyPreloaderClass='rcx-lazy-preloader' runCallbacksOnInit - initialSlide={currentSlide} onKeyPress={(_, keyCode) => String(keyCode) === '27' && onClose()} modules={[Navigation, Zoom, Keyboard, A11y]} onInit={(swiper) => setSwiperInst(swiper)} onReachEnd={loadMore} > - {images?.map((image, index) => ( - + {images?.map(({ _id, url }) => ( +
- +
diff --git a/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts b/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts index d7ba82dc9fc5..9d058a010fdc 100644 --- a/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts +++ b/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts @@ -1,31 +1,21 @@ -import { useState, useEffect, useMemo, useContext } from 'react'; +import { useMemo, useContext } from 'react'; import { ImageGalleryContext } from '../../../contexts/ImageGalleryContext'; import { useRecordList } from '../../../hooks/lists/useRecordList'; import { useRoom } from '../../../views/room/contexts/RoomContext'; -import { useFilesList } from '../../../views/room/contextualBar/RoomFiles/hooks/useFilesList'; +import { useImagesList } from './useImagesList'; export const useImageGallery = () => { const { _id: rid } = useRoom(); - const { imageUrl, onClose } = useContext(ImageGalleryContext); - const [images, setImages] = useState([]); - const [currentSlide, setCurrentSlide] = useState(); + const { imageId, onClose } = useContext(ImageGalleryContext); - const { filesList, loadMoreItems } = useFilesList(useMemo(() => ({ rid, type: 'image', text: '' }), [rid])); + const { filesList, loadMoreItems } = useImagesList(useMemo(() => ({ roomId: rid, startingFromId: imageId }), [imageId, rid])); const { phase, items: filesItems } = useRecordList(filesList); - useEffect(() => { - if (phase === 'resolved') { - setImages(filesItems.map((item) => item.url || '').filter(Boolean)); - setCurrentSlide(filesItems.findIndex((item) => imageUrl?.includes(item._id))); - } - }, [filesItems, phase, imageUrl]); - return { - images, - isLoading: phase === 'loading' || currentSlide === undefined, - loadMore: () => loadMoreItems(filesItems.length - (currentSlide || 0), filesItems.length + 5), - currentSlide, + images: filesItems, + isLoading: phase === 'loading', + loadMore: () => loadMoreItems(filesItems.length - 1), onClose, }; }; diff --git a/apps/meteor/client/components/ImageGallery/hooks/useImagesList.ts b/apps/meteor/client/components/ImageGallery/hooks/useImagesList.ts new file mode 100644 index 000000000000..da05c7880746 --- /dev/null +++ b/apps/meteor/client/components/ImageGallery/hooks/useImagesList.ts @@ -0,0 +1,69 @@ +import type { ChannelsImagesProps } from '@rocket.chat/rest-typings'; +import { useUserId, useEndpoint } from '@rocket.chat/ui-contexts'; +import { useCallback, useEffect, useState } from 'react'; + +import { useScrollableRecordList } from '../../../hooks/lists/useScrollableRecordList'; +import { useStreamUpdatesForMessageList } from '../../../hooks/lists/useStreamUpdatesForMessageList'; +import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; +import { ImagesList } from '../../../lib/lists/ImagesList'; +import type { MessageList } from '../../../lib/lists/MessageList'; + +export const useImagesList = ( + options: ChannelsImagesProps, +): { + filesList: ImagesList; + initialItemCount: number; + reload: () => void; + loadMoreItems: (start: number) => void; +} => { + const [filesList, setFilesList] = useState(() => new ImagesList(options)); + const reload = useCallback(() => setFilesList(new ImagesList(options)), [options]); + const uid = useUserId(); + + useComponentDidUpdate(() => { + options && reload(); + }, [options, reload]); + + useEffect(() => { + if (filesList.options !== options) { + filesList.updateFilters(options); + } + }, [filesList, options]); + + const apiEndPoint = '/v1/channels.images'; + + const getFiles = useEndpoint('GET', apiEndPoint); + + const fetchMessages = useCallback( + async (start, end) => { + const { files, total } = await getFiles({ + roomId: options.roomId, + startingFromId: options.startingFromId, + offset: start, + count: end, + }); + + return { + items: files.map((file) => ({ + ...file, + uploadedAt: file.uploadedAt ? new Date(file.uploadedAt) : undefined, + modifiedAt: file.modifiedAt ? new Date(file.modifiedAt) : undefined, + })), + itemCount: total, + }; + }, + [getFiles, options.roomId, options.startingFromId], + ); + + const { loadMoreItems, initialItemCount } = useScrollableRecordList(filesList, fetchMessages, 5); + + // TODO: chapter day : frontend create useStreamUpdatesForUploadList + useStreamUpdatesForMessageList(filesList as unknown as MessageList, uid, options.roomId || null); + + return { + reload, + filesList, + loadMoreItems, + initialItemCount, + }; +}; diff --git a/apps/meteor/client/components/message/content/Attachments.tsx b/apps/meteor/client/components/message/content/Attachments.tsx index b08215c94a5f..8a17f1d98363 100644 --- a/apps/meteor/client/components/message/content/Attachments.tsx +++ b/apps/meteor/client/components/message/content/Attachments.tsx @@ -7,13 +7,14 @@ import AttachmentsItem from './attachments/AttachmentsItem'; type AttachmentsProps = { attachments: MessageAttachmentBase[]; collapsed?: boolean; + id: string | undefined; }; -const Attachments = ({ attachments, collapsed }: AttachmentsProps): ReactElement => { +const Attachments = ({ attachments, collapsed, id }: AttachmentsProps): ReactElement => { return ( <> {attachments?.map((attachment, index) => ( - + ))} ); diff --git a/apps/meteor/client/components/message/content/attachments/AttachmentsItem.tsx b/apps/meteor/client/components/message/content/attachments/AttachmentsItem.tsx index 6ca97fe7ecde..589549d4bcc1 100644 --- a/apps/meteor/client/components/message/content/attachments/AttachmentsItem.tsx +++ b/apps/meteor/client/components/message/content/attachments/AttachmentsItem.tsx @@ -9,11 +9,12 @@ import { QuoteAttachment } from './QuoteAttachment'; type AttachmentsItemProps = { attachment: MessageAttachmentBase; + id: string | undefined; }; -const AttachmentsItem = ({ attachment }: AttachmentsItemProps): ReactElement => { +const AttachmentsItem = ({ attachment, id }: AttachmentsItemProps): ReactElement => { if (isFileAttachment(attachment)) { - return ; + return ; } if (isQuoteAttachment(attachment)) { diff --git a/apps/meteor/client/components/message/content/attachments/FileAttachment.tsx b/apps/meteor/client/components/message/content/attachments/FileAttachment.tsx index 423464b72569..942ace9055d3 100644 --- a/apps/meteor/client/components/message/content/attachments/FileAttachment.tsx +++ b/apps/meteor/client/components/message/content/attachments/FileAttachment.tsx @@ -8,7 +8,7 @@ import { GenericFileAttachment } from './file/GenericFileAttachment'; import { ImageAttachment } from './file/ImageAttachment'; import { VideoAttachment } from './file/VideoAttachment'; -export const FileAttachment: FC = (attachment) => { +export const FileAttachment: FC = (attachment) => { if (isFileImageAttachment(attachment)) { return ; } diff --git a/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx b/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx index 70c29fafacb3..13cd375c7eeb 100644 --- a/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx +++ b/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx @@ -9,7 +9,8 @@ import MessageContentBody from '../../../MessageContentBody'; import AttachmentImage from '../structure/AttachmentImage'; import { useLoadImage } from './hooks/useLoadImage'; -export const ImageAttachment: FC = ({ +export const ImageAttachment: FC = ({ + id, title, image_url: url, image_preview: imagePreview, @@ -38,6 +39,7 @@ export const ImageAttachment: FC = ({ dataSrc={getURL(link || url)} src={getURL(url)} previewUrl={`data:image/png;base64,${imagePreview}`} + id={id} /> diff --git a/apps/meteor/client/components/message/content/attachments/structure/AttachmentImage.tsx b/apps/meteor/client/components/message/content/attachments/structure/AttachmentImage.tsx index 00249bb857fc..a82e3a7daa5e 100644 --- a/apps/meteor/client/components/message/content/attachments/structure/AttachmentImage.tsx +++ b/apps/meteor/client/components/message/content/attachments/structure/AttachmentImage.tsx @@ -14,6 +14,7 @@ type AttachmentImageProps = { src: string; loadImage?: boolean; setLoadImage: () => void; + id: string | undefined; } & Dimensions & ({ loadImage: true } | { loadImage: false; setLoadImage: () => void }); @@ -36,7 +37,7 @@ const getDimensions = ( return { width, height, ratio: (height / width) * 100 }; }; -const AttachmentImage: FC = ({ previewUrl, dataSrc, loadImage = true, setLoadImage, src, ...size }) => { +const AttachmentImage: FC = ({ id, previewUrl, dataSrc, loadImage = true, setLoadImage, src, ...size }) => { const limits = useAttachmentDimensions(); const [error, setError] = useState(false); @@ -77,7 +78,14 @@ const AttachmentImage: FC = ({ previewUrl, dataSrc, loadIm right: 0, }} > - + diff --git a/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx b/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx index b22627bea8d2..7f4cf6694b7a 100644 --- a/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx +++ b/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx @@ -64,7 +64,7 @@ const RoomMessageContent = ({ message, unread, all, mention, searchText }: RoomM )} - {!!normalizedMessage?.attachments?.length && } + {!!normalizedMessage?.attachments?.length && } {oembedEnabled && !!normalizedMessage.urls?.length && } diff --git a/apps/meteor/client/contexts/ImageGalleryContext.ts b/apps/meteor/client/contexts/ImageGalleryContext.ts index 38f53c3f975b..2fea5e46a40f 100644 --- a/apps/meteor/client/contexts/ImageGalleryContext.ts +++ b/apps/meteor/client/contexts/ImageGalleryContext.ts @@ -1,13 +1,13 @@ import { createContext } from 'react'; export type ImageGalleryContextValue = { - imageUrl: string; + imageId: string; isOpen: boolean; onClose: () => void; }; export const ImageGalleryContext = createContext({ - imageUrl: '', + imageId: '', isOpen: false, onClose: () => undefined, }); diff --git a/apps/meteor/client/lib/lists/ImagesList.ts b/apps/meteor/client/lib/lists/ImagesList.ts new file mode 100644 index 000000000000..540d5bb1c0f7 --- /dev/null +++ b/apps/meteor/client/lib/lists/ImagesList.ts @@ -0,0 +1,39 @@ +import type { IUpload } from '@rocket.chat/core-typings'; + +import { RecordList } from './RecordList'; + +type FilesMessage = Omit & Required>; + +export type ImagesListOptions = { + roomId: Required['rid']; + startingFromId: string; + count?: number; + offset?: number; +}; + +const isFileMessageInRoom = (upload: IUpload, rid: IUpload['rid']): upload is FilesMessage => upload.rid === rid && 'rid' in upload; + +export class ImagesList extends RecordList { + public constructor(private _options: ImagesListOptions) { + super(); + } + + public get options(): ImagesListOptions { + return this._options; + } + + public updateFilters(options: ImagesListOptions): void { + this._options = options; + this.clear(); + } + + protected filter(message: IUpload): boolean { + const { roomId } = this._options; + + if (!isFileMessageInRoom(message, roomId)) { + return false; + } + + return true; + } +} diff --git a/apps/meteor/client/providers/ImageGalleryProvider.tsx b/apps/meteor/client/providers/ImageGalleryProvider.tsx index 98e0811010a5..6d14e28c53ce 100644 --- a/apps/meteor/client/providers/ImageGalleryProvider.tsx +++ b/apps/meteor/client/providers/ImageGalleryProvider.tsx @@ -5,41 +5,38 @@ import { ImageGalleryContext } from '../contexts/ImageGalleryContext'; type ImageGalleryProviderProps = { children: ReactNode; - imageSelector?: string; - containerSelector?: string; }; -const ImageGalleryProvider = ({ children, imageSelector, containerSelector }: ImageGalleryProviderProps) => { - const [imageUrl, setImageUrl] = useState(); +const ImageGalleryProvider = ({ children }: ImageGalleryProviderProps) => { + const [imageId, setImageId] = useState(); useEffect(() => { document.addEventListener('click', (event: Event) => { const target = event?.target as HTMLElement | null; - if (target?.classList.contains('gallery-item')) { - return setImageUrl(target.dataset.src || target?.parentElement?.parentElement?.querySelector('img')?.src); + return setImageId(target.dataset.id || target?.parentElement?.parentElement?.dataset.id); } if (target?.classList.contains('gallery-item-container')) { - return setImageUrl((target.querySelector('img.rcx-avatar__element') as HTMLImageElement | null)?.src); + return setImageId(target.dataset.id); } if ( target?.classList.contains('gallery-item') && target?.parentElement?.parentElement?.classList.contains('gallery-item-container') ) { - return setImageUrl((target.parentElement.parentElement.querySelector('img.rcx-avatar__element') as HTMLImageElement | null)?.src); + return setImageId(target.dataset.id || target?.parentElement?.parentElement?.dataset.id); } if (target?.classList.contains('rcx-avatar__element') && target?.parentElement?.classList.contains('gallery-item')) { - return setImageUrl((target as HTMLImageElement).src); + return setImageId(target.dataset.id || target?.parentElement?.parentElement?.dataset.id); } }); - }, [containerSelector, imageSelector]); + }, []); return ( - setImageUrl(undefined) }}> + setImageId(undefined) }}> {children} - {!!imageUrl && } + {!!imageId && } ); }; diff --git a/apps/meteor/client/views/admin/moderation/helpers/ContextMessage.tsx b/apps/meteor/client/views/admin/moderation/helpers/ContextMessage.tsx index 09caf368b793..5d8ecfed60a9 100644 --- a/apps/meteor/client/views/admin/moderation/helpers/ContextMessage.tsx +++ b/apps/meteor/client/views/admin/moderation/helpers/ContextMessage.tsx @@ -77,7 +77,7 @@ const ContextMessage = ({ )} {message.blocks && } - {message.attachments && } + {message.attachments && } diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js index cfed9fabe4b1..30cbce6a14e1 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js @@ -22,7 +22,7 @@ const FileItem = ({ fileData, isDeletionAllowed, onClickDelete }) => { return ( {typeGroup === 'image' ? ( - + ) : ( { +const ImageItem = ({ id, url, name, timestamp, username }: ImageItemProps) => { return ( - + {url && } {name && ( From cd9069d7c1f7295257bcd681e3761b3fa79576c6 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 3 Nov 2023 10:45:03 -0300 Subject: [PATCH 19/21] fix: type --- apps/meteor/client/components/message/content/Attachments.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/components/message/content/Attachments.tsx b/apps/meteor/client/components/message/content/Attachments.tsx index 8a17f1d98363..2c1b6675cb7b 100644 --- a/apps/meteor/client/components/message/content/Attachments.tsx +++ b/apps/meteor/client/components/message/content/Attachments.tsx @@ -7,7 +7,7 @@ import AttachmentsItem from './attachments/AttachmentsItem'; type AttachmentsProps = { attachments: MessageAttachmentBase[]; collapsed?: boolean; - id: string | undefined; + id?: string | undefined; }; const Attachments = ({ attachments, collapsed, id }: AttachmentsProps): ReactElement => { From 8b36bea75694948e74d9f86c7b238ecbce4f1161 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 4 Dec 2023 12:44:32 -0300 Subject: [PATCH 20/21] remove photoswipe deps --- apps/meteor/.meteor/packages | 1 - apps/meteor/.meteor/versions | 1 - .../emoji-custom/client/lib/emojiCustom.js | 44 -- apps/meteor/app/theme/client/main.css | 1 - .../app/theme/client/vendor/photoswipe.css | 422 ------------------ apps/meteor/package.json | 3 - yarn.lock | 38 +- 7 files changed, 7 insertions(+), 503 deletions(-) delete mode 100644 apps/meteor/app/theme/client/vendor/photoswipe.css diff --git a/apps/meteor/.meteor/packages b/apps/meteor/.meteor/packages index ae788af78034..e99aa0bdfb67 100644 --- a/apps/meteor/.meteor/packages +++ b/apps/meteor/.meteor/packages @@ -72,6 +72,5 @@ autoupdate@1.8.0 # photoswipe -jquery zodern:types zodern:standard-minifier-js diff --git a/apps/meteor/.meteor/versions b/apps/meteor/.meteor/versions index 66f61e2cd8cc..6dab889e3e38 100644 --- a/apps/meteor/.meteor/versions +++ b/apps/meteor/.meteor/versions @@ -43,7 +43,6 @@ hot-code-push@1.0.4 http@2.0.0 id-map@1.1.1 inter-process-messaging@0.1.1 -jquery@3.0.0 kadira:flow-router@2.12.1 localstorage@1.2.0 logging@1.3.2 diff --git a/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js b/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js index 1340ead95789..2508ed777548 100644 --- a/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js +++ b/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js @@ -45,9 +45,6 @@ export const deleteEmojiCustom = function (emojiData) { }; export const updateEmojiCustom = function (emojiData) { - let key = `emoji_random_${emojiData.name}`; - Session.set(key, Math.round(Math.random() * 1000)); - const previousExists = isSetNotNull(() => emojiData.previousName); const currentAliases = isSetNotNull(() => emojiData.aliases); @@ -88,47 +85,6 @@ export const updateEmojiCustom = function (emojiData) { } } - const url = getEmojiUrlFromName(emojiData.name, emojiData.extension); - - // update in admin interface - if (previousExists && emojiData.name !== emojiData.previousName) { - $(document) - .find(`.emojiAdminPreview-image[data-emoji='${emojiData.previousName}']`) - .css('background-image', `url('${url})'`) - .attr('data-emoji', `${emojiData.name}`); - } else { - $(document).find(`.emojiAdminPreview-image[data-emoji='${emojiData.name}']`).css('background-image', `url('${url}')`); - } - - // update in picker - if (previousExists && emojiData.name !== emojiData.previousName) { - $(document) - .find(`li[data-emoji='${emojiData.previousName}'] span`) - .css('background-image', `url('${url}')`) - .attr('data-emoji', `${emojiData.name}`); - $(document) - .find(`li[data-emoji='${emojiData.previousName}']`) - .attr('data-emoji', `${emojiData.name}`) - .attr('class', `emoji-${emojiData.name}`); - } else { - $(document).find(`li[data-emoji='${emojiData.name}'] span`).css('background-image', `url('${url}')`); - } - - // update in picker and opened rooms - for (key in LegacyRoomManager.openedRooms) { - if (LegacyRoomManager.openedRooms.hasOwnProperty(key)) { - const room = LegacyRoomManager.openedRooms[key]; - if (previousExists && emojiData.name !== emojiData.previousName) { - $(room.dom) - .find(`span[data-emoji='${emojiData.previousName}']`) - .css('background-image', `url('${url}')`) - .attr('data-emoji', `${emojiData.name}`); - } else { - $(room.dom).find(`span[data-emoji='${emojiData.name}']`).css('background-image', `url('${url}')`); - } - } - } - updateRecent('rocket'); }; diff --git a/apps/meteor/app/theme/client/main.css b/apps/meteor/app/theme/client/main.css index 6c0a20d844bb..33b7a8f0d290 100644 --- a/apps/meteor/app/theme/client/main.css +++ b/apps/meteor/app/theme/client/main.css @@ -21,7 +21,6 @@ /* Legacy theming */ @import 'imports/general/theme_old.css'; -@import './vendor/photoswipe.css'; @import './vendor/fontello/css/fontello.css'; @import './rocketchat.font.css'; @import './mentionLink.css'; diff --git a/apps/meteor/app/theme/client/vendor/photoswipe.css b/apps/meteor/app/theme/client/vendor/photoswipe.css deleted file mode 100644 index 53f352ff7018..000000000000 --- a/apps/meteor/app/theme/client/vendor/photoswipe.css +++ /dev/null @@ -1,422 +0,0 @@ -.pswp__button { - position: relative; - - display: block; - float: right; - overflow: visible; - - width: 44px; - height: 44px; - margin: 0; - padding: 0; - - cursor: pointer; - transition: opacity 0.2s; - - opacity: 0.75; - border: 0; - background: none; - box-shadow: none; - appearance: none; - - &:focus, - &:hover { - opacity: 1; - } - - &:active { - opacity: 0.9; - outline: none; - } - - &::-moz-focus-inner { - padding: 0; - - border: 0; - } -} - -.pswp__ui--over-close .pswp__button--close { - opacity: 1; -} - -.pswp__button, -.pswp__button--arrow--left::before, -.pswp__button--arrow--right::before { - width: 44px; - height: 44px; - - background: url(/images/gallery-skin.svg) 0 0 no-repeat; - background-size: 264px 88px; -} - -@media (-webkit-min-device-pixel-ratio: 1.1), - (-webkit-min-device-pixel-ratio: 1.09375), - (min-resolution: 105dpi), - (min-resolution: 1.1dppx) { - .pswp--svg .pswp__button--arrow--left, - .pswp--svg .pswp__button--arrow--right { - background: none; - } -} - -.pswp__button--close { - background-position: 0 -44px; -} - -.pswp__button--share { - background-position: -44px -44px; -} - -.pswp__button--fs { - display: none; -} - -.pswp--supports-fs .pswp__button--fs { - display: block; -} - -.pswp--fs .pswp__button--fs { - background-position: -44px 0; -} - -.pswp__button--zoom { - display: none; - - background-position: -88px 0; -} - -.pswp--zoom-allowed .pswp__button--zoom { - display: block; -} - -.pswp--zoomed-in .pswp__button--zoom { - background-position: -132px 0; -} - -.pswp--touch .pswp__button--arrow--left, -.pswp--touch .pswp__button--arrow--right { - visibility: hidden; -} - -.pswp__button--arrow--left, -.pswp__button--arrow--right { - position: absolute; - top: 50%; - - width: 70px; - height: 100px; - margin-top: -50px; - - background: none; -} - -.pswp__button--arrow--left { - left: 0; -} - -.pswp__button--arrow--right { - right: 0; -} - -.pswp__button--arrow--left::before, -.pswp__button--arrow--right::before { - position: absolute; - top: 35px; - - width: 32px; - height: 30px; - - content: ''; - - background-color: rgba(0, 0, 0, 0.3); -} - -.pswp__button--arrow--left::before { - left: 6px; - - background-position: -138px -44px; -} - -.pswp__button--arrow--right::before { - right: 6px; - - background-position: -94px -44px; -} - -/* - - 4. Caption - - */ -.pswp__caption { - position: absolute; - bottom: 0; - left: 0; - - width: 100%; - min-height: 44px; - - & small { - color: #bbbbbb; - - font-size: 11px; - } -} - -.pswp__caption__center { - max-width: 420px; - margin: 0 auto; - padding: 10px; - - text-align: left; - - color: #cccccc; - - font-size: 13px; - line-height: 20px; -} - -.pswp__caption--empty { - display: none; -} - -/* Fake caption element, used to calculate height of next/prev image */ -.pswp__caption--fake { - visibility: hidden; -} - -/* - - 5. Loading indicator (preloader) - - You can play with it here - http://codepen.io/dimsemenov/pen/yyBWoR - - */ -.pswp__preloader { - position: absolute; - top: 0; - left: 50%; - - width: 44px; - height: 44px; - margin-left: -22px; - - transition: opacity 0.25s ease-out; - - opacity: 0; - direction: ltr; - will-change: opacity; -} - -.pswp__preloader__icn { - width: 20px; - height: 20px; - margin: 12px; -} - -.pswp__preloader--active { - opacity: 1; -} - -.pswp--css_animation { - & .pswp__preloader--active { - opacity: 1; - - & .pswp__preloader__icn { - position: absolute; - top: 15px; - left: 15px; - - width: 14px; - height: 14px; - margin: 0; - - animation: clockwise 500ms linear infinite; - - opacity: 0.75; - background: none; - } - - & .pswp__preloader__donut { - position: absolute; - top: 0; - left: 0; - - box-sizing: border-box; - width: 14px; - height: 14px; - margin: 0; - - animation: donut-rotate 1000ms cubic-bezier(0.4, 0, 0.22, 1) infinite; - - border: 1px solid #ffffff; - border-bottom-color: transparent; - border-left-color: transparent; - border-radius: 50%; - background: none; - } - - & .pswp__preloader__cut { - /* - The idea of animating inner circle is based on Polymer ("material") loading indicator - by Keanu Lee https://blog.keanulee.com/2014/10/20/the-tale-of-three-spinners.html - */ - position: relative; - - overflow: hidden; - - width: 7px; - height: 14px; - } - } -} - -@media screen and (max-width: 1024px) { - .pswp__preloader { - position: relative; - top: auto; - left: auto; - - float: right; - - margin: 0; - } -} - -@-webkit-keyframes clockwise { - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(360deg); - } -} - -@keyframes clockwise { - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(360deg); - } -} - -@-webkit-keyframes donut-rotate { - 0% { - transform: rotate(0); - } - - 50% { - transform: rotate(-140deg); - } - - 100% { - transform: rotate(0); - } -} - -@keyframes donut-rotate { - 0% { - transform: rotate(0); - } - - 50% { - transform: rotate(-140deg); - } - - 100% { - transform: rotate(0); - } -} - -/* - - 6. Additional styles - - */ - -/* root element of UI */ -.pswp__ui { - z-index: 1550; - - visibility: visible; - - opacity: 1; - -webkit-font-smoothing: auto; -} - -/* top black bar with buttons and "1 of X" indicator */ -.pswp__top-bar { - position: absolute; - top: 0; - left: 0; - - width: 100%; - height: 44px; -} - -.pswp__caption, -.pswp__top-bar, -.pswp--has_mouse .pswp__button--arrow--left, -.pswp--has_mouse .pswp__button--arrow--right { - transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1); - -webkit-backface-visibility: hidden; - will-change: opacity; -} - -/* pswp--has_mouse class is added only when two subsequent mousemove events occur */ -.pswp--has_mouse .pswp__button--arrow--left, -.pswp--has_mouse .pswp__button--arrow--right { - visibility: visible; -} - -.pswp__top-bar, -.pswp__caption { - background-color: rgba(0, 0, 0, 0.5); -} - -/* pswp__ui--fit class is added when main image "fits" between top bar and bottom bar (caption) */ -.pswp__ui--fit .pswp__top-bar, -.pswp__ui--fit .pswp__caption { - background-color: rgba(0, 0, 0, 0.3); -} - -/* pswp__ui--idle class is added when mouse isn't moving for several seconds (JS option timeToIdle) */ -.pswp__ui--idle .pswp__top-bar { - opacity: 0; -} - -.pswp__ui--idle .pswp__button--arrow--left, -.pswp__ui--idle .pswp__button--arrow--right { - opacity: 0; -} - -/* - pswp__ui--hidden class is added when controls are hidden - e.g. when user taps to toggle visibility of controls -*/ -.pswp__ui--hidden .pswp__top-bar, -.pswp__ui--hidden .pswp__caption, -.pswp__ui--hidden .pswp__button--arrow--left, -.pswp__ui--hidden .pswp__button--arrow--right { - /* Force paint & create composition layer for controls. */ - opacity: 0.001; -} - -/* pswp__ui--one-slide class is added when there is just one item in gallery */ -.pswp__ui--one-slide .pswp__button--arrow--left, -.pswp__ui--one-slide .pswp__button--arrow--right, -.pswp__ui--one-slide .pswp__counter { - display: none; -} - -.pswp__element--disabled { - display: none !important; -} - -.pswp--minimal--dark .pswp__top-bar { - background: none; -} diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 135e71d2c090..fb5204d1ed4b 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -135,7 +135,6 @@ "@types/nodemailer": "^6.4.13", "@types/oauth2-server": "^3.0.15", "@types/parseurl": "^1.3.2", - "@types/photoswipe": "^4.1.5", "@types/prometheus-gc-stats": "^0.6.3", "@types/proxyquire": "^1.3.30", "@types/psl": "^1.1.2", @@ -351,7 +350,6 @@ "ip-range-check": "^0.2.0", "is-svg": "^4.3.2", "isolated-vm": "4.4.2", - "jquery": "^3.6.0", "jschardet": "^3.0.0", "jsdom": "^16.7.0", "jsrsasign": "^10.5.24", @@ -388,7 +386,6 @@ "path": "^0.12.7", "path-to-regexp": "^6.2.1", "pdfjs-dist": "^2.13.216", - "photoswipe": "^4.1.3", "pino": "^8.15.0", "postis": "^2.2.0", "prom-client": "^14.0.1", diff --git a/yarn.lock b/yarn.lock index 5b64eedcdf9b..cd3958856cfc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8217,9 +8217,9 @@ __metadata: "@rocket.chat/icons": "*" "@rocket.chat/prettier-config": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-contexts": 3.0.0-rc.14 + "@rocket.chat/ui-contexts": 3.0.0 "@rocket.chat/ui-kit": "*" - "@rocket.chat/ui-video-conf": 3.0.0-rc.14 + "@rocket.chat/ui-video-conf": 3.0.0 "@tanstack/react-query": "*" react: "*" react-dom: "*" @@ -8307,8 +8307,8 @@ __metadata: "@rocket.chat/fuselage-tokens": "*" "@rocket.chat/message-parser": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-client": 3.0.0-rc.14 - "@rocket.chat/ui-contexts": 3.0.0-rc.14 + "@rocket.chat/ui-client": 3.0.0 + "@rocket.chat/ui-contexts": 3.0.0 katex: "*" react: "*" languageName: unknown @@ -8697,7 +8697,6 @@ __metadata: "@types/oauth2-server": ^3.0.15 "@types/object-path": ^0.11.3 "@types/parseurl": ^1.3.2 - "@types/photoswipe": ^4.1.5 "@types/prometheus-gc-stats": ^0.6.3 "@types/proxy-from-env": ^1.0.3 "@types/proxyquire": ^1.3.30 @@ -8811,7 +8810,6 @@ __metadata: is-svg: ^4.3.2 isolated-vm: 4.4.2 jest: ~29.6.4 - jquery: ^3.6.0 jschardet: ^3.0.0 jsdom: ^16.7.0 jsdom-global: ^3.0.2 @@ -8852,7 +8850,6 @@ __metadata: path: ^0.12.7 path-to-regexp: ^6.2.1 pdfjs-dist: ^2.13.216 - photoswipe: ^4.1.3 pino: ^8.15.0 pino-pretty: ^7.6.1 playwright-qase-reporter: ^1.2.1 @@ -9471,7 +9468,7 @@ __metadata: "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-hooks": "*" "@rocket.chat/icons": "*" - "@rocket.chat/ui-contexts": 3.0.0-rc.14 + "@rocket.chat/ui-contexts": 3.0.0 react: ~17.0.2 languageName: unknown linkType: soft @@ -9624,7 +9621,7 @@ __metadata: "@rocket.chat/fuselage-hooks": "*" "@rocket.chat/icons": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-contexts": 3.0.0-rc.14 + "@rocket.chat/ui-contexts": 3.0.0 react: ^17.0.2 react-dom: ^17.0.2 languageName: unknown @@ -9710,7 +9707,7 @@ __metadata: peerDependencies: "@rocket.chat/layout": "*" "@rocket.chat/tools": "*" - "@rocket.chat/ui-contexts": 3.0.0-rc.14 + "@rocket.chat/ui-contexts": 3.0.0 "@tanstack/react-query": "*" react: "*" react-hook-form: "*" @@ -12863,13 +12860,6 @@ __metadata: languageName: node linkType: hard -"@types/photoswipe@npm:^4.1.5": - version: 4.1.5 - resolution: "@types/photoswipe@npm:4.1.5" - checksum: a6b5fd6b029b8e84bc43f680549abf33dbc02e551e8dd176e29ca82e80edf66d0b62777fd42fe415d9e52b7a570718cac684aaea544f8dc084d1cb8bcc422f0f - languageName: node - linkType: hard - "@types/polka@npm:^0.5.6": version: 0.5.6 resolution: "@types/polka@npm:0.5.6" @@ -26298,13 +26288,6 @@ __metadata: languageName: node linkType: hard -"jquery@npm:^3.6.0": - version: 3.6.0 - resolution: "jquery@npm:3.6.0" - checksum: 8fd5fef4aa48fd374ec716dd1c1df1af407814a228e15c1260ca140de3a697c2a77c30c54ff1d238b6a3ab4ddc445ddeef9adce6c6d28e4869d85eb9d3951c0e - languageName: node - linkType: hard - "js-git@npm:^0.7.8": version: 0.7.8 resolution: "js-git@npm:0.7.8" @@ -31019,13 +31002,6 @@ __metadata: languageName: node linkType: hard -"photoswipe@npm:^4.1.3": - version: 4.1.3 - resolution: "photoswipe@npm:4.1.3" - checksum: 2eecc188d81642832cd5086e3954a6b5546b70651b6a76752625d9ff7b5e63ede9347316edc8110d7dba90acf55d2e8b2e917f454ed46c2594b09084c383aa37 - languageName: node - linkType: hard - "picocolors@npm:^0.2.1": version: 0.2.1 resolution: "picocolors@npm:0.2.1" From 72934b1f8ffa826de5d7b0d496c0c36b58cc6563 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Mon, 4 Dec 2023 13:42:11 -0300 Subject: [PATCH 21/21] fix: remove unused imports --- apps/meteor/app/emoji-custom/client/lib/emojiCustom.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js b/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js index 2508ed777548..64f1df9bd932 100644 --- a/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js +++ b/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js @@ -1,11 +1,9 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; -import $ from 'jquery'; import { Meteor } from 'meteor/meteor'; import { Session } from 'meteor/session'; import { emoji, updateRecent } from '../../../emoji/client'; import { CachedCollectionManager } from '../../../ui-cached-collection/client'; -import { LegacyRoomManager } from '../../../ui-utils/client'; import { getURL } from '../../../utils/client'; import { sdk } from '../../../utils/client/lib/SDKClient'; import { isSetNotNull } from './function-isSet';