From 5dab788680e00534a29f8c021023c09623dbe644 Mon Sep 17 00:00:00 2001 From: gopienkonikita Date: Fri, 22 Sep 2023 15:10:48 +0300 Subject: [PATCH] Web: Files: InfoPanel: added infinite-loader to members list --- .../client/public/locales/en/InfoPanel.json | 6 +- .../panels/InvitePanel/StyledInvitePanel.js | 7 + .../components/panels/InvitePanel/index.js | 51 ++-- .../sub-components/AccessSelector.js | 5 +- .../sub-components/ExternalLinks.js | 64 ++--- .../InvitePanel/sub-components/InfoBar.js | 80 ++++++ .../panels/InvitePanel/sub-components/Item.js | 1 + .../Home/InfoPanel/Body/styles/common.js | 19 ++ .../Home/InfoPanel/Body/styles/members.js | 34 +-- .../ItemTitle/FilesItemTitle.js | 107 ++++++-- .../ItemTitle/ItemContextOptions.js | 15 +- .../Body/sub-components/ItemTitle/index.js | 1 - .../Body/views/Members/MembersList.js | 139 ++++++++++ .../Home/InfoPanel/Body/views/Members/User.js | 24 +- .../InfoPanel/Body/views/Members/index.js | 238 +++++++----------- .../Members/sub-components/PublicRoomBar.js | 1 - .../sub-components/StyledPublicRoom.js | 4 + .../src/pages/Home/InfoPanel/Header/index.js | 2 + .../TilesView/sub-components/TileContainer.js | 32 +-- packages/client/src/store/FilesStore.js | 52 +++- packages/common/api/rooms/index.js | 20 +- packages/components/submenu/index.js | 12 +- packages/components/submenu/styled-submenu.js | 7 + public/images/info.outline.react.svg | 14 +- yarn.lock | 15 +- 25 files changed, 639 insertions(+), 311 deletions(-) create mode 100644 packages/client/src/components/panels/InvitePanel/sub-components/InfoBar.js create mode 100644 packages/client/src/pages/Home/InfoPanel/Body/views/Members/MembersList.js diff --git a/packages/client/public/locales/en/InfoPanel.json b/packages/client/public/locales/en/InfoPanel.json index 2c92ac3d04..e4fb6a9461 100644 --- a/packages/client/public/locales/en/InfoPanel.json +++ b/packages/client/public/locales/en/InfoPanel.json @@ -39,5 +39,9 @@ "SubmenuHistory": "History", "SystemProperties": "System properties", "UsersInRoom": "Users in room", - "Versions": "Versions" + "Versions": "Versions", + "Administration": "Administration", + "Users": "Users", + "ExpectUsers": "Expect users", + "InfoBanner": "The list of invited users includes the owner and/or admins of this DocSpace with full access to all rooms. The owner and/or administrator cannot be assigned other access rights. Once added to the room, they will be notified of all changes." } diff --git a/packages/client/src/components/panels/InvitePanel/StyledInvitePanel.js b/packages/client/src/components/panels/InvitePanel/StyledInvitePanel.js index 48c8400c47..65c943b0c6 100644 --- a/packages/client/src/components/panels/InvitePanel/StyledInvitePanel.js +++ b/packages/client/src/components/panels/InvitePanel/StyledInvitePanel.js @@ -181,6 +181,13 @@ const StyledRow = styled.div` margin-right: 0; `} } + + .combo-button-label { + color: ${(props) => props.theme.text.disableColor}; + } + .combo-buttons_expander-icon path { + fill: ${(props) => props.theme.text.disableColor}; + } `; const StyledInviteInput = styled.div` diff --git a/packages/client/src/components/panels/InvitePanel/index.js b/packages/client/src/components/panels/InvitePanel/index.js index d3d08a9f26..96002da9f8 100644 --- a/packages/client/src/components/panels/InvitePanel/index.js +++ b/packages/client/src/components/panels/InvitePanel/index.js @@ -24,6 +24,9 @@ import InviteInput from "./sub-components/InviteInput"; import ExternalLinks from "./sub-components/ExternalLinks"; import Scrollbar from "@docspace/components/scrollbar"; import { LinkType } from "../../../helpers/constants"; + +import InfoBar from "./sub-components/InfoBar"; + const InvitePanel = ({ folders, getFolderInfo, @@ -57,9 +60,12 @@ const InvitePanel = ({ const [externalLinksVisible, setExternalLinksVisible] = useState(false); const [scrollAllPanelContent, setScrollAllPanelContent] = useState(false); const [activeLink, setActiveLink] = useState({}); + const [infoBarIsVisible, setInfoBarIsVisible] = useState(true); const [addUsersPanelVisible, setAddUsersPanelVisible] = useState(false); const [isMobileView, setIsMobileView] = useState(isMobileOnly); + const onCloseBar = () => setInfoBarIsVisible(false); + const inputsRef = useRef(); const invitePanelBodyRef = useRef(); @@ -67,10 +73,6 @@ const InvitePanel = ({ setExternalLinksVisible(visible); }; - const onChangeActiveLink = (activeLink) => { - setActiveLink(activeLink); - }; - const selectRoom = () => { const room = folders.find((folder) => folder.id === roomId); @@ -84,26 +86,26 @@ const InvitePanel = ({ }; const getInfo = () => { - getRoomSecurityInfo(roomId).then((users) => { - let links = []; + getRoomSecurityInfo(roomId).then((links) => { + const link = links[0]; + if (link) { + const { shareLink, id, title, expirationDate } = link.sharedTo; - users.map((user) => { - const { shareLink, id, title, expirationDate, linkType } = - user.sharedTo; + const activeLink = { + id, + title, + shareLink, + expirationDate, + access: link.access || defaultAccess, + }; - if (!!shareLink && linkType === LinkType.Invite) { - links.push({ - id, - title, - shareLink, - expirationDate, - access: user.access || defaultAccess, - }); - } - }); + onChangeExternalLinksVisible(!!links.length); - setShareLinks(links); - setRoomUsers(users); + setShareLinks([activeLink]); + setActiveLink(activeLink); + } + + // setRoomUsers(users); // TODO: }); }; @@ -241,6 +243,7 @@ const InvitePanel = ({ const roomType = selectedRoom ? selectedRoom.roomType : -1; const hasInvitedUsers = !!inviteItems.length; + const hasAdmins = inviteItems.findIndex((u) => u.isAdmin || u.isOwner) > -1; const bodyInvitePanel = useMemo(() => { return ( @@ -248,11 +251,12 @@ const InvitePanel = ({ @@ -267,6 +271,9 @@ const InvitePanel = ({ setAddUsersPanelVisible={setAddUsersPanelVisible} isMobileView={isMobileView} /> + {infoBarIsVisible && hasAdmins && ( + + )} {hasInvitedUsers && ( { const [horizontalOrientation, setHorizontalOrientation] = useState(false); const width = containerRef?.current?.offsetWidth - 32; @@ -64,7 +65,7 @@ const AccessSelector = ({ selectedOption={selectedOption} onSelect={onSelectAccess} accessOptions={filteredAccesses ? filteredAccesses : accessOptions} - noBorder={false} + noBorder={noBorder} directionX="right" directionY="bottom" fixedDirection={true} @@ -82,7 +83,7 @@ const AccessSelector = ({ selectedOption={selectedOption} onSelect={onSelectAccess} accessOptions={filteredAccesses ? filteredAccesses : accessOptions} - noBorder={false} + noBorder={noBorder} directionX="right" directionY="top" fixedDirection={true} diff --git a/packages/client/src/components/panels/InvitePanel/sub-components/ExternalLinks.js b/packages/client/src/components/panels/InvitePanel/sub-components/ExternalLinks.js index d4ad7ae80d..6a603494b5 100644 --- a/packages/client/src/components/panels/InvitePanel/sub-components/ExternalLinks.js +++ b/packages/client/src/components/panels/InvitePanel/sub-components/ExternalLinks.js @@ -1,6 +1,6 @@ import MediaDownloadReactSvgUrl from "PUBLIC_DIR/images/media.download.react.svg?url"; import CopyReactSvgUrl from "PUBLIC_DIR/images/copy.react.svg?url"; -import React, { useState, useEffect, useRef, useCallback } from "react"; +import React, { useState, useRef, useCallback } from "react"; import { inject, observer } from "mobx-react"; import copy from "copy-to-clipboard"; @@ -29,12 +29,12 @@ const ExternalLinks = ({ roomType, defaultAccess, shareLinks, + setShareLinks, setInvitationLinks, isOwner, - getInfo, onChangeExternalLinksVisible, externalLinksVisible, - onChangeActiveLink, + setActiveLink, activeLink, isMobileView, }) => { @@ -42,44 +42,32 @@ const ExternalLinks = ({ const inputsRef = useRef(); - useEffect(() => { - if (shareLinks[0]?.expirationDate) toggleLinks(false); - }, [shareLinks]); - - const toggleLinks = (withCopy = true) => { - let link = null; - if (!shareLinks.length) return; - - if (roomId === -1) { - link = shareLinks.find((l) => l.access === +defaultAccess); - - onChangeActiveLink(link); - } else { - link = shareLinks[0]; - - !externalLinksVisible ? editLink() : disableLink(); - } - + const toggleLinks = () => { + !externalLinksVisible ? editLink() : disableLink(); onChangeExternalLinksVisible(!externalLinksVisible); - - if (!externalLinksVisible && withCopy) copyLink(link?.shareLink); }; const disableLink = () => { - setInvitationLinks(roomId, shareLinks[0].id, "Invite", 0); - setTimeout(() => getInfo(), 100); + setInvitationLinks(roomId, "Invite", 0, shareLinks[0].id); + setShareLinks([]); }; - const editLink = () => { - if (!shareLinks[0].expirationDate) { - setInvitationLinks( - roomId, - shareLinks[0].id, - "Invite", - shareLinks[0].access - ); - } - onChangeActiveLink(shareLinks[0]); + const editLink = async () => { + const link = await setInvitationLinks(roomId, "Invite", 2); + + const { shareLink, id, title, expirationDate } = link.sharedTo; + + const activeLink = { + id, + title, + shareLink, + expirationDate, + access: link.access || defaultAccess, + }; + + copyLink(shareLink); + setShareLinks([activeLink]); + setActiveLink(activeLink); }; const onSelectAccess = (access) => { @@ -87,12 +75,12 @@ const ExternalLinks = ({ if (roomId === -1) { link = shareLinks.find((l) => l.access === access.access); - onChangeActiveLink(link); + setActiveLink(link); } else { - setInvitationLinks(roomId, shareLinks[0].id, "Invite", +access.access); + setInvitationLinks(roomId, "Invite", +access.access, shareLinks[0].id); link = shareLinks[0]; - onChangeActiveLink(shareLinks[0]); + setActiveLink(shareLinks[0]); } copyLink(link.shareLink); diff --git a/packages/client/src/components/panels/InvitePanel/sub-components/InfoBar.js b/packages/client/src/components/panels/InvitePanel/sub-components/InfoBar.js new file mode 100644 index 0000000000..4709ea0f4e --- /dev/null +++ b/packages/client/src/components/panels/InvitePanel/sub-components/InfoBar.js @@ -0,0 +1,80 @@ +import React from "react"; +import { ReactSVG } from "react-svg"; +import styled from "styled-components"; +import InfoIcon from "PUBLIC_DIR/images/info.outline.react.svg?url"; +import CrossReactSvg from "PUBLIC_DIR/images/cross.react.svg?url"; +import IconButton from "@docspace/components/icon-button"; +import Text from "@docspace/components/text"; + +const StyledInfoBar = styled.div` + display: flex; + background-color: #f8f9f9; + color: #333; + font-size: 12px; + padding: 12px 16px; + border-radius: 6px; + margin-bottom: 10px; + margin: -4px 16px 20px; + .text-container { + width: 100%; + display: flex; + flex-direction: column; + } + .header-body { + display: flex; + height: fit-content; + width: 100%; + gap: 8px; + font-weight: 600; + .header-icon { + svg { + path { + fill: #ed7309; + } + } + } + } + .body-container { + color: #555f65; + font-weight: 400; + } + .close-icon { + margin: 3px 1px 0px 0px; + path { + fill: ${({ theme }) => theme.iconButton.color}; + } + svg { + weight: 10px; + height: 10px; + } + } +`; + +const InfoBar = (props) => { + const { t, iconName, onClose, ...rest } = props; + + return ( + +
+
+
+ +
+ + {t("Common:Info")} + +
+
{t("InfoPanel:InfoBanner")}
+
+ + +
+ ); +}; + +export default InfoBar; diff --git a/packages/client/src/components/panels/InvitePanel/sub-components/Item.js b/packages/client/src/components/panels/InvitePanel/sub-components/Item.js index 127d0c2f15..7c0924e1af 100644 --- a/packages/client/src/components/panels/InvitePanel/sub-components/Item.js +++ b/packages/client/src/components/panels/InvitePanel/sub-components/Item.js @@ -149,6 +149,7 @@ const Item = ({ filteredAccesses={filteredAccesses} setIsOpenItemAccess={setIsOpenItemAccess} isMobileView={isMobileView} + noBorder /> )} diff --git a/packages/client/src/pages/Home/InfoPanel/Body/styles/common.js b/packages/client/src/pages/Home/InfoPanel/Body/styles/common.js index 717c0332c3..0c4d7e8689 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/styles/common.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/styles/common.js @@ -63,6 +63,25 @@ const StyledTitle = styled.div` align-items: center; height: 32px; + .info_title-icons { + display: flex; + margin-left: auto; + gap: 14px; + .icon { + cursor: pointer; + path, + rect { + fill: ${(props) => props.theme.infoPanel.members.iconColor}; + } + &:hover { + path, + rect { + fill: ${(props) => props.theme.infoPanel.members.iconHoverColor}; + } + } + } + } + img { &.icon { display: flex; diff --git a/packages/client/src/pages/Home/InfoPanel/Body/styles/members.js b/packages/client/src/pages/Home/InfoPanel/Body/styles/members.js index a84b5fd710..476cd7fce2 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/styles/members.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/styles/members.js @@ -6,30 +6,18 @@ const StyledUserTypeHeader = styled.div` display: flex; align-items: center; justify-content: space-between; - padding-top: ${props => (props.isExpect ? "20px" : "8px")}; + padding-top: ${(props) => (props.isExpect ? "20px" : "16px")}; padding-bottom: 12px; .title { font-weight: 600; font-size: 14px; line-height: 20px; - color: ${props => props.theme.infoPanel.members.subtitleColor}; + color: ${(props) => props.theme.infoPanel.members.subtitleColor}; } .icon { - cursor: pointer; - - path, - rect { - fill: ${props => props.theme.infoPanel.members.iconColor}; - } - - &:hover { - path, - rect { - fill: ${props => props.theme.infoPanel.members.iconHoverColor}; - } - } + margin-right: 8px; } `; @@ -56,7 +44,7 @@ const StyledUser = styled.div` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - ${props => + ${(props) => props.isExpect && `color: ${props.theme.infoPanel.members.isExpectName}`}; } @@ -64,8 +52,8 @@ const StyledUser = styled.div` font-weight: 600; font-size: 14px; line-height: 16px; - color: ${props => props.theme.infoPanel.members.meLabelColor}; - ${props => + color: ${(props) => props.theme.infoPanel.members.meLabelColor}; + ${(props) => props.theme.interfaceDirection === "rtl" ? css` margin-right: -8px; @@ -76,7 +64,7 @@ const StyledUser = styled.div` } .role-wrapper { - ${props => + ${(props) => props.theme.interfaceDirection === "rtl" ? css` padding-right: 8px; @@ -93,8 +81,10 @@ const StyledUser = styled.div` white-space: nowrap; .disabled-role-combobox { - color: ${props => + color: ${(props) => props.theme.infoPanel.members.disabledRoleSelectorColor}; + + margin-right: 16px; } } @@ -102,14 +92,14 @@ const StyledUser = styled.div` cursor: pointer; svg { path { - fill: ${props => props.theme.iconButton.color}; + fill: ${(props) => props.theme.iconButton.color}; } } :hover { svg { path { - fill: ${props => props.theme.iconButton.hoverColor}; + fill: ${(props) => props.theme.iconButton.hoverColor}; } } } diff --git a/packages/client/src/pages/Home/InfoPanel/Body/sub-components/ItemTitle/FilesItemTitle.js b/packages/client/src/pages/Home/InfoPanel/Body/sub-components/ItemTitle/FilesItemTitle.js index 2b82158909..dd912f1038 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/sub-components/ItemTitle/FilesItemTitle.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/sub-components/ItemTitle/FilesItemTitle.js @@ -1,16 +1,26 @@ import React, { useRef } from "react"; import { inject, observer } from "mobx-react"; import { withTranslation } from "react-i18next"; -import { ReactSVG } from "react-svg"; - -import { Text } from "@docspace/components"; - +import PersonPlusReactSvgUrl from "PUBLIC_DIR/images/person+.react.svg?url"; +import IconButton from "@docspace/components/icon-button"; +import Text from "@docspace/components/text"; import ItemContextOptions from "./ItemContextOptions"; - import { StyledTitle } from "../../styles/common"; import RoomIcon from "@docspace/client/src/components/RoomIcon"; -const FilesItemTitle = ({ t, selection, isSeveralItems }) => { +import { RoomsType, ShareAccessRights } from "@docspace/common/constants"; + +const FilesItemTitle = ({ + t, + selection, + isSeveralItems, + selectionParentRoom, + setIsMobileHidden, + isGracePeriod, + setInvitePanelOptions, + setInviteUsersWarningDialogVisible, + isPublicRoomType, +}) => { const itemTitleRef = useRef(); if (isSeveralItems) return <>; @@ -18,6 +28,27 @@ const FilesItemTitle = ({ t, selection, isSeveralItems }) => { const icon = selection.icon; const isLoadedRoomIcon = !!selection.logo?.medium; const showDefaultRoomIcon = !isLoadedRoomIcon && selection.isRoom; + const security = selectionParentRoom ? selectionParentRoom.security : {}; + const canInviteUserInRoomAbility = security?.EditAccess; + + const onClickInviteUsers = () => { + setIsMobileHidden(true); + const parentRoomId = selectionParentRoom.id; + + if (isGracePeriod) { + setInviteUsersWarningDialogVisible(true); + return; + } + + setInvitePanelOptions({ + visible: true, + roomId: parentRoomId, + hideSelector: false, + defaultAccess: isPublicRoomType + ? ShareAccessRights.RoomManager + : ShareAccessRights.ReadOnly, + }); + }; return ( @@ -37,21 +68,57 @@ const FilesItemTitle = ({ t, selection, isSeveralItems }) => { )} {selection.title} - {selection && ( - - )} +
+ {canInviteUserInRoomAbility && ( + + )} + {selection && ( + + )} +
); }; -export default withTranslation([ - "Files", - "Common", - "Translations", - "InfoPanel", - "SharingPanel", -])(observer(FilesItemTitle)); +export default inject(({ auth, dialogsStore, selectedFolderStore }) => { + const { selectionParentRoom, setIsMobileHidden } = auth.infoPanelStore; + const { isGracePeriod } = auth.currentTariffStatusStore; + + const { setInvitePanelOptions, setInviteUsersWarningDialogVisible } = + dialogsStore; + + const roomType = + selectedFolderStore.roomType ?? selectionParentRoom?.roomType; + + const isPublicRoomType = + roomType === RoomsType.PublicRoom || roomType === RoomsType.CustomRoom; + + return { + selectionParentRoom, + setIsMobileHidden, + isGracePeriod, + setInvitePanelOptions, + setInviteUsersWarningDialogVisible, + isPublicRoomType, + }; +})( + withTranslation([ + "Files", + "Common", + "Translations", + "InfoPanel", + "SharingPanel", + ])(observer(FilesItemTitle)) +); diff --git a/packages/client/src/pages/Home/InfoPanel/Body/sub-components/ItemTitle/ItemContextOptions.js b/packages/client/src/pages/Home/InfoPanel/Body/sub-components/ItemTitle/ItemContextOptions.js index 8bbbda9a92..4f36210ea7 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/sub-components/ItemTitle/ItemContextOptions.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/sub-components/ItemTitle/ItemContextOptions.js @@ -6,17 +6,6 @@ import { ContextMenu, ContextMenuButton } from "@docspace/components"; import ContextHelper from "../../helpers/ContextHelper"; -const StyledItemContextOptions = styled.div` - ${props => - props.theme.interfaceDirection === "rtl" - ? css` - margin-right: auto; - ` - : css` - margin-left: auto; - `} -`; - const ItemContextOptions = ({ t, selection, @@ -70,7 +59,7 @@ const ItemContextOptions = ({ }; return ( - + <> )} - + ); }; diff --git a/packages/client/src/pages/Home/InfoPanel/Body/sub-components/ItemTitle/index.js b/packages/client/src/pages/Home/InfoPanel/Body/sub-components/ItemTitle/index.js index f7902f1b77..832b623396 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/sub-components/ItemTitle/index.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/sub-components/ItemTitle/index.js @@ -49,7 +49,6 @@ const ItemTitle = ({ selectionLength={selectionLength} selection={filesItemSelection} isSeveralItems={isSeveralItems} - getIcon={getIcon} /> ); }; diff --git a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/MembersList.js b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/MembersList.js new file mode 100644 index 0000000000..7c8a9b81ae --- /dev/null +++ b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/MembersList.js @@ -0,0 +1,139 @@ +import React, { useState, useCallback, memo } from "react"; +import styled from "styled-components"; +import { FixedSizeList as List, areEqual } from "react-window"; +import AutoSizer from "react-virtualized-auto-sizer"; +import CustomScrollbarsVirtualList from "@docspace/components/scrollbar/custom-scrollbars-virtual-list"; +import InfiniteLoader from "react-window-infinite-loader"; +import User from "./User"; + +const StyledMembersList = styled.div` + height: calc(100vh - 266px); +`; + +const Item = memo(({ data, index, style }) => { + const { + t, + members, + security, + membersHelper, + currentMember, + updateRoomMemberRole, + selectionParentRoom, + setSelectionParentRoom, + changeUserType, + setIsScrollLocked, + canInviteUserInRoomAbility, + onRepeatInvitation, + } = data; + + const user = members[index]; + + return ( +
+ +
+ ); +}, areEqual); + +const MembersList = (props) => { + const { + t, + security, + membersHelper, + currentMember, + updateRoomMemberRole, + selectionParentRoom, + setSelectionParentRoom, + changeUserType, + setIsScrollLocked, + members, + hasNextPage, + itemCount, + onRepeatInvitation, + loadNextPage, + } = props; + + const itemsCount = members.length; + const canInviteUserInRoomAbility = security?.EditAccess; + const [isNextPageLoading, setIsNextPageLoading] = useState(false); + + const isItemLoaded = useCallback( + (index) => { + return !hasNextPage || index < itemsCount; + }, + [hasNextPage, itemsCount] + ); + + const loadMoreItems = useCallback( + async (startIndex) => { + setIsNextPageLoading(true); + if (!isNextPageLoading) { + await loadNextPage(startIndex - 1); + } + setIsNextPageLoading(false); + }, + [isNextPageLoading, loadNextPage] + ); + + return ( + + + {({ height, width }) => ( + + {({ onItemsRendered, ref }) => ( + + {Item} + + )} + + )} + + + ); +}; + +export default MembersList; diff --git a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/User.js b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/User.js index 241ca42213..2a3b14dc00 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/User.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/User.js @@ -9,6 +9,10 @@ import { isMobileOnly } from "react-device-detect"; import { decode } from "he"; import { filterUserRoleOptions } from "SRC_DIR/helpers/utils"; import { getUserRole } from "@docspace/common/utils"; +import Text from "@docspace/components/text"; +import EmailPlusReactSvgUrl from "PUBLIC_DIR/images/e-mail+.react.svg?url"; +import { StyledUserTypeHeader } from "../../styles/members"; +import IconButton from "@docspace/components/icon-button"; const User = ({ t, @@ -21,6 +25,9 @@ const User = ({ setSelectionParentRoom, changeUserType, setIsScrollLocked, + isTitle, + onRepeatInvitation, + showInviteIcon, }) => { if (!selectionParentRoom) return null; if (!user.displayName && !user.email) return null; @@ -126,7 +133,22 @@ const User = ({ user.isOwner ? t("Common:DocSpaceOwner") : t("Common:DocSpaceAdmin") }. ${t("Common:HasFullAccess")}`; - return ( + return isTitle ? ( + + {user.displayName} + + {showInviteIcon && ( + + )} + + ) : ( { const membersHelper = new MembersHelper({ t }); @@ -53,34 +45,66 @@ const Members = ({ const security = selectionParentRoom ? selectionParentRoom.security : {}; - const canInviteUserInRoomAbility = security?.EditAccess; - - const fetchMembers = async (roomId) => { + const fetchMembers = async (roomId, clearFilter = true) => { let timerId; - if (members) timerId = setTimeout(() => setShowLoader(true), 1000); - let data = await getRoomMembers(roomId); + // if (members) timerId = setTimeout(() => setShowLoader(true), 1000); - setExternalLinks(data); + const data = await getRoomMembers(roomId, clearFilter); + // const links = await getRoomLinks(roomId); + + // console.log("links", links); + // setExternalLinks(data); TODO: - data = data.filter((m) => m.sharedTo.email || m.sharedTo.displayName); clearTimeout(timerId); - let inRoomMembers = []; - let expectedMembers = []; + const users = []; + const administrators = []; + const expectedMembers = []; data.map((fetchedMember) => { const member = { access: fetchedMember.access, canEditAccess: fetchedMember.canEditAccess, ...fetchedMember.sharedTo, }; - if (member.activationStatus !== 2) inRoomMembers.push(member); - else expectedMembers.push(member); + + if (member.activationStatus === EmployeeActivationStatus.Pending) { + expectedMembers.push(member); + } else if ( + member.access === ShareAccessRights.FullAccess || + member.access === ShareAccessRights.RoomManager + ) { + administrators.push(member); + } else { + users.push(member); + } }); + if (administrators.length && !members?.administrators?.length) { + administrators.unshift({ + id: "administration", + displayName: t("Administration"), + isTitle: true, + }); + } + + if (users.length && !members?.users?.length) { + users.unshift({ id: "user", displayName: t("Users"), isTitle: true }); + } + + if (expectedMembers.length && !members?.expected?.length) { + expectedMembers.unshift({ + id: "expected", + displayName: t("ExpectUsers"), + isTitle: true, + isExpect: true, + }); + } + setShowLoader(false); setUpdateRoomMembers(false); return { - inRoom: inRoomMembers, + users, + administrators, expected: expectedMembers, }; }; @@ -129,6 +153,7 @@ const Members = ({ ...selectionParentRoom, members: fetchedMembers, }); + setMembers(fetchedMembers); }, [selectionParentRoom, selection?.id, updateRoomMembers]); @@ -141,25 +166,6 @@ const Members = ({ updateMembersAction, ]); - const onClickInviteUsers = () => { - setIsMobileHidden(true); - const parentRoomId = selectionParentRoom.id; - - if (isGracePeriod) { - setInviteUsersWarningDialogVisible(true); - return; - } - - setInvitePanelOptions({ - visible: true, - roomId: parentRoomId, - hideSelector: false, - defaultAccess: isPublicRoomType - ? ShareAccessRights.RoomManager - : ShareAccessRights.ReadOnly, - }); - }; - const onRepeatInvitation = async () => { const userIds = members.expected.map((user) => user.id); resendEmailInvitations(selectionParentRoom.id, userIds) @@ -169,105 +175,55 @@ const Members = ({ .catch((err) => toastr.error(err)); }; - if (showLoader) return ; if (!selectionParentRoom || !members) return null; - const [currentMember] = members.inRoom.filter( + const [currentMember] = members.administrators.filter( (member) => member.id === selfId ); + const loadNextPage = async () => { + const roomId = selectionParentRoom.id; + const fetchedMembers = await fetchMembers(roomId, false); + const { users, administrators, expected } = fetchedMembers; + + const newMembers = { + administrators: [...members.administrators, administrators], + users: [...members.users, ...users], + expected: [...members.expected, ...expected], + }; + + setMembers(newMembers); + }; + + const { administrators, users, expected } = members; + const membersList = [...administrators, ...users, ...expected]; + return ( <> {isPublicRoomType && } - - - - {t("UsersInRoom")} : {members.inRoom.length} - - {canInviteUserInRoomAbility && ( - - )} - - - - {Object.values(members.inRoom).map((user) => ( - - ))} - - - {!!members.expected.length && ( - - {t("PendingInvitations")} - {canInviteUserInRoomAbility && ( - - )} - - )} - - - {Object.values(members.expected).map((user, i) => ( - - ))} - + ); }; export default inject( - ({ - auth, - filesStore, - peopleStore, - dialogsStore, - selectedFolderStore, - publicRoomStore, - }) => { + ({ auth, filesStore, peopleStore, selectedFolderStore, publicRoomStore }) => { const { - setIsMobileHidden, selectionParentRoom, setSelectionParentRoom, @@ -279,12 +235,14 @@ export default inject( setIsScrollLocked, } = auth.infoPanelStore; - const { getRoomMembers, updateRoomMemberRole, resendEmailInvitations } = - filesStore; + const { + getRoomMembers, + getRoomLinks, + updateRoomMemberRole, + resendEmailInvitations, + membersFilter, + } = filesStore; const { id: selfId } = auth.userStore.user; - const { isGracePeriod } = auth.currentTariffStatusStore; - const { setInvitePanelOptions, setInviteUsersWarningDialogVisible } = - dialogsStore; const { changeType: changeUserType } = peopleStore; const { setExternalLinks } = publicRoomStore; @@ -298,13 +256,13 @@ export default inject( return { setView, roomsView, - setIsMobileHidden, selectionParentRoom, setSelectionParentRoom, setIsScrollLocked, getRoomMembers, + getRoomLinks, updateRoomMemberRole, updateRoomMembers, @@ -312,13 +270,11 @@ export default inject( selfId, - setInvitePanelOptions, - setInviteUsersWarningDialogVisible, resendEmailInvitations, changeUserType, - isGracePeriod, isPublicRoomType, setExternalLinks, + membersFilter, }; } )( diff --git a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/PublicRoomBar.js b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/PublicRoomBar.js index 33f124ee95..ebd2059fb0 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/PublicRoomBar.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/PublicRoomBar.js @@ -30,7 +30,6 @@ const PublicRoomBar = (props) => { size={8} iconName={CrossReactSvg} onClick={onClose} - color="#657077" /> */} ); diff --git a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/StyledPublicRoom.js b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/StyledPublicRoom.js index 8dfb5384a8..1315c50670 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/StyledPublicRoom.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/StyledPublicRoom.js @@ -36,6 +36,10 @@ const StyledPublicRoomBar = styled.div` .close-icon { margin: -5px -17px 0 0; + path { + fill: ${({ theme }) => theme.iconButton.color}; + } + svg { weight: 8px; height: 8px; diff --git a/packages/client/src/pages/Home/InfoPanel/Header/index.js b/packages/client/src/pages/Home/InfoPanel/Header/index.js index 272f97b2db..5443e420c4 100644 --- a/packages/client/src/pages/Home/InfoPanel/Header/index.js +++ b/packages/client/src/pages/Home/InfoPanel/Header/index.js @@ -126,12 +126,14 @@ const InfoPanelHeaderContent = (props) => { style={{ width: "100%" }} data={roomsSubmenu} forsedActiveItemId={roomsView} + size="scale" /> ) : ( )} diff --git a/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TileContainer.js b/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TileContainer.js index 735405b468..927a3726ff 100644 --- a/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TileContainer.js +++ b/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TileContainer.js @@ -10,7 +10,7 @@ import InfiniteGrid from "./InfiniteGrid"; const paddingCss = css` @media ${desktop} { - ${props => + ${(props) => props.theme.interfaceDirection === "rtl" ? css` margin-right: 1px; @@ -23,7 +23,7 @@ const paddingCss = css` } @media ${tablet} { - ${props => + ${(props) => props.theme.interfaceDirection === "rtl" ? css` margin-right: -1px; @@ -36,12 +36,12 @@ const paddingCss = css` const StyledGridWrapper = styled.div` display: grid; - grid-template-columns: ${props => + grid-template-columns: ${(props) => props.isRooms ? "repeat(auto-fill, minmax(274px, 1fr))" : "repeat(auto-fill, minmax(216px, 1fr))"}; width: 100%; - margin-bottom: ${props => (props.isFolders || props.isRooms ? "23px" : 0)}; + margin-bottom: ${(props) => (props.isFolders || props.isRooms ? "23px" : 0)}; box-sizing: border-box; ${paddingCss}; @@ -90,7 +90,7 @@ const StyledTileContainer = styled.div` cursor: pointer !important; .sort-combo-box { - ${props => + ${(props) => props.theme.interfaceDirection === "rtl" ? css` margin-left: 3px; @@ -119,14 +119,14 @@ const StyledTileContainer = styled.div` .option-item__icon { display: none; cursor: pointer; - ${props => + ${(props) => props.isDesc && css` transform: rotate(180deg); `} path { - fill: ${props => props.theme.filterInput.sort.sortFill}; + fill: ${(props) => props.theme.filterInput.sort.sortFill}; } } @@ -138,7 +138,7 @@ const StyledTileContainer = styled.div` } .selected-option-item { - background: ${props => + background: ${(props) => props.theme.filterInput.sort.hoverBackground}; cursor: auto; @@ -156,10 +156,10 @@ const StyledTileContainer = styled.div` font-size: 12px; font-weight: 600; - color: ${props => props.theme.filterInput.sort.tileSortColor}; + color: ${(props) => props.theme.filterInput.sort.tileSortColor}; .sort-icon { - ${props => + ${(props) => props.theme.interfaceDirection === "rtl" ? css` margin-left: 8px; @@ -169,7 +169,7 @@ const StyledTileContainer = styled.div` `} svg { path { - fill: ${props => props.theme.filterInput.sort.tileSortFill}; + fill: ${(props) => props.theme.filterInput.sort.tileSortFill}; } } } @@ -183,7 +183,7 @@ const StyledTileContainer = styled.div` } @media ${tablet} { - ${props => + ${(props) => props.theme.interfaceDirection === "rtl" ? css` margin-left: -3px; @@ -217,7 +217,7 @@ class TileContainer extends React.PureComponent { const Folders = []; const Files = []; - React.Children.map(children, item => { + React.Children.map(children, (item) => { const { isFolder, isRoom, fileExst, id } = item.props.item; if ((isFolder || id === -1) && !fileExst && !isRoom) { Folders.push( @@ -254,7 +254,8 @@ class TileContainer extends React.PureComponent { + className="tile-items-heading" + > {headingFolders} )} @@ -287,7 +288,8 @@ class TileContainer extends React.PureComponent { className={`${className} files-tile-container`} style={style} useReactWindow={useReactWindow} - isDesc={isDesc}> + isDesc={isDesc} + > {useReactWindow ? ( {renderTile} ) : ( diff --git a/packages/client/src/store/FilesStore.js b/packages/client/src/store/FilesStore.js index c574b28187..dd24273b11 100644 --- a/packages/client/src/store/FilesStore.js +++ b/packages/client/src/store/FilesStore.js @@ -80,8 +80,9 @@ class FilesStore { bufferSelection = null; selected = "close"; - filter = FilesFilter.getDefault(); //TODO: FILTER + filter = FilesFilter.getDefault(); roomsFilter = RoomsFilter.getDefault(); + membersFilter = { page: 0, pageCount: 100, total: 0 }; categoryType = getCategoryType(window.location); @@ -2425,9 +2426,48 @@ class FilesStore { return api.rooms.removeLogoFromRoom(id); } - getRoomMembers(id) { - return api.rooms.getRoomMembers(id); - } + getDefaultMembersFilter = () => { + return { page: 0, pageCount: 100, total: 0 }; + }; + + setRoomMembersFilter = (roomMembersFilter) => { + this.roomMembersFilter = roomMembersFilter; + }; + + getRoomMembers = (id, clearFilter = true) => { + let newFilter = this.membersFilter; + + if (clearFilter) { + newFilter = this.getDefaultMembersFilter(); + } else { + newFilter.page += 1; + } + this.setRoomMembersFilter(newFilter); + + const membersFilters = { + startIndex: newFilter.page * newFilter.pageCount, + count: newFilter.pageCount, + filterType: 0, // 0 (Members) + }; + + return api.rooms.getRoomMembers(id, membersFilters).then((res) => { + const newFilter = this.membersFilter; + newFilter.total = res.total; + this.membersFilter = newFilter; + + return res.items; + }); + }; + + // 2 (External link), 3 (All links); + + getRoomLinks = (id) => { + // 1 (Invitation link) + + return api.rooms + .getRoomMembers(id, { filterType: 1 }) + .then((res) => res.items); + }; updateRoomMemberRole(id, data) { return api.rooms.updateRoomMemberRole(id, data); @@ -3625,7 +3665,7 @@ class FilesStore { return Math.floor(sectionWidth / minTileWidth); }; - setInvitationLinks = async (roomId, linkId, title, access) => { + setInvitationLinks = async (roomId, title, access, linkId) => { return await api.rooms.setInvitationLinks(roomId, linkId, title, access); }; @@ -3634,7 +3674,7 @@ class FilesStore { }; getRoomSecurityInfo = async (id) => { - return await api.rooms.getRoomSecurityInfo(id); + return await api.rooms.getRoomSecurityInfo(id).then((res) => res.items); }; setRoomSecurity = async (id, data) => { diff --git a/packages/common/api/rooms/index.js b/packages/common/api/rooms/index.js index 6e4e3a49b9..c1ef6cad95 100644 --- a/packages/common/api/rooms/index.js +++ b/packages/common/api/rooms/index.js @@ -1,5 +1,9 @@ import { request } from "../client"; -import { checkFilterInstance, decodeDisplayName } from "../../utils"; +import { + checkFilterInstance, + decodeDisplayName, + toUrlParams, +} from "../../utils"; import { FolderType } from "../../constants"; import RoomsFilter from "./filter"; @@ -45,10 +49,17 @@ export function getRoomInfo(id) { }); } -export function getRoomMembers(id) { +export function getRoomMembers(id, filter) { + let params = ""; + const str = toUrlParams(filter); + + if (str) { + params = `?${str}`; + } + const options = { method: "get", - url: `/files/rooms/${id}/share`, + url: `/files/rooms/${id}/share${params}`, }; return request(options).then((res) => { @@ -302,10 +313,11 @@ export const resendEmailInvitations = async (id, usersIds) => { return res; }; +//// 1 (Invitation link) export const getRoomSecurityInfo = async (id) => { const options = { method: "get", - url: `/files/rooms/${id}/share`, + url: `/files/rooms/${id}/share?filterType=1`, }; const res = await request(options); diff --git a/packages/components/submenu/index.js b/packages/components/submenu/index.js index cc1c4ec3dd..f2b9242157 100644 --- a/packages/components/submenu/index.js +++ b/packages/components/submenu/index.js @@ -21,6 +21,7 @@ const Submenu = (props) => { startSelect = 0, forsedActiveItemId, onSelect, + size, ...rest } = props; if (!data) return null; @@ -105,7 +106,7 @@ const Submenu = (props) => {
- + {data.map((d) => { const isActive = @@ -144,9 +145,14 @@ const Submenu = (props) => { ); })} + {size !== "scale" && ( + + )} - + {size === "scale" && ( + + )}
@@ -164,6 +170,8 @@ Submenu.propTypes = { startSelect: PropTypes.oneOfType([PropTypes.object, PropTypes.number]), /** Property that allows explicitly selecting content passed through an external operation */ forsedActiveItemId: PropTypes.any, + /** Scales the width of the bottom line to 100%. */ + size: PropTypes.string, /** Sets a callback function that is triggered when the submenu item is selected */ onSelect: PropTypes.func, }; diff --git a/packages/components/submenu/styled-submenu.js b/packages/components/submenu/styled-submenu.js index 0c71ca21ae..28833502f1 100644 --- a/packages/components/submenu/styled-submenu.js +++ b/packages/components/submenu/styled-submenu.js @@ -128,6 +128,13 @@ export const SubmenuScroller = styled.div` } overflow-x: auto; overflow-y: hidden; + + ${(props) => + props.size !== "scale" && + css` + display: grid; + flex: 0 1 auto; + `} `; export const SubmenuRoot = styled.div` diff --git a/public/images/info.outline.react.svg b/public/images/info.outline.react.svg index ac41fda0d9..38fb3cf6db 100644 --- a/public/images/info.outline.react.svg +++ b/public/images/info.outline.react.svg @@ -1,12 +1,10 @@ - - - - - + + + - - + + - + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8ee1022b45..e46200ec61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3147,7 +3147,7 @@ __metadata: react-player: ^1.15.3 react-router: ^6.10.0 react-router-dom: ^6.10.0 - react-tooltip: 5.21.1 + react-tooltip: ^5.21.1 react-viewer: ^3.2.2 react-virtualized-auto-sizer: ^1.0.7 react-window: ^1.8.8 @@ -22258,19 +22258,6 @@ __metadata: languageName: node linkType: hard -"react-tooltip@npm:5.21.1": - version: 5.21.1 - resolution: "react-tooltip@npm:5.21.1" - dependencies: - "@floating-ui/dom": ^1.0.0 - classnames: ^2.3.0 - peerDependencies: - react: ">=16.14.0" - react-dom: ">=16.14.0" - checksum: 44fbe22169eecc12b32e401460997c3b26bae96f70c527260ad41b5015c2ead37b8f713e01737be2fb748ffbb7b6ee8c461a9f46b064c36e6b39a542aab1b852 - languageName: node - linkType: hard - "react-tooltip@npm:^5.21.1": version: 5.21.4 resolution: "react-tooltip@npm:5.21.4"