Merge branch 'release/v2.6.0' of https://github.com/ONLYOFFICE/DocSpace-client into release/v2.6.0
This commit is contained in:
commit
4d059d3067
@ -0,0 +1,38 @@
|
|||||||
|
// (c) Copyright Ascensio System SIA 2009-2024
|
||||||
|
//
|
||||||
|
// This program is a free software product.
|
||||||
|
// You can redistribute it and/or modify it under the terms
|
||||||
|
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
|
||||||
|
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
|
||||||
|
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
|
||||||
|
// any third-party rights.
|
||||||
|
//
|
||||||
|
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
|
||||||
|
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
|
||||||
|
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
//
|
||||||
|
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
|
||||||
|
//
|
||||||
|
// The interactive user interfaces in modified source and object code versions of the Program must
|
||||||
|
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
|
||||||
|
//
|
||||||
|
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
|
||||||
|
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
|
||||||
|
// trademark law for use of our trademarks.
|
||||||
|
//
|
||||||
|
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
|
||||||
|
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||||
|
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||||
|
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { ModalDialog } from "@docspace/shared/components/modal-dialog";
|
||||||
|
|
||||||
|
export const StyledModalDialog = styled(ModalDialog)`
|
||||||
|
.modal-body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
margin: 16px 16px 12px;
|
||||||
|
}
|
||||||
|
`;
|
@ -24,23 +24,32 @@
|
|||||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||||
|
|
||||||
|
import { inject, observer } from "mobx-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
ModalDialogType,
|
ModalDialogType,
|
||||||
} from "@docspace/shared/components/modal-dialog";
|
} from "@docspace/shared/components/modal-dialog";
|
||||||
import { observer, inject } from "mobx-react";
|
|
||||||
import { useState, useEffect, useTransition } from "react";
|
|
||||||
import { getGroupMembersInRoom } from "@docspace/shared/api/groups";
|
import { getGroupMembersInRoom } from "@docspace/shared/api/groups";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { InputSize } from "@docspace/shared/components/text-input";
|
import { InputSize } from "@docspace/shared/components/text-input";
|
||||||
import { SearchInput } from "@docspace/shared/components/search-input";
|
import { SearchInput } from "@docspace/shared/components/search-input";
|
||||||
import GroupMember from "./GroupMember";
|
import {
|
||||||
|
TGroup,
|
||||||
|
TGroupMemberInvitedInRoom,
|
||||||
|
} from "@docspace/shared/api/groups/types";
|
||||||
|
|
||||||
import EmptyContainer from "./EmptyContainer";
|
import EmptyContainer from "./EmptyContainer";
|
||||||
|
import GroupMembersList from "./sub-components/GroupMembersList/GroupMembersList";
|
||||||
|
import { StyledModalDialog } from "./EditGroupMembersDialog.styled";
|
||||||
|
import { ModalBodyLoader } from "./sub-components/ModalBodyLoader/ModalBodyLoader";
|
||||||
|
import { MIN_LOADER_TIMER } from "@docspace/shared/selectors/Files/FilesSelector.constants";
|
||||||
|
|
||||||
interface EditGroupMembersProps {
|
interface EditGroupMembersProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
setVisible: (visible: boolean) => void;
|
setVisible: (visible: boolean) => void;
|
||||||
group: any;
|
group: TGroup;
|
||||||
infoPanelSelection: any;
|
infoPanelSelection: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,33 +62,63 @@ const EditGroupMembers = ({
|
|||||||
const { t } = useTranslation(["Common"]);
|
const { t } = useTranslation(["Common"]);
|
||||||
|
|
||||||
const [searchValue, setSearchValue] = useState<string>("");
|
const [searchValue, setSearchValue] = useState<string>("");
|
||||||
const onChangeSearchValue = (newValue: string) => {
|
const [total, setTotal] = useState(0);
|
||||||
setSearchValue(newValue);
|
const [groupMembers, setGroupMembers] = useState<
|
||||||
};
|
TGroupMemberInvitedInRoom[] | null
|
||||||
const onClearSearch = () => onChangeSearchValue("");
|
>(null);
|
||||||
|
const [isSearchResultLoading, setIsSearchResultLoading] = useState(false);
|
||||||
|
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
|
||||||
|
|
||||||
const [groupMembers, setGroupMembers] = useState<any[] | null>(null);
|
const onChangeSearchValue = (value: string) => {
|
||||||
const filteredGroupMembers = groupMembers?.filter((groupMember) =>
|
setIsSearchResultLoading(true);
|
||||||
groupMember.user.displayName.includes(searchValue),
|
setSearchValue(value.trim());
|
||||||
);
|
};
|
||||||
const [, startTransition] = useTransition();
|
|
||||||
|
const onClearSearch = () => onChangeSearchValue("");
|
||||||
|
|
||||||
const onClose = () => setVisible(false);
|
const onClose = () => setVisible(false);
|
||||||
|
|
||||||
const isSearchListEmpty =
|
const loadNextPage = async (startIndex: number) => {
|
||||||
filteredGroupMembers && !filteredGroupMembers.length;
|
const startLoadingTime = new Date();
|
||||||
const hasMembers = filteredGroupMembers && filteredGroupMembers.length !== 0;
|
|
||||||
|
try {
|
||||||
|
setIsNextPageLoading(true);
|
||||||
|
const filter = { startIndex, count: 100, filterValue: searchValue };
|
||||||
|
|
||||||
|
const data = await getGroupMembersInRoom(
|
||||||
|
infoPanelSelection.id,
|
||||||
|
group.id,
|
||||||
|
filter,
|
||||||
|
);
|
||||||
|
|
||||||
|
setTotal(data.total);
|
||||||
|
if (startIndex === 0 || !groupMembers) {
|
||||||
|
setGroupMembers(data.items);
|
||||||
|
} else {
|
||||||
|
setGroupMembers([...groupMembers, ...data.items]);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
const nowDate = new Date();
|
||||||
|
const diff = Math.abs(nowDate.getTime() - startLoadingTime.getTime());
|
||||||
|
|
||||||
|
if (diff < MIN_LOADER_TIMER) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsSearchResultLoading(false);
|
||||||
|
}, MIN_LOADER_TIMER - diff);
|
||||||
|
} else {
|
||||||
|
setIsSearchResultLoading(false);
|
||||||
|
}
|
||||||
|
setIsNextPageLoading(false);
|
||||||
|
// setIsSearchResultLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchGroup = async () => {
|
loadNextPage(0);
|
||||||
if (!group) return;
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [searchValue]);
|
||||||
getGroupMembersInRoom(infoPanelSelection.id, group.id)!
|
|
||||||
.then((data: any) => startTransition(() => setGroupMembers(data.items)))
|
|
||||||
.catch((err: any) => console.error(err));
|
|
||||||
};
|
|
||||||
fetchGroup();
|
|
||||||
}, [group, infoPanelSelection.id]);
|
|
||||||
|
|
||||||
if (!infoPanelSelection?.isRoom) {
|
if (!infoPanelSelection?.isRoom) {
|
||||||
onClose();
|
onClose();
|
||||||
@ -87,7 +126,7 @@ const EditGroupMembers = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalDialog
|
<StyledModalDialog
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
displayType={ModalDialogType.aside}
|
displayType={ModalDialogType.aside}
|
||||||
@ -95,6 +134,10 @@ const EditGroupMembers = ({
|
|||||||
<ModalDialog.Header>{group.name}</ModalDialog.Header>
|
<ModalDialog.Header>{group.name}</ModalDialog.Header>
|
||||||
|
|
||||||
<ModalDialog.Body>
|
<ModalDialog.Body>
|
||||||
|
{!groupMembers ? (
|
||||||
|
<ModalBodyLoader withSearch />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<SearchInput
|
<SearchInput
|
||||||
className="search-input"
|
className="search-input"
|
||||||
placeholder={t("PeopleTranslations:SearchByGroupMembers")}
|
placeholder={t("PeopleTranslations:SearchByGroupMembers")}
|
||||||
@ -104,20 +147,27 @@ const EditGroupMembers = ({
|
|||||||
size={InputSize.base}
|
size={InputSize.base}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div style={{ height: "12px", width: "100%" }} />
|
{isSearchResultLoading ? (
|
||||||
|
<ModalBodyLoader withSearch={false} />
|
||||||
{isSearchListEmpty && <EmptyContainer />}
|
) : !groupMembers.length ? (
|
||||||
|
<EmptyContainer />
|
||||||
{hasMembers &&
|
) : (
|
||||||
filteredGroupMembers.map(({ user, ...rest }) => (
|
<GroupMembersList
|
||||||
<GroupMember t={t} key={user.id} user={{ ...user, ...rest }} />
|
members={groupMembers}
|
||||||
))}
|
loadNextPage={loadNextPage}
|
||||||
|
hasNextPage={groupMembers.length < total}
|
||||||
|
total={total}
|
||||||
|
isNextPageLoading={isNextPageLoading}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ModalDialog.Body>
|
</ModalDialog.Body>
|
||||||
</ModalDialog>
|
</StyledModalDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default inject(({ infoPanelStore, userStore, dialogsStore }) => ({
|
export default inject(({ infoPanelStore, userStore, dialogsStore }: any) => ({
|
||||||
infoPanelSelection: infoPanelStore.infoPanelSelection,
|
infoPanelSelection: infoPanelStore.infoPanelSelection,
|
||||||
selfId: userStore.user.id,
|
selfId: userStore.user.id,
|
||||||
group: dialogsStore.editMembersGroup,
|
group: dialogsStore.editMembersGroup,
|
||||||
|
@ -31,7 +31,7 @@ export const GroupMember = styled.div<{ isExpect: boolean }>`
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 8px 0;
|
padding: 8px 16px;
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
min-width: 32px;
|
min-width: 32px;
|
@ -24,39 +24,47 @@
|
|||||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
import { inject, observer } from "mobx-react";
|
import { inject, observer } from "mobx-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { isMobile, isMobileOnly } from "react-device-detect";
|
||||||
|
|
||||||
import AtReactSvgUrl from "PUBLIC_DIR/images/@.react.svg?url";
|
import AtReactSvgUrl from "PUBLIC_DIR/images/@.react.svg?url";
|
||||||
// import { StyledUser } from "../../styles/members";
|
|
||||||
import { Avatar } from "@docspace/shared/components/avatar";
|
import { Avatar } from "@docspace/shared/components/avatar";
|
||||||
import { ComboBox } from "@docspace/shared/components/combobox";
|
import { ComboBox } from "@docspace/shared/components/combobox";
|
||||||
import DefaultUserPhotoUrl from "PUBLIC_DIR/images/default_user_photo_size_82-82.png";
|
import DefaultUserPhotoUrl from "PUBLIC_DIR/images/default_user_photo_size_82-82.png";
|
||||||
import { isMobileOnly, isMobile } from "react-device-detect";
|
|
||||||
import { decode } from "he";
|
import { decode } from "he";
|
||||||
import { filterUserRoleOptions } from "SRC_DIR/helpers";
|
import { filterUserRoleOptions } from "SRC_DIR/helpers";
|
||||||
import { Text } from "@docspace/shared/components/text";
|
import { Text } from "@docspace/shared/components/text";
|
||||||
import * as Styled from "./index.styled";
|
|
||||||
import { getUserRoleOptionsByUserAccess } from "@docspace/shared/utils/room-members/getUserRoleOptionsByUserAccess";
|
import { getUserRoleOptionsByUserAccess } from "@docspace/shared/utils/room-members/getUserRoleOptionsByUserAccess";
|
||||||
import { getUserRoleOptionsByRoomType } from "@docspace/shared/utils/room-members/getUserRoleOptionsByRoomType";
|
import { getUserRoleOptionsByRoomType } from "@docspace/shared/utils/room-members/getUserRoleOptionsByRoomType";
|
||||||
import { updateRoomMemberRole } from "@docspace/shared/api/rooms";
|
import { updateRoomMemberRole } from "@docspace/shared/api/rooms";
|
||||||
import { toastr } from "@docspace/shared/components/toast";
|
import { toastr } from "@docspace/shared/components/toast";
|
||||||
import { useState } from "react";
|
|
||||||
import { HelpButton } from "@docspace/shared/components/help-button";
|
import { HelpButton } from "@docspace/shared/components/help-button";
|
||||||
import { getUserRoleOptions } from "@docspace/shared/utils/room-members/getUserRoleOptions";
|
import { getUserRoleOptions } from "@docspace/shared/utils/room-members/getUserRoleOptions";
|
||||||
import { ShareAccessRights } from "@docspace/shared/enums";
|
import { ShareAccessRights } from "@docspace/shared/enums";
|
||||||
import { getUserRole, getUserTypeLabel } from "@docspace/shared/utils/common";
|
import { getUserRole } from "@docspace/shared/utils/common";
|
||||||
|
import { TGroupMemberInvitedInRoom } from "@docspace/shared/api/groups/types";
|
||||||
|
|
||||||
|
import * as Styled from "./index.styled";
|
||||||
|
|
||||||
interface GroupMemberProps {
|
interface GroupMemberProps {
|
||||||
t: any;
|
member: TGroupMemberInvitedInRoom;
|
||||||
user: any;
|
|
||||||
infoPanelSelection: any;
|
infoPanelSelection: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GroupMember = ({ t, user, infoPanelSelection }: GroupMemberProps) => {
|
const GroupMember = ({ member, infoPanelSelection }: GroupMemberProps) => {
|
||||||
|
const { user } = member;
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { t } = useTranslation("Common");
|
||||||
|
|
||||||
const userRole = user.isOwner
|
const userRole = user.isOwner
|
||||||
? getUserRoleOptions(t).portalAdmin
|
? getUserRoleOptions(t).portalAdmin
|
||||||
: getUserRoleOptionsByUserAccess(t, user.userAccess || user.groupAccess);
|
: getUserRoleOptionsByUserAccess(
|
||||||
|
t,
|
||||||
|
member.userAccess || member.groupAccess,
|
||||||
|
);
|
||||||
|
|
||||||
const fullRoomRoleOptions = getUserRoleOptionsByRoomType(
|
const fullRoomRoleOptions = getUserRoleOptionsByRoomType(
|
||||||
t,
|
t,
|
||||||
@ -86,7 +94,7 @@ const GroupMember = ({ t, user, infoPanelSelection }: GroupMemberProps) => {
|
|||||||
else
|
else
|
||||||
selectedUserRoleCBOption = getUserRoleOptionsByUserAccess(
|
selectedUserRoleCBOption = getUserRoleOptionsByUserAccess(
|
||||||
t,
|
t,
|
||||||
user.userAccess || user.groupAccess,
|
member.userAccess || member.groupAccess,
|
||||||
);
|
);
|
||||||
|
|
||||||
const availableUserRoleCBOptions = filterUserRoleOptions(
|
const availableUserRoleCBOptions = filterUserRoleOptions(
|
||||||
@ -101,7 +109,7 @@ const GroupMember = ({ t, user, infoPanelSelection }: GroupMemberProps) => {
|
|||||||
notify: false,
|
notify: false,
|
||||||
sharingMessage: "",
|
sharingMessage: "",
|
||||||
})
|
})
|
||||||
.then(() => (user.userAccess = userRoleOption.access))
|
.then(() => (member.userAccess = userRoleOption.access))
|
||||||
.catch((err) => toastr.error(err))
|
.catch((err) => toastr.error(err))
|
||||||
.finally(() => setIsLoading(false));
|
.finally(() => setIsLoading(false));
|
||||||
};
|
};
|
||||||
@ -134,8 +142,8 @@ const GroupMember = ({ t, user, infoPanelSelection }: GroupMemberProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="individual-rights-tooltip">
|
<div className="individual-rights-tooltip">
|
||||||
{user.userAccess &&
|
{member.userAccess &&
|
||||||
user.userAccess !== user.groupAccess &&
|
member.userAccess !== member.groupAccess &&
|
||||||
!user.isOwner && (
|
!user.isOwner && (
|
||||||
<HelpButton
|
<HelpButton
|
||||||
place="left"
|
place="left"
|
||||||
@ -152,7 +160,7 @@ const GroupMember = ({ t, user, infoPanelSelection }: GroupMemberProps) => {
|
|||||||
|
|
||||||
{userRole && userRoleOptions && (
|
{userRole && userRoleOptions && (
|
||||||
<div className="role-wrapper">
|
<div className="role-wrapper">
|
||||||
{user.canEditAccess && !user.isOwner ? (
|
{member.canEditAccess && !user.isOwner ? (
|
||||||
<ComboBox
|
<ComboBox
|
||||||
className="role-combobox"
|
className="role-combobox"
|
||||||
selectedOption={userRole}
|
selectedOption={userRole}
|
||||||
@ -180,6 +188,6 @@ const GroupMember = ({ t, user, infoPanelSelection }: GroupMemberProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default inject(({ infoPanelStore }) => ({
|
export default inject(({ infoPanelStore }: any) => ({
|
||||||
infoPanelSelection: infoPanelStore.infoPanelSelection,
|
infoPanelSelection: infoPanelStore.infoPanelSelection,
|
||||||
}))(observer(GroupMember));
|
}))(observer(GroupMember));
|
@ -0,0 +1,126 @@
|
|||||||
|
// (c) Copyright Ascensio System SIA 2009-2024
|
||||||
|
//
|
||||||
|
// This program is a free software product.
|
||||||
|
// You can redistribute it and/or modify it under the terms
|
||||||
|
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
|
||||||
|
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
|
||||||
|
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
|
||||||
|
// any third-party rights.
|
||||||
|
//
|
||||||
|
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
|
||||||
|
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
|
||||||
|
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
//
|
||||||
|
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
|
||||||
|
//
|
||||||
|
// The interactive user interfaces in modified source and object code versions of the Program must
|
||||||
|
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
|
||||||
|
//
|
||||||
|
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
|
||||||
|
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
|
||||||
|
// trademark law for use of our trademarks.
|
||||||
|
//
|
||||||
|
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
|
||||||
|
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||||
|
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||||
|
|
||||||
|
import React, { memo, useCallback } from "react";
|
||||||
|
import { useTheme } from "styled-components";
|
||||||
|
import { areEqual, FixedSizeList as List } from "react-window";
|
||||||
|
import AutoSizer from "react-virtualized-auto-sizer";
|
||||||
|
import InfiniteLoader from "react-window-infinite-loader";
|
||||||
|
|
||||||
|
import { Scrollbar } from "@docspace/shared/components/scrollbar";
|
||||||
|
import { RowLoader } from "@docspace/shared/skeletons/selector";
|
||||||
|
import { TGroupMemberInvitedInRoom } from "@docspace/shared/api/groups/types";
|
||||||
|
|
||||||
|
import GroupMember from "../GroupMember";
|
||||||
|
|
||||||
|
const ROW_HEIGHT = 48;
|
||||||
|
const SEARCH_WITH_PADDING_HEIGHT = 60;
|
||||||
|
|
||||||
|
const Row = memo(
|
||||||
|
({
|
||||||
|
data,
|
||||||
|
index,
|
||||||
|
style,
|
||||||
|
}: {
|
||||||
|
data: TGroupMemberInvitedInRoom[];
|
||||||
|
index: number;
|
||||||
|
style: React.CSSProperties;
|
||||||
|
}) => {
|
||||||
|
const member = data[index];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={style}>
|
||||||
|
{member ? (
|
||||||
|
<GroupMember member={member} />
|
||||||
|
) : (
|
||||||
|
<RowLoader isMultiSelect={false} isContainer isUser />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
areEqual,
|
||||||
|
);
|
||||||
|
|
||||||
|
Row.displayName = "Row";
|
||||||
|
|
||||||
|
interface GroupMembersListProps {
|
||||||
|
members: TGroupMemberInvitedInRoom[];
|
||||||
|
loadNextPage: (startIndex: number) => void;
|
||||||
|
hasNextPage: boolean;
|
||||||
|
total: number;
|
||||||
|
isNextPageLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GroupMembersList = ({
|
||||||
|
members,
|
||||||
|
loadNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
total,
|
||||||
|
isNextPageLoading,
|
||||||
|
}: GroupMembersListProps) => {
|
||||||
|
const { interfaceDirection } = useTheme();
|
||||||
|
|
||||||
|
const itemCount = hasNextPage ? members.length + 1 : members.length;
|
||||||
|
|
||||||
|
const isItemLoaded = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
return !hasNextPage || index < itemCount - 1;
|
||||||
|
},
|
||||||
|
[hasNextPage, itemCount],
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadMoreItems = isNextPageLoading ? () => {} : loadNextPage;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AutoSizer>
|
||||||
|
{({ height, width }) => (
|
||||||
|
<InfiniteLoader
|
||||||
|
isItemLoaded={isItemLoaded}
|
||||||
|
itemCount={total}
|
||||||
|
loadMoreItems={loadMoreItems}
|
||||||
|
>
|
||||||
|
{({ onItemsRendered, ref }) => (
|
||||||
|
<List
|
||||||
|
ref={ref}
|
||||||
|
direction={interfaceDirection}
|
||||||
|
height={height - SEARCH_WITH_PADDING_HEIGHT}
|
||||||
|
width={width}
|
||||||
|
itemCount={itemCount}
|
||||||
|
itemSize={ROW_HEIGHT}
|
||||||
|
itemData={members}
|
||||||
|
outerElementType={Scrollbar}
|
||||||
|
onItemsRendered={onItemsRendered}
|
||||||
|
>
|
||||||
|
{Row}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
</InfiniteLoader>
|
||||||
|
)}
|
||||||
|
</AutoSizer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GroupMembersList;
|
@ -0,0 +1,44 @@
|
|||||||
|
// (c) Copyright Ascensio System SIA 2009-2024
|
||||||
|
//
|
||||||
|
// This program is a free software product.
|
||||||
|
// You can redistribute it and/or modify it under the terms
|
||||||
|
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
|
||||||
|
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
|
||||||
|
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
|
||||||
|
// any third-party rights.
|
||||||
|
//
|
||||||
|
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
|
||||||
|
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
|
||||||
|
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
//
|
||||||
|
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
|
||||||
|
//
|
||||||
|
// The interactive user interfaces in modified source and object code versions of the Program must
|
||||||
|
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
|
||||||
|
//
|
||||||
|
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
|
||||||
|
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
|
||||||
|
// trademark law for use of our trademarks.
|
||||||
|
//
|
||||||
|
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
|
||||||
|
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||||
|
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||||
|
|
||||||
|
import { SearchLoader } from "@docspace/shared/skeletons/selector";
|
||||||
|
import React from "react";
|
||||||
|
import { MemberLoader } from "@docspace/shared/skeletons/info-panel/body/views/MembersLoader";
|
||||||
|
|
||||||
|
interface ModalBodyLoaderProps {
|
||||||
|
withSearch: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModalBodyLoader = ({ withSearch }: ModalBodyLoaderProps) => {
|
||||||
|
return (
|
||||||
|
<div style={{ paddingTop: withSearch ? "16px" : "0" }}>
|
||||||
|
{withSearch && <SearchLoader />}
|
||||||
|
<div style={{ paddingInline: "16px" }}>
|
||||||
|
<MemberLoader count={25} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -27,8 +27,12 @@
|
|||||||
import Filter from "./filter";
|
import Filter from "./filter";
|
||||||
|
|
||||||
import { request } from "../client";
|
import { request } from "../client";
|
||||||
import { checkFilterInstance } from "../../utils/common";
|
import { checkFilterInstance, toUrlParams } from "../../utils/common";
|
||||||
import { TGroup } from "./types";
|
import {
|
||||||
|
TGetGroupMembersInRoom,
|
||||||
|
TGetGroupMembersInRoomFilter,
|
||||||
|
TGroup,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
// * Create
|
// * Create
|
||||||
|
|
||||||
@ -104,11 +108,14 @@ export const getGroupsByUserId = (userId: string) => {
|
|||||||
export const getGroupMembersInRoom = (
|
export const getGroupMembersInRoom = (
|
||||||
folderId: string | number,
|
folderId: string | number,
|
||||||
groupId: string,
|
groupId: string,
|
||||||
|
filter: TGetGroupMembersInRoomFilter,
|
||||||
) => {
|
) => {
|
||||||
|
const url = `/files/folder/${folderId}/group/${groupId}/share?${toUrlParams(filter, false)}`;
|
||||||
|
|
||||||
return request({
|
return request({
|
||||||
method: "get",
|
method: "get",
|
||||||
url: `/files/folder/${folderId}/group/${groupId}/share`,
|
url,
|
||||||
});
|
}) as Promise<TGetGroupMembersInRoom>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// * Update
|
// * Update
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||||
|
|
||||||
import { TUser } from "../people/types";
|
import { TUser } from "../people/types";
|
||||||
|
import { ShareAccessRights } from "../../enums";
|
||||||
|
|
||||||
export type TGroup = {
|
export type TGroup = {
|
||||||
category: string;
|
category: string;
|
||||||
@ -37,3 +38,23 @@ export type TGroup = {
|
|||||||
shared?: boolean;
|
shared?: boolean;
|
||||||
isLDAP: boolean;
|
isLDAP: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TGroupMemberInvitedInRoom = {
|
||||||
|
user: TUser;
|
||||||
|
canEditAccess: boolean;
|
||||||
|
overridden: boolean;
|
||||||
|
owner: boolean;
|
||||||
|
groupAccess: number;
|
||||||
|
userAccess: ShareAccessRights;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetGroupMembersInRoom = {
|
||||||
|
items: TGroupMemberInvitedInRoom[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetGroupMembersInRoomFilter = {
|
||||||
|
startIndex?: number;
|
||||||
|
count?: number;
|
||||||
|
filterValue?: string;
|
||||||
|
};
|
||||||
|
@ -33,6 +33,30 @@ import {
|
|||||||
StyledMembersLoader,
|
StyledMembersLoader,
|
||||||
} from "../body.styled";
|
} from "../body.styled";
|
||||||
|
|
||||||
|
export const MemberLoader = ({ count = 1 }: { count?: number }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{[...Array(count).keys()].map((i) => (
|
||||||
|
<StyledMemberLoader key={i}>
|
||||||
|
<RectangleSkeleton
|
||||||
|
className="avatar"
|
||||||
|
width="32px"
|
||||||
|
height="32px"
|
||||||
|
borderRadius="50%"
|
||||||
|
/>
|
||||||
|
<RectangleSkeleton width="212px" height="16px" borderRadius="3px" />
|
||||||
|
<RectangleSkeleton
|
||||||
|
className="role-selector"
|
||||||
|
width="64px"
|
||||||
|
height="20px"
|
||||||
|
borderRadius="3px"
|
||||||
|
/>
|
||||||
|
</StyledMemberLoader>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const MembersLoader = () => {
|
const MembersLoader = () => {
|
||||||
return (
|
return (
|
||||||
<StyledMembersLoader>
|
<StyledMembersLoader>
|
||||||
@ -41,46 +65,14 @@ const MembersLoader = () => {
|
|||||||
<RectangleSkeleton width="16px" height="16px" borderRadius="3px" />
|
<RectangleSkeleton width="16px" height="16px" borderRadius="3px" />
|
||||||
</StyledMemberSubtitleLoader>
|
</StyledMemberSubtitleLoader>
|
||||||
|
|
||||||
{[...Array(4).keys()].map((i) => (
|
<MemberLoader count={4} />
|
||||||
<StyledMemberLoader key={i}>
|
|
||||||
<RectangleSkeleton
|
|
||||||
className="avatar"
|
|
||||||
width="32px"
|
|
||||||
height="32px"
|
|
||||||
borderRadius="50%"
|
|
||||||
/>
|
|
||||||
<RectangleSkeleton width="212px" height="16px" borderRadius="3px" />
|
|
||||||
<RectangleSkeleton
|
|
||||||
className="role-selector"
|
|
||||||
width="64px"
|
|
||||||
height="20px"
|
|
||||||
borderRadius="3px"
|
|
||||||
/>
|
|
||||||
</StyledMemberLoader>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<StyledMemberSubtitleLoader className="pending_users">
|
<StyledMemberSubtitleLoader className="pending_users">
|
||||||
<RectangleSkeleton width="111px" height="16px" borderRadius="3px" />
|
<RectangleSkeleton width="111px" height="16px" borderRadius="3px" />
|
||||||
<RectangleSkeleton width="16px" height="16px" borderRadius="3px" />
|
<RectangleSkeleton width="16px" height="16px" borderRadius="3px" />
|
||||||
</StyledMemberSubtitleLoader>
|
</StyledMemberSubtitleLoader>
|
||||||
|
|
||||||
{[...Array(4).keys()].map((i) => (
|
<MemberLoader count={4} />
|
||||||
<StyledMemberLoader key={i}>
|
|
||||||
<RectangleSkeleton
|
|
||||||
className="avatar"
|
|
||||||
width="32px"
|
|
||||||
height="32px"
|
|
||||||
borderRadius="50%"
|
|
||||||
/>
|
|
||||||
<RectangleSkeleton width="212px" height="16px" borderRadius="3px" />
|
|
||||||
<RectangleSkeleton
|
|
||||||
className="role-selector"
|
|
||||||
width="64px"
|
|
||||||
height="20px"
|
|
||||||
borderRadius="3px"
|
|
||||||
/>
|
|
||||||
</StyledMemberLoader>
|
|
||||||
))}
|
|
||||||
</StyledMembersLoader>
|
</StyledMembersLoader>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user