Web: Files: InfoPanel: added infinite-loader to members list

This commit is contained in:
Nikita Gopienko 2023-09-22 15:10:48 +03:00
parent e28d1b8704
commit 5dab788680
25 changed files with 639 additions and 311 deletions

View File

@ -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."
}

View File

@ -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`

View File

@ -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;
if (!!shareLink && linkType === LinkType.Invite) {
links.push({
const activeLink = {
id,
title,
shareLink,
expirationDate,
access: user.access || defaultAccess,
});
}
});
access: link.access || defaultAccess,
};
setShareLinks(links);
setRoomUsers(users);
onChangeExternalLinksVisible(!!links.length);
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 = ({
<ExternalLinks
t={t}
shareLinks={shareLinks}
setShareLinks={setShareLinks}
getInfo={getInfo}
roomType={roomType}
onChangeExternalLinksVisible={onChangeExternalLinksVisible}
externalLinksVisible={externalLinksVisible}
onChangeActiveLink={onChangeActiveLink}
setActiveLink={setActiveLink}
activeLink={activeLink}
isMobileView={isMobileView}
/>
@ -267,6 +271,9 @@ const InvitePanel = ({
setAddUsersPanelVisible={setAddUsersPanelVisible}
isMobileView={isMobileView}
/>
{infoBarIsVisible && hasAdmins && (
<InfoBar t={t} onClose={onCloseBar} />
)}
{hasInvitedUsers && (
<ItemsList
t={t}

View File

@ -21,6 +21,7 @@ const AccessSelector = ({
className,
standalone,
isMobileView,
noBorder = false,
}) => {
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}

View File

@ -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];
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);

View File

@ -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 (
<StyledInfoBar {...rest}>
<div className="text-container">
<div className="header-body">
<div className="header-icon">
<ReactSVG src={InfoIcon} />
</div>
<Text fontSize="12px" fontWeight={600}>
{t("Common:Info")}
</Text>
</div>
<div className="body-container">{t("InfoPanel:InfoBanner")}</div>
</div>
<IconButton
className="close-icon"
size={10}
iconName={CrossReactSvg}
onClick={onClose}
/>
</StyledInfoBar>
);
};
export default InfoBar;

View File

@ -149,6 +149,7 @@ const Item = ({
filteredAccesses={filteredAccesses}
setIsOpenItemAccess={setIsOpenItemAccess}
isMobileView={isMobileView}
noBorder
/>
)}
</>

View File

@ -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;

View File

@ -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};
}
}
}

View File

@ -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 (
<StyledTitle ref={itemTitleRef}>
@ -37,6 +68,18 @@ const FilesItemTitle = ({ t, selection, isSeveralItems }) => {
)}
</div>
<Text className="text">{selection.title}</Text>
<div className="info_title-icons">
{canInviteUserInRoomAbility && (
<IconButton
id="info_add-user"
className={"icon"}
title={t("Common:AddUsers")}
iconName={PersonPlusReactSvgUrl}
isFill={true}
onClick={onClickInviteUsers}
size={16}
/>
)}
{selection && (
<ItemContextOptions
t={t}
@ -44,14 +87,38 @@ const FilesItemTitle = ({ t, selection, isSeveralItems }) => {
selection={selection}
/>
)}
</div>
</StyledTitle>
);
};
export default withTranslation([
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));
])(observer(FilesItemTitle))
);

View File

@ -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 (
<StyledItemContextOptions>
<>
<ContextMenu
ref={contextMenuRef}
getContextModel={getData}
@ -91,7 +80,7 @@ const ItemContextOptions = ({
displayType="toggle"
/>
)}
</StyledItemContextOptions>
</>
);
};

View File

@ -49,7 +49,6 @@ const ItemTitle = ({
selectionLength={selectionLength}
selection={filesItemSelection}
isSeveralItems={isSeveralItems}
getIcon={getIcon}
/>
);
};

View File

@ -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 (
<div key={user.id} style={{ ...style, width: "calc(100% - 30px)" }}>
<User
t={t}
user={user}
key={user.id}
security={security}
membersHelper={membersHelper}
currentMember={currentMember}
updateRoomMemberRole={updateRoomMemberRole}
roomId={selectionParentRoom.id}
roomType={selectionParentRoom.roomType}
selectionParentRoom={selectionParentRoom}
setSelectionParentRoom={setSelectionParentRoom}
changeUserType={changeUserType}
setIsScrollLocked={setIsScrollLocked}
isTitle={user.isTitle}
isExpect={user.isExpect}
showInviteIcon={canInviteUserInRoomAbility && user.isExpect}
onRepeatInvitation={onRepeatInvitation}
/>
</div>
);
}, 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 (
<StyledMembersList className="aaaaaaaaaw">
<AutoSizer>
{({ height, width }) => (
<InfiniteLoader
isItemLoaded={isItemLoaded}
itemCount={itemCount}
loadMoreItems={loadMoreItems}
>
{({ onItemsRendered, ref }) => (
<List
ref={ref}
width={width + 20}
height={height}
itemCount={itemsCount}
itemSize={48}
itemData={{
t,
security,
membersHelper,
currentMember,
updateRoomMemberRole,
selectionParentRoom,
setSelectionParentRoom,
changeUserType,
setIsScrollLocked,
members,
canInviteUserInRoomAbility,
onRepeatInvitation,
}}
outerElementType={CustomScrollbarsVirtualList}
onItemsRendered={onItemsRendered}
>
{Item}
</List>
)}
</InfiniteLoader>
)}
</AutoSizer>
</StyledMembersList>
);
};
export default MembersList;

View File

@ -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 ? (
<StyledUserTypeHeader isExpect={isExpect}>
<Text className="title">{user.displayName}</Text>
{showInviteIcon && (
<IconButton
className={"icon"}
title={t("Common:RepeatInvitation")}
iconName={EmailPlusReactSvgUrl}
isFill={true}
onClick={onRepeatInvitation}
size={16}
/>
)}
</StyledUserTypeHeader>
) : (
<StyledUser isExpect={isExpect} key={user.id}>
<Avatar
role={role}

View File

@ -2,29 +2,22 @@ import React, { useState, useEffect, useCallback } from "react";
import { inject, observer } from "mobx-react";
import { withTranslation } from "react-i18next";
import toastr from "@docspace/components/toast/toastr";
import { FolderType } from "@docspace/common/constants";
import Loaders from "@docspace/common/components/Loaders";
import PersonPlusReactSvgUrl from "PUBLIC_DIR/images/person+.react.svg?url";
import EmailPlusReactSvgUrl from "PUBLIC_DIR/images/e-mail+.react.svg?url";
import {
EmployeeActivationStatus,
RoomsType,
ShareAccessRights,
} from "@docspace/common/constants";
import { StyledUserList, StyledUserTypeHeader } from "../../styles/members";
import { RoomsType, ShareAccessRights } from "@docspace/common/constants";
import IconButton from "@docspace/components/icon-button";
import Text from "@docspace/components/text";
import User from "./User";
import MembersHelper from "../../helpers/MembersHelper";
import PublicRoomBlock from "./sub-components/PublicRoomBlock";
import MembersList from "./MembersList";
const Members = ({
t,
selfId,
selection,
setIsMobileHidden,
updateRoomMembers,
setUpdateRoomMembers,
@ -34,17 +27,16 @@ const Members = ({
setIsScrollLocked,
getRoomMembers,
getRoomLinks,
updateRoomMemberRole,
setView,
roomsView,
resendEmailInvitations,
setInvitePanelOptions,
setInviteUsersWarningDialogVisible,
changeUserType,
isGracePeriod,
isPublicRoomType,
setExternalLinks,
membersFilter,
}) => {
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 <Loaders.InfoPanelViewLoader view="members" />;
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 && <PublicRoomBlock t={t} />}
<StyledUserTypeHeader>
<Text className="title">
{t("UsersInRoom")} : {members.inRoom.length}
</Text>
{canInviteUserInRoomAbility && (
<IconButton
id="info_add-user"
className={"icon"}
title={t("Common:AddUsers")}
iconName={PersonPlusReactSvgUrl}
isFill={true}
onClick={onClickInviteUsers}
size={16}
/>
)}
</StyledUserTypeHeader>
<StyledUserList>
{Object.values(members.inRoom).map((user) => (
<User
security={security}
key={user.id}
<MembersList
loadNextPage={loadNextPage}
t={t}
user={user}
security={security}
members={membersList}
membersHelper={membersHelper}
currentMember={currentMember}
updateRoomMemberRole={updateRoomMemberRole}
roomId={selectionParentRoom.id}
roomType={selectionParentRoom.roomType}
selectionParentRoom={selectionParentRoom}
setSelectionParentRoom={setSelectionParentRoom}
changeUserType={changeUserType}
setIsScrollLocked={setIsScrollLocked}
hasNextPage={membersList.length < membersFilter.total}
itemCount={membersFilter.total}
onRepeatInvitation={onRepeatInvitation}
/>
))}
</StyledUserList>
{!!members.expected.length && (
<StyledUserTypeHeader isExpect>
<Text className="title">{t("PendingInvitations")}</Text>
{canInviteUserInRoomAbility && (
<IconButton
className={"icon"}
title={t("Common:RepeatInvitation")}
iconName={EmailPlusReactSvgUrl}
isFill={true}
onClick={onRepeatInvitation}
size={16}
/>
)}
</StyledUserTypeHeader>
)}
<StyledUserList>
{Object.values(members.expected).map((user, i) => (
<User
security={security}
isExpect
key={i}
t={t}
user={user}
membersHelper={membersHelper}
currentMember={currentMember}
updateRoomMemberRole={updateRoomMemberRole}
roomId={selectionParentRoom.id}
roomType={selectionParentRoom.roomType}
selectionParentRoom={selectionParentRoom}
setSelectionParentRoom={setSelectionParentRoom}
changeUserType={changeUserType}
setIsScrollLocked={setIsScrollLocked}
/>
))}
</StyledUserList>
</>
);
};
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,
};
}
)(

View File

@ -30,7 +30,6 @@ const PublicRoomBar = (props) => {
size={8}
iconName={CrossReactSvg}
onClick={onClose}
color="#657077"
/> */}
</StyledPublicRoomBar>
);

View File

@ -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;

View File

@ -126,12 +126,14 @@ const InfoPanelHeaderContent = (props) => {
style={{ width: "100%" }}
data={roomsSubmenu}
forsedActiveItemId={roomsView}
size="scale"
/>
) : (
<Submenu
style={{ width: "100%" }}
data={personalSubmenu}
forsedActiveItemId={fileView}
size="scale"
/>
)}
</div>

View File

@ -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 {
<Heading
size="xsmall"
id={"folder-tile-heading"}
className="tile-items-heading">
className="tile-items-heading"
>
{headingFolders}
</Heading>
)}
@ -287,7 +288,8 @@ class TileContainer extends React.PureComponent {
className={`${className} files-tile-container`}
style={style}
useReactWindow={useReactWindow}
isDesc={isDesc}>
isDesc={isDesc}
>
{useReactWindow ? (
<InfiniteGrid>{renderTile}</InfiniteGrid>
) : (

View File

@ -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) => {

View File

@ -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);

View File

@ -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) => {
<div className="sticky">
<SubmenuRoot>
<SubmenuScrollbarSize />
<SubmenuScroller>
<SubmenuScroller size={size}>
<StyledSubmenuItems ref={submenuItemsRef} role="list">
{data.map((d) => {
const isActive =
@ -144,9 +145,14 @@ const Submenu = (props) => {
);
})}
</StyledSubmenuItems>
{size !== "scale" && (
<StyledSubmenuBottomLine className="bottom-line" />
)}
</SubmenuScroller>
</SubmenuRoot>
{size === "scale" && (
<StyledSubmenuBottomLine className="bottom-line" />
)}
</div>
<div className="sticky-indent"></div>
@ -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,
};

View File

@ -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`

View File

@ -1,12 +1,10 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_21120_56765)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.5 2C5.18629 2 2.5 4.68629 2.5 8C2.5 11.3137 5.18629 14 8.5 14C11.8137 14 14.5 11.3137 14.5 8C14.5 4.68629 11.8137 2 8.5 2ZM0.5 8C0.5 3.58172 4.08172 0 8.5 0C12.9183 0 16.5 3.58172 16.5 8C16.5 12.4183 12.9183 16 8.5 16C4.08172 16 0.5 12.4183 0.5 8Z" fill="#333333"/>
<circle cx="8.5" cy="5" r="1" fill="#333333"/>
<rect x="7.5" y="7" width="2" height="5" rx="1" fill="#333333"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_460_8224)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2ZM0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8ZM9 5C9 5.55228 8.55228 6 8 6C7.44772 6 7 5.55228 7 5C7 4.44772 7.44772 4 8 4C8.55228 4 9 4.44772 9 5ZM8 7C7.44772 7 7 7.44772 7 8V11C7 11.5523 7.44772 12 8 12C8.55228 12 9 11.5523 9 11V8C9 7.44772 8.55228 7 8 7Z" fill="#657077"/>
</g>
<defs>
<clipPath id="clip0_21120_56765">
<rect width="16" height="16" fill="white" transform="translate(0.5)"/>
<clipPath id="clip0_460_8224">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 710 B

After

Width:  |  Height:  |  Size: 739 B

View File

@ -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"