Merge branch 'develop' into feature/tg-reports
This commit is contained in:
commit
114bca4692
@ -46,7 +46,7 @@ if [ "$#" -ge "2" ]; then
|
||||
if [ -f "${OPENRESTY}/onlyoffice-proxy-ssl.conf.template" ]; then
|
||||
cp -f ${OPENRESTY}/onlyoffice-proxy-ssl.conf.template ${OPENRESTY}/onlyoffice-proxy.conf
|
||||
|
||||
ENVIRONMENT=$(grep -oP 'ENVIRONMENT=\K.*' /usr/lib/systemd/system/${PRODUCT}-api.service)
|
||||
ENVIRONMENT=$(grep -oP 'ENVIRONMENT=\K.*' $(dirname $(dpkg-query -L ${PRODUCT}-api | grep systemd/system/))/${PRODUCT}-api.service)
|
||||
sed -i "s/\(\"portal\":\).*/\1 \"https:\/\/${DOMAIN:-$(hostname --fqdn)}\"/" /etc/onlyoffice/docspace/appsettings.$ENVIRONMENT.json
|
||||
sed -i "s~\(ssl_certificate \).*;~\1${CERTIFICATE_FILE};~g" ${OPENRESTY}/onlyoffice-proxy.conf
|
||||
sed -i "s~\(ssl_certificate_key \).*;~\1${PRIVATEKEY_FILE};~g" ${OPENRESTY}/onlyoffice-proxy.conf
|
||||
|
@ -157,6 +157,7 @@ public class EmployeeFullDto : EmployeeDto
|
||||
/// <summary>Portal used space</summary>
|
||||
/// <type>System.Double, System</type>
|
||||
public double UsedSpace { get; set; }
|
||||
public bool? Shared { get; set; }
|
||||
|
||||
public static new EmployeeFullDto GetSample()
|
||||
{
|
||||
@ -257,6 +258,7 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
|
||||
|
||||
return lambda;
|
||||
}
|
||||
|
||||
public async Task<EmployeeFullDto> GetSimple(UserInfo userInfo)
|
||||
{
|
||||
var result = new EmployeeFullDto
|
||||
@ -279,7 +281,7 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<EmployeeFullDto> GetFullAsync(UserInfo userInfo)
|
||||
public async Task<EmployeeFullDto> GetFullAsync(UserInfo userInfo, bool? shared = null)
|
||||
{
|
||||
var currentType = await _userManager.GetUserTypeAsync(userInfo.Id);
|
||||
|
||||
@ -300,7 +302,8 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
|
||||
IsOwner = userInfo.IsOwner(_context.Tenant),
|
||||
IsCollaborator = currentType is EmployeeType.Collaborator,
|
||||
IsLDAP = userInfo.IsLDAP(),
|
||||
IsSSO = userInfo.IsSSO()
|
||||
IsSSO = userInfo.IsSSO(),
|
||||
Shared = shared,
|
||||
};
|
||||
|
||||
await InitAsync(result, userInfo);
|
||||
@ -373,6 +376,7 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task FillGroupsAsync(EmployeeFullDto result, UserInfo userInfo)
|
||||
{
|
||||
if (!_context.Check("groups") && !_context.Check("department"))
|
||||
|
@ -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."
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ const DeleteLinkDialogComponent = (props) => {
|
||||
setIsVisible,
|
||||
tReady,
|
||||
roomId,
|
||||
setExternalLinks,
|
||||
deleteExternalLink,
|
||||
editExternalLink,
|
||||
} = props;
|
||||
|
||||
@ -45,8 +45,8 @@ const DeleteLinkDialogComponent = (props) => {
|
||||
newLink.access = 0;
|
||||
|
||||
editExternalLink(roomId, newLink)
|
||||
.then((res) => {
|
||||
setExternalLinks(res);
|
||||
.then(() => {
|
||||
deleteExternalLink(newLink.sharedTo.id);
|
||||
toastr.success(t("Files:LinkDeletedSuccessfully"));
|
||||
})
|
||||
.catch((err) => toastr.error(err?.message))
|
||||
@ -104,7 +104,7 @@ export default inject(({ auth, dialogsStore, publicRoomStore }) => {
|
||||
setDeleteLinkDialogVisible: setIsVisible,
|
||||
linkParams,
|
||||
} = dialogsStore;
|
||||
const { editExternalLink, setExternalLinks } = publicRoomStore;
|
||||
const { editExternalLink, deleteExternalLink } = publicRoomStore;
|
||||
|
||||
return {
|
||||
visible,
|
||||
@ -112,6 +112,6 @@ export default inject(({ auth, dialogsStore, publicRoomStore }) => {
|
||||
roomId: selectionParentRoom.id,
|
||||
link: linkParams.link,
|
||||
editExternalLink,
|
||||
setExternalLinks,
|
||||
deleteExternalLink,
|
||||
};
|
||||
})(observer(DeleteLinkDialog));
|
||||
|
@ -6,19 +6,27 @@ import Heading from "@docspace/components/heading";
|
||||
import Aside from "@docspace/components/aside";
|
||||
import IconButton from "@docspace/components/icon-button";
|
||||
import { ShareAccessRights } from "@docspace/common/constants";
|
||||
import PeopleSelector from "@docspace/client/src/components/PeopleSelector";
|
||||
import Selector from "@docspace/components/selector";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import Loaders from "@docspace/common/components/Loaders";
|
||||
import withLoader from "../../../HOCs/withLoader";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import Filter from "@docspace/common/api/people/filter";
|
||||
|
||||
import { getMembersList } from "@docspace/common/api/people";
|
||||
import { getUserRole } from "@docspace/common/utils";
|
||||
import DefaultUserPhoto from "PUBLIC_DIR/images/default_user_photo_size_82-82.png";
|
||||
import CatalogAccountsReactSvgUrl from "PUBLIC_DIR/images/catalog.accounts.react.svg?url";
|
||||
import EmptyScreenPersonsSvgUrl from "PUBLIC_DIR/images/empty_screen_persons.svg?url";
|
||||
import EmptyScreenPersonsSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_persons_dark.svg?url";
|
||||
|
||||
let timer = null;
|
||||
|
||||
const AddUsersPanel = ({
|
||||
isEncrypted,
|
||||
defaultAccess,
|
||||
onClose,
|
||||
onParentPanelClose,
|
||||
shareDataItems,
|
||||
tempDataItems,
|
||||
setDataItems,
|
||||
t,
|
||||
@ -29,6 +37,7 @@ const AddUsersPanel = ({
|
||||
theme,
|
||||
withoutBackground,
|
||||
withBlur,
|
||||
roomId,
|
||||
}) => {
|
||||
const accessRight = defaultAccess
|
||||
? defaultAccess
|
||||
@ -61,25 +70,21 @@ const AddUsersPanel = ({
|
||||
const items = [];
|
||||
|
||||
for (let item of users) {
|
||||
const currentItem = shareDataItems.find((x) => x.sharedTo.id === item.id);
|
||||
const currentAccess =
|
||||
item.isOwner || item.isAdmin
|
||||
? ShareAccessRights.RoomManager
|
||||
: access.access;
|
||||
|
||||
if (!currentItem) {
|
||||
const currentAccess =
|
||||
item.isOwner || item.isAdmin
|
||||
? ShareAccessRights.RoomManager
|
||||
: access.access;
|
||||
|
||||
const newItem = {
|
||||
access: currentAccess,
|
||||
email: item.email,
|
||||
id: item.id,
|
||||
displayName: item.label,
|
||||
avatar: item.avatar,
|
||||
isOwner: item.isOwner,
|
||||
isAdmin: item.isAdmin,
|
||||
};
|
||||
items.push(newItem);
|
||||
}
|
||||
const newItem = {
|
||||
access: currentAccess,
|
||||
email: item.email,
|
||||
id: item.id,
|
||||
displayName: item.label,
|
||||
avatar: item.avatar,
|
||||
isOwner: item.isOwner,
|
||||
isAdmin: item.isAdmin,
|
||||
};
|
||||
items.push(newItem);
|
||||
}
|
||||
|
||||
if (users.length > items.length)
|
||||
@ -89,22 +94,128 @@ const AddUsersPanel = ({
|
||||
onClose();
|
||||
};
|
||||
|
||||
const onUserSelect = (owner) => {
|
||||
const ownerItem = shareDataItems.find((x) => x.isOwner);
|
||||
ownerItem.sharedTo = owner[0];
|
||||
|
||||
if (owner[0].key) {
|
||||
owner[0].id = owner[0].key;
|
||||
}
|
||||
|
||||
setDataItems(shareDataItems);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const selectedAccess = accessOptions.filter(
|
||||
(access) => access.access === accessRight
|
||||
)[0];
|
||||
|
||||
const [itemsList, setItemsList] = useState(null);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [hasNextPage, setHasNextPage] = useState(true);
|
||||
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const cleanTimer = () => {
|
||||
timer && clearTimeout(timer);
|
||||
timer = null;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadNextPage(0);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
cleanTimer();
|
||||
timer = setTimeout(() => {
|
||||
setIsLoading(true);
|
||||
}, 100);
|
||||
} else {
|
||||
cleanTimer();
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
return () => {
|
||||
cleanTimer();
|
||||
};
|
||||
}, [isLoading]);
|
||||
|
||||
const onSearch = (value) => {
|
||||
setSearchValue(value);
|
||||
loadNextPage(0, value);
|
||||
};
|
||||
|
||||
const onClearSearch = () => {
|
||||
setSearchValue("");
|
||||
loadNextPage(0, "");
|
||||
};
|
||||
|
||||
const toListItem = (item) => {
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
avatar,
|
||||
icon,
|
||||
displayName,
|
||||
hasAvatar,
|
||||
isOwner,
|
||||
isAdmin,
|
||||
isVisitor,
|
||||
isCollaborator,
|
||||
} = item;
|
||||
|
||||
const role = getUserRole(item);
|
||||
|
||||
const userAvatar = hasAvatar ? avatar : DefaultUserPhoto;
|
||||
|
||||
return {
|
||||
id,
|
||||
email,
|
||||
avatar: userAvatar,
|
||||
icon,
|
||||
label: displayName || email,
|
||||
role,
|
||||
isOwner,
|
||||
isAdmin,
|
||||
isVisitor,
|
||||
isCollaborator,
|
||||
};
|
||||
};
|
||||
|
||||
const loadNextPage = (startIndex, search = searchValue) => {
|
||||
const pageCount = 100;
|
||||
|
||||
setIsNextPageLoading(true);
|
||||
|
||||
if (startIndex === 0) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
|
||||
const currentFilter = getFilterWithOutDisabledUser();
|
||||
|
||||
currentFilter.page = startIndex / pageCount;
|
||||
currentFilter.pageCount = pageCount;
|
||||
currentFilter.excludeShared = true;
|
||||
|
||||
if (!!search.length) {
|
||||
currentFilter.search = search;
|
||||
}
|
||||
|
||||
getMembersList(roomId, currentFilter)
|
||||
.then((response) => {
|
||||
let newItems = startIndex ? itemsList : [];
|
||||
let totalDifferent = startIndex ? response.total - total : 0;
|
||||
|
||||
const items = response.items.map((item) => toListItem(item));
|
||||
|
||||
newItems = [...newItems, ...items];
|
||||
|
||||
const newTotal = response.total - totalDifferent;
|
||||
|
||||
setHasNextPage(newItems.length < newTotal);
|
||||
setItemsList(newItems);
|
||||
setTotal(newTotal);
|
||||
|
||||
setIsNextPageLoading(false);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((error) => console.log(error));
|
||||
};
|
||||
|
||||
const emptyScreenImage = theme.isBase
|
||||
? EmptyScreenPersonsSvgUrl
|
||||
: EmptyScreenPersonsSvgDarkUrl;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Backdrop
|
||||
@ -121,18 +232,45 @@ const AddUsersPanel = ({
|
||||
onClose={onClosePanels}
|
||||
withoutBodyScroll
|
||||
>
|
||||
<PeopleSelector
|
||||
isMultiSelect={isMultiSelect}
|
||||
onAccept={onUsersSelect}
|
||||
<Selector
|
||||
headerLabel={t("PeopleSelector:ListAccounts")}
|
||||
onBackClick={onBackClick}
|
||||
accessRights={accessOptions}
|
||||
searchPlaceholder={t("Common:Search")}
|
||||
searchValue={searchValue}
|
||||
onSearch={onSearch}
|
||||
onClearSearch={onClearSearch}
|
||||
items={itemsList}
|
||||
isMultiSelect={isMultiSelect}
|
||||
acceptButtonLabel={t("Common:AddButton")}
|
||||
selectedAccessRight={selectedAccess}
|
||||
onCancel={onClosePanels}
|
||||
withCancelButton={!isMultiSelect}
|
||||
withAccessRights={isMultiSelect}
|
||||
onAccept={onUsersSelect}
|
||||
withSelectAll={isMultiSelect}
|
||||
filter={getFilterWithOutDisabledUser}
|
||||
selectAllLabel={t("PeopleSelector:AllAccounts")}
|
||||
selectAllIcon={CatalogAccountsReactSvgUrl}
|
||||
withAccessRights={isMultiSelect}
|
||||
accessRights={accessOptions}
|
||||
selectedAccessRight={selectedAccess}
|
||||
withCancelButton={!isMultiSelect}
|
||||
cancelButtonLabel={t("Common:CancelButton")}
|
||||
onCancel={onClosePanels}
|
||||
emptyScreenImage={emptyScreenImage}
|
||||
emptyScreenHeader={t("PeopleSelector:EmptyHeader")}
|
||||
emptyScreenDescription={t("PeopleSelector:EmptyDescription")}
|
||||
searchEmptyScreenImage={emptyScreenImage}
|
||||
searchEmptyScreenHeader={t("People:NotFoundUsers")}
|
||||
searchEmptyScreenDescription={t("SearchEmptyDescription")}
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadNextPage={loadNextPage}
|
||||
totalItems={total}
|
||||
isLoading={isLoading}
|
||||
searchLoader={<Loaders.SelectorSearchLoader />}
|
||||
rowLoader={
|
||||
<Loaders.SelectorRowLoader
|
||||
isMultiSelect={false}
|
||||
isContainer={isLoading}
|
||||
isUser={true}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Aside>
|
||||
</>
|
||||
@ -146,7 +284,9 @@ AddUsersPanel.propTypes = {
|
||||
};
|
||||
|
||||
export default inject(({ auth }) => {
|
||||
return { theme: auth.settingsStore.theme };
|
||||
return {
|
||||
theme: auth.settingsStore.theme,
|
||||
};
|
||||
})(
|
||||
observer(
|
||||
withTranslation(["SharingPanel", "PeopleTranslations", "Common"])(
|
||||
|
@ -21,20 +21,18 @@ import LinkBlock from "./LinkBlock";
|
||||
import ToggleBlock from "./ToggleBlock";
|
||||
import PasswordAccessBlock from "./PasswordAccessBlock";
|
||||
import LimitTimeBlock from "./LimitTimeBlock";
|
||||
import { LinkType } from "../../../helpers/constants";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
|
||||
const EditLinkPanel = (props) => {
|
||||
const {
|
||||
t,
|
||||
roomId,
|
||||
linkId,
|
||||
isEdit,
|
||||
visible,
|
||||
password,
|
||||
setIsVisible,
|
||||
editExternalLink,
|
||||
setExternalLinks,
|
||||
setExternalLink,
|
||||
shareLink,
|
||||
unsavedChangesDialogVisible,
|
||||
setUnsavedChangesDialog,
|
||||
@ -47,7 +45,7 @@ const EditLinkPanel = (props) => {
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const linkTitle = link.sharedTo.title ?? "";
|
||||
const linkTitle = link?.sharedTo?.title ?? "";
|
||||
const [linkNameValue, setLinkNameValue] = useState(linkTitle);
|
||||
const [passwordValue, setPasswordValue] = useState(password);
|
||||
const [expirationDate, setExpirationDate] = useState(date);
|
||||
@ -93,7 +91,9 @@ const EditLinkPanel = (props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const newLink = JSON.parse(JSON.stringify(link));
|
||||
const externalLink = link ?? { access: 2, sharedTo: {} };
|
||||
|
||||
const newLink = JSON.parse(JSON.stringify(externalLink));
|
||||
|
||||
newLink.sharedTo.title = linkNameValue;
|
||||
newLink.sharedTo.password = passwordAccessIsChecked ? passwordValue : null;
|
||||
@ -102,10 +102,8 @@ const EditLinkPanel = (props) => {
|
||||
|
||||
setIsLoading(true);
|
||||
editExternalLink(roomId, newLink)
|
||||
.then((res) => {
|
||||
setExternalLinks(res);
|
||||
|
||||
const link = res.find((l) => l?.sharedTo?.id === linkId);
|
||||
.then((link) => {
|
||||
setExternalLink(link);
|
||||
|
||||
if (isEdit) {
|
||||
copy(linkValue);
|
||||
@ -271,25 +269,22 @@ export default inject(({ auth, dialogsStore, publicRoomStore }) => {
|
||||
setUnsavedChangesDialog,
|
||||
linkParams,
|
||||
} = dialogsStore;
|
||||
const { externalLinks, editExternalLink, setExternalLinks } = publicRoomStore;
|
||||
const { externalLinks, editExternalLink, setExternalLink } = publicRoomStore;
|
||||
const { isEdit } = linkParams;
|
||||
|
||||
const linkId = linkParams?.link?.sharedTo?.id;
|
||||
const link = externalLinks.find((l) => l?.sharedTo?.id === linkId);
|
||||
const template = externalLinks.find(
|
||||
(t) =>
|
||||
t?.sharedTo?.isTemplate && t?.sharedTo?.linkType === LinkType.External
|
||||
);
|
||||
const shareLink = link?.sharedTo?.shareLink ?? template?.sharedTo?.shareLink;
|
||||
|
||||
const shareLink = link?.sharedTo?.shareLink;
|
||||
|
||||
return {
|
||||
visible: editLinkPanelIsVisible,
|
||||
setIsVisible: setEditLinkPanelIsVisible,
|
||||
isEdit,
|
||||
linkId: link?.sharedTo?.id ?? template?.sharedTo?.id,
|
||||
linkId: link?.sharedTo?.id,
|
||||
editExternalLink,
|
||||
roomId: selectionParentRoom.id,
|
||||
setExternalLinks,
|
||||
setExternalLink,
|
||||
isLocked: !!link?.sharedTo?.password,
|
||||
password: link?.sharedTo?.password ?? "",
|
||||
date: link?.sharedTo?.expirationDate,
|
||||
@ -298,7 +293,7 @@ export default inject(({ auth, dialogsStore, publicRoomStore }) => {
|
||||
externalLinks,
|
||||
unsavedChangesDialogVisible,
|
||||
setUnsavedChangesDialog,
|
||||
link: link ?? template,
|
||||
link,
|
||||
language: auth.language,
|
||||
};
|
||||
})(
|
||||
|
@ -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`
|
||||
|
@ -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,
|
||||
@ -52,14 +55,16 @@ const InvitePanel = ({
|
||||
const [selectedRoom, setSelectedRoom] = useState(null);
|
||||
const [hasErrors, setHasErrors] = useState(false);
|
||||
const [shareLinks, setShareLinks] = useState([]);
|
||||
const [roomUsers, setRoomUsers] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
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 +72,6 @@ const InvitePanel = ({
|
||||
setExternalLinksVisible(visible);
|
||||
};
|
||||
|
||||
const onChangeActiveLink = (activeLink) => {
|
||||
setActiveLink(activeLink);
|
||||
};
|
||||
|
||||
const selectRoom = () => {
|
||||
const room = folders.find((folder) => folder.id === roomId);
|
||||
|
||||
@ -84,26 +85,24 @@ 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -241,6 +240,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 +248,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}
|
||||
/>
|
||||
@ -260,13 +261,15 @@ const InvitePanel = ({
|
||||
<InviteInput
|
||||
t={t}
|
||||
onClose={onClose}
|
||||
roomUsers={roomUsers}
|
||||
roomType={roomType}
|
||||
inputsRef={inputsRef}
|
||||
addUsersPanelVisible={addUsersPanelVisible}
|
||||
setAddUsersPanelVisible={setAddUsersPanelVisible}
|
||||
isMobileView={isMobileView}
|
||||
/>
|
||||
{infoBarIsVisible && hasAdmins && (
|
||||
<InfoBar t={t} onClose={onCloseBar} />
|
||||
)}
|
||||
{hasInvitedUsers && (
|
||||
<ItemsList
|
||||
t={t}
|
||||
@ -289,7 +292,6 @@ const InvitePanel = ({
|
||||
onChangeExternalLinksVisible,
|
||||
externalLinksVisible,
|
||||
onClose,
|
||||
roomUsers,
|
||||
setHasErrors,
|
||||
scrollAllPanelContent,
|
||||
hasInvitedUsers,
|
||||
@ -450,5 +452,6 @@ export default inject(({ auth, peopleStore, filesStore, dialogsStore }) => {
|
||||
"Translations",
|
||||
"Common",
|
||||
"InfoPanel",
|
||||
"PeopleSelector",
|
||||
])(observer(InvitePanel))
|
||||
);
|
||||
|
@ -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}
|
||||
|
@ -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";
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
StyledToggleButton,
|
||||
StyledDescription,
|
||||
} from "../StyledInvitePanel";
|
||||
import { RoomsType, ShareAccessRights } from "@docspace/common/constants";
|
||||
|
||||
const ExternalLinks = ({
|
||||
t,
|
||||
@ -29,12 +30,12 @@ const ExternalLinks = ({
|
||||
roomType,
|
||||
defaultAccess,
|
||||
shareLinks,
|
||||
setShareLinks,
|
||||
setInvitationLinks,
|
||||
isOwner,
|
||||
getInfo,
|
||||
onChangeExternalLinksVisible,
|
||||
externalLinksVisible,
|
||||
onChangeActiveLink,
|
||||
setActiveLink,
|
||||
activeLink,
|
||||
isMobileView,
|
||||
}) => {
|
||||
@ -42,44 +43,37 @@ 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 type =
|
||||
roomType === RoomsType.PublicRoom
|
||||
? ShareAccessRights.RoomManager
|
||||
: ShareAccessRights.ReadOnly;
|
||||
|
||||
const link = await setInvitationLinks(roomId, "Invite", type);
|
||||
|
||||
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 +81,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);
|
||||
|
@ -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;
|
@ -24,16 +24,19 @@ import {
|
||||
StyledDescription,
|
||||
} from "../StyledInvitePanel";
|
||||
|
||||
import Filter from "@docspace/common/api/people/filter";
|
||||
import { getMembersList } from "@docspace/common/api/people";
|
||||
|
||||
const searchUsersThreshold = 2;
|
||||
|
||||
const InviteInput = ({
|
||||
defaultAccess,
|
||||
getUsersByQuery,
|
||||
hideSelector,
|
||||
inviteItems,
|
||||
onClose,
|
||||
roomId,
|
||||
roomType,
|
||||
setInviteItems,
|
||||
roomUsers,
|
||||
t,
|
||||
isOwner,
|
||||
inputsRef,
|
||||
@ -44,15 +47,12 @@ const InviteInput = ({
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [usersList, setUsersList] = useState([]);
|
||||
const [searchPanelVisible, setSearchPanelVisible] = useState(false);
|
||||
const [isAddEmailPanelBlocked, setIsAddEmailPanelBlocked] = useState(true);
|
||||
|
||||
const [selectedAccess, setSelectedAccess] = useState(defaultAccess);
|
||||
|
||||
const searchRef = useRef();
|
||||
|
||||
const inRoom = (id) => {
|
||||
return roomUsers.some((user) => user.sharedTo.id === id);
|
||||
};
|
||||
|
||||
const toUserItems = (query) => {
|
||||
const addresses = parseAddresses(query);
|
||||
const uid = () => Math.random().toString(36).slice(-6);
|
||||
@ -81,10 +81,17 @@ const InviteInput = ({
|
||||
const searchByQuery = async (value) => {
|
||||
const query = value.trim();
|
||||
|
||||
if (!!query.length) {
|
||||
const users = await getUsersByQuery(query);
|
||||
setUsersList(users);
|
||||
} else {
|
||||
if (query.length >= searchUsersThreshold) {
|
||||
const filter = Filter.getFilterWithOutDisabledUser();
|
||||
filter.search = query;
|
||||
|
||||
const users = await getMembersList(roomId, filter);
|
||||
|
||||
setUsersList(users.items);
|
||||
setIsAddEmailPanelBlocked(false);
|
||||
}
|
||||
|
||||
if (!query) {
|
||||
closeInviteInputPanel();
|
||||
setInputValue("");
|
||||
setUsersList([]);
|
||||
@ -100,11 +107,20 @@ const InviteInput = ({
|
||||
const value = e.target.value;
|
||||
const clearValue = value.trim();
|
||||
|
||||
if ((!!usersList.length || clearValue.length > 2) && !searchPanelVisible) {
|
||||
openInviteInputPanel();
|
||||
setInputValue(value);
|
||||
|
||||
if (clearValue.length < searchUsersThreshold) {
|
||||
setUsersList([]);
|
||||
setIsAddEmailPanelBlocked(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setInputValue(value);
|
||||
if (
|
||||
(!!usersList.length || clearValue.length >= searchUsersThreshold) &&
|
||||
!searchPanelVisible
|
||||
) {
|
||||
openInviteInputPanel();
|
||||
}
|
||||
|
||||
if (roomId !== -1) {
|
||||
debouncedSearch(clearValue);
|
||||
@ -124,9 +140,7 @@ const InviteInput = ({
|
||||
};
|
||||
|
||||
const getItemContent = (item) => {
|
||||
const { avatar, displayName, email, id } = item;
|
||||
|
||||
const invited = inRoom(id);
|
||||
const { avatar, displayName, email, id, shared } = item;
|
||||
|
||||
item.access = selectedAccess;
|
||||
|
||||
@ -146,18 +160,18 @@ const InviteInput = ({
|
||||
<DropDownItem
|
||||
key={id}
|
||||
onClick={addUser}
|
||||
disabled={invited}
|
||||
disabled={shared}
|
||||
height={48}
|
||||
className="list-item"
|
||||
>
|
||||
<Avatar size="min" role="user" source={avatar} />
|
||||
<div>
|
||||
<SearchItemText primary disabled={invited}>
|
||||
<SearchItemText primary disabled={shared}>
|
||||
{displayName}
|
||||
</SearchItemText>
|
||||
<SearchItemText>{email}</SearchItemText>
|
||||
</div>
|
||||
{invited && <SearchItemText info>{t("Invited")}</SearchItemText>}
|
||||
{shared && <SearchItemText info>{t("Invited")}</SearchItemText>}
|
||||
</DropDownItem>
|
||||
);
|
||||
};
|
||||
@ -203,13 +217,27 @@ const InviteInput = ({
|
||||
};
|
||||
|
||||
const closeInviteInputPanel = (e) => {
|
||||
// if (e?.target.tagName.toUpperCase() == "INPUT") return;
|
||||
if (e?.target.tagName.toUpperCase() === "INPUT") return;
|
||||
|
||||
setSearchPanelVisible(false);
|
||||
};
|
||||
|
||||
const foundUsers = usersList.map((user) => getItemContent(user));
|
||||
|
||||
const addEmailPanel = isAddEmailPanelBlocked ? (
|
||||
<></>
|
||||
) : (
|
||||
<DropDownItem
|
||||
className="add-item"
|
||||
style={{ width: "inherit" }}
|
||||
textOverflow
|
||||
onClick={addEmail}
|
||||
height={48}
|
||||
>
|
||||
{t("Common:AddButton")} «{inputValue}»
|
||||
</DropDownItem>
|
||||
);
|
||||
|
||||
const accessOptions = getAccessOptions(t, roomType);
|
||||
|
||||
const onSelectAccess = (item) => {
|
||||
@ -277,7 +305,7 @@ const InviteInput = ({
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</StyledInviteInput>
|
||||
{inputValue.length > 2 && (
|
||||
{inputValue.length >= searchUsersThreshold && (
|
||||
<StyledDropDown
|
||||
width={searchRef?.current?.offsetWidth}
|
||||
isDefaultMode={false}
|
||||
@ -288,19 +316,7 @@ const InviteInput = ({
|
||||
eventTypes="click"
|
||||
{...dropDownMaxHeight}
|
||||
>
|
||||
{!!usersList.length ? (
|
||||
foundUsers
|
||||
) : (
|
||||
<DropDownItem
|
||||
className="add-item"
|
||||
style={{ width: "inherit" }}
|
||||
textOverflow
|
||||
onClick={addEmail}
|
||||
height={48}
|
||||
>
|
||||
{t("Common:AddButton")} «{inputValue}»
|
||||
</DropDownItem>
|
||||
)}
|
||||
{!!usersList.length ? foundUsers : addEmailPanel}
|
||||
</StyledDropDown>
|
||||
)}
|
||||
|
||||
@ -320,7 +336,6 @@ const InviteInput = ({
|
||||
onParentPanelClose={onClose}
|
||||
onClose={closeUsersPanel}
|
||||
visible={addUsersPanelVisible}
|
||||
shareDataItems={roomUsers}
|
||||
tempDataItems={inviteItems}
|
||||
setDataItems={addItems}
|
||||
accessOptions={accessOptions}
|
||||
@ -329,6 +344,7 @@ const InviteInput = ({
|
||||
defaultAccess={selectedAccess}
|
||||
withoutBackground={isMobileView}
|
||||
withBlur={!isMobileView}
|
||||
roomId={roomId}
|
||||
/>
|
||||
)}
|
||||
</StyledInviteInputContainer>
|
||||
@ -336,16 +352,14 @@ const InviteInput = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(({ auth, peopleStore, filesStore, dialogsStore }) => {
|
||||
export default inject(({ auth, dialogsStore }) => {
|
||||
const { theme } = auth.settingsStore;
|
||||
const { isOwner } = auth.userStore.user;
|
||||
const { getUsersByQuery } = peopleStore.usersStore;
|
||||
const { invitePanelOptions, setInviteItems, inviteItems } = dialogsStore;
|
||||
|
||||
return {
|
||||
setInviteItems,
|
||||
inviteItems,
|
||||
getUsersByQuery,
|
||||
roomId: invitePanelOptions.roomId,
|
||||
hideSelector: invitePanelOptions.hideSelector,
|
||||
defaultAccess: invitePanelOptions.defaultAccess,
|
||||
|
@ -149,6 +149,7 @@ const Item = ({
|
||||
filteredAccesses={filteredAccesses}
|
||||
setIsOpenItemAccess={setIsOpenItemAccess}
|
||||
isMobileView={isMobileView}
|
||||
noBorder
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -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;
|
||||
|
@ -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};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,21 +68,57 @@ const FilesItemTitle = ({ t, selection, isSeveralItems }) => {
|
||||
)}
|
||||
</div>
|
||||
<Text className="text">{selection.title}</Text>
|
||||
{selection && (
|
||||
<ItemContextOptions
|
||||
t={t}
|
||||
itemTitleRef={itemTitleRef}
|
||||
selection={selection}
|
||||
/>
|
||||
)}
|
||||
<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}
|
||||
itemTitleRef={itemTitleRef}
|
||||
selection={selection}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</StyledTitle>
|
||||
);
|
||||
};
|
||||
|
||||
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))
|
||||
);
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -49,7 +49,6 @@ const ItemTitle = ({
|
||||
selectionLength={selectionLength}
|
||||
selection={filesItemSelection}
|
||||
isSeveralItems={isSeveralItems}
|
||||
getIcon={getIcon}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,191 @@
|
||||
import React, { useState, useCallback, useEffect, 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";
|
||||
import { tablet, mobile, isMobile } from "@docspace/components/utils/device";
|
||||
|
||||
const StyledMembersList = styled.div`
|
||||
height: ${({ withBanner, isPublicRoomType }) =>
|
||||
isPublicRoomType
|
||||
? withBanner
|
||||
? "calc(100vh - 442px)"
|
||||
: "calc(100vh - 286px)"
|
||||
: "calc(100vh - 266px)"};
|
||||
|
||||
@media ${tablet} {
|
||||
height: ${({ withBanner, isPublicRoomType }) =>
|
||||
isPublicRoomType
|
||||
? withBanner
|
||||
? "calc(100vh - 362px)"
|
||||
: "calc(100vh - 206px)"
|
||||
: "calc(100vh - 186px)"};
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
height: ${({ withBanner, isPublicRoomType }) =>
|
||||
isPublicRoomType
|
||||
? withBanner
|
||||
? "calc(100vh - 426px)"
|
||||
: "calc(100vh - 270px)"
|
||||
: "calc(100vh - 250px)"};
|
||||
}
|
||||
`;
|
||||
|
||||
const Item = memo(({ data, index, style }) => {
|
||||
const {
|
||||
t,
|
||||
members,
|
||||
setMembers,
|
||||
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% - 8px)" }}>
|
||||
<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}
|
||||
setMembers={setMembers}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}, areEqual);
|
||||
|
||||
const MembersList = (props) => {
|
||||
const {
|
||||
t,
|
||||
security,
|
||||
membersHelper,
|
||||
currentMember,
|
||||
updateRoomMemberRole,
|
||||
selectionParentRoom,
|
||||
setSelectionParentRoom,
|
||||
changeUserType,
|
||||
setIsScrollLocked,
|
||||
members,
|
||||
setMembers,
|
||||
hasNextPage,
|
||||
itemCount,
|
||||
onRepeatInvitation,
|
||||
loadNextPage,
|
||||
isPublicRoomType,
|
||||
withBanner,
|
||||
} = props;
|
||||
|
||||
const itemsCount = members.length;
|
||||
|
||||
const canInviteUserInRoomAbility = security?.EditAccess;
|
||||
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
|
||||
const [isMobileView, setIsMobileView] = useState(isMobile());
|
||||
|
||||
const onResize = () => {
|
||||
const isMobileView = isMobile();
|
||||
setIsMobileView(isMobileView);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("resize", onResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", onResize);
|
||||
};
|
||||
});
|
||||
|
||||
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
|
||||
withBanner={withBanner}
|
||||
isPublicRoomType={isPublicRoomType}
|
||||
>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<InfiniteLoader
|
||||
isItemLoaded={isItemLoaded}
|
||||
itemCount={itemCount}
|
||||
loadMoreItems={loadMoreItems}
|
||||
>
|
||||
{({ onItemsRendered, ref }) => {
|
||||
const listWidth = isMobileView ? width + 16 : width + 20; // for scroll
|
||||
|
||||
return (
|
||||
<List
|
||||
ref={ref}
|
||||
width={listWidth}
|
||||
height={height}
|
||||
itemCount={itemsCount}
|
||||
itemSize={48}
|
||||
itemData={{
|
||||
t,
|
||||
security,
|
||||
membersHelper,
|
||||
currentMember,
|
||||
updateRoomMemberRole,
|
||||
selectionParentRoom,
|
||||
setSelectionParentRoom,
|
||||
changeUserType,
|
||||
setIsScrollLocked,
|
||||
members,
|
||||
setMembers,
|
||||
canInviteUserInRoomAbility,
|
||||
onRepeatInvitation,
|
||||
}}
|
||||
outerElementType={CustomScrollbarsVirtualList}
|
||||
onItemsRendered={onItemsRendered}
|
||||
>
|
||||
{Item}
|
||||
</List>
|
||||
);
|
||||
}}
|
||||
</InfiniteLoader>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</StyledMembersList>
|
||||
);
|
||||
};
|
||||
|
||||
export default MembersList;
|
@ -9,10 +9,15 @@ 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,
|
||||
user,
|
||||
setMembers,
|
||||
isExpect,
|
||||
membersHelper,
|
||||
currentMember,
|
||||
@ -21,6 +26,9 @@ const User = ({
|
||||
setSelectionParentRoom,
|
||||
changeUserType,
|
||||
setIsScrollLocked,
|
||||
isTitle,
|
||||
onRepeatInvitation,
|
||||
showInviteIcon,
|
||||
}) => {
|
||||
if (!selectionParentRoom) return null;
|
||||
if (!user.displayName && !user.email) return null;
|
||||
@ -48,22 +56,45 @@ const User = ({
|
||||
})
|
||||
.then(() => {
|
||||
setIsLoading(false);
|
||||
const inRoomMembers = selectionParentRoom.members.inRoom;
|
||||
const users = selectionParentRoom.members.users;
|
||||
const administrators = selectionParentRoom.members.administrators;
|
||||
const expectedMembers = selectionParentRoom.members.expected;
|
||||
if (option.key === "remove") {
|
||||
setMembers({
|
||||
users: users?.filter((m) => m.id !== user.id),
|
||||
administrators: administrators?.filter((m) => m.id !== user.id),
|
||||
expected: expectedMembers?.filter((m) => m.id !== user.id),
|
||||
});
|
||||
|
||||
setSelectionParentRoom({
|
||||
...selectionParentRoom,
|
||||
members: {
|
||||
inRoom: inRoomMembers?.filter((m) => m.id !== user.id),
|
||||
users: users?.filter((m) => m.id !== user.id),
|
||||
administrators: administrators?.filter((m) => m.id !== user.id),
|
||||
expected: expectedMembers?.filter((m) => m.id !== user.id),
|
||||
},
|
||||
});
|
||||
//setUserIsRemoved(true);
|
||||
} else {
|
||||
setMembers({
|
||||
users: users?.map((m) =>
|
||||
m.id === user.id ? { ...m, access: option.access } : m
|
||||
),
|
||||
administrators: administrators?.map((m) =>
|
||||
m.id === user.id ? { ...m, access: option.access } : m
|
||||
),
|
||||
expected: expectedMembers?.map((m) =>
|
||||
m.id === user.id ? { ...m, access: option.access } : m
|
||||
),
|
||||
});
|
||||
|
||||
setSelectionParentRoom({
|
||||
...selectionParentRoom,
|
||||
members: {
|
||||
inRoom: inRoomMembers?.map((m) =>
|
||||
users: users?.map((m) =>
|
||||
m.id === user.id ? { ...m, access: option.access } : m
|
||||
),
|
||||
administrators: administrators?.map((m) =>
|
||||
m.id === user.id ? { ...m, access: option.access } : m
|
||||
),
|
||||
expected: expectedMembers?.map((m) =>
|
||||
@ -126,7 +157,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}
|
||||
|
@ -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,80 +27,116 @@ const Members = ({
|
||||
setIsScrollLocked,
|
||||
|
||||
getRoomMembers,
|
||||
getRoomLinks,
|
||||
updateRoomMemberRole,
|
||||
setView,
|
||||
roomsView,
|
||||
resendEmailInvitations,
|
||||
setInvitePanelOptions,
|
||||
setInviteUsersWarningDialogVisible,
|
||||
changeUserType,
|
||||
isGracePeriod,
|
||||
isPublicRoomType,
|
||||
|
||||
setExternalLinks,
|
||||
membersFilter,
|
||||
externalLinks,
|
||||
}) => {
|
||||
const membersHelper = new MembersHelper({ t });
|
||||
|
||||
const [members, setMembers] = useState(null);
|
||||
const [showLoader, setShowLoader] = useState(false);
|
||||
|
||||
const security = selectionParentRoom ? selectionParentRoom.security : {};
|
||||
|
||||
const canInviteUserInRoomAbility = security?.EditAccess;
|
||||
const fetchMembers = async (roomId, clearFilter = true) => {
|
||||
const isPublic = selection?.roomType ?? selectionParentRoom?.roomType;
|
||||
const requests = [getRoomMembers(roomId, clearFilter)];
|
||||
|
||||
const fetchMembers = async (roomId) => {
|
||||
let timerId;
|
||||
if (members) timerId = setTimeout(() => setShowLoader(true), 1000);
|
||||
let data = await getRoomMembers(roomId);
|
||||
if (isPublic && clearFilter) {
|
||||
requests.push(getRoomLinks(roomId));
|
||||
}
|
||||
|
||||
setExternalLinks(data);
|
||||
const [data, links] = await Promise.all(requests);
|
||||
|
||||
data = data.filter((m) => m.sharedTo.email || m.sharedTo.displayName);
|
||||
clearTimeout(timerId);
|
||||
links && setExternalLinks(links);
|
||||
|
||||
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) {
|
||||
member.isExpect = true;
|
||||
expectedMembers.push(member);
|
||||
} else if (
|
||||
member.access === ShareAccessRights.FullAccess ||
|
||||
member.access === ShareAccessRights.RoomManager
|
||||
) {
|
||||
administrators.push(member);
|
||||
} else {
|
||||
users.push(member);
|
||||
}
|
||||
});
|
||||
|
||||
setShowLoader(false);
|
||||
let hasPrevAdminsTitle =
|
||||
members?.roomId === roomId
|
||||
? getHasPrevTitle(members?.administrators, "administration")
|
||||
: false;
|
||||
|
||||
if (administrators.length && !hasPrevAdminsTitle) {
|
||||
administrators.unshift({
|
||||
id: "administration",
|
||||
displayName: t("Administration"),
|
||||
isTitle: true,
|
||||
});
|
||||
}
|
||||
|
||||
let hasPrevUsersTitle =
|
||||
members?.roomId === roomId
|
||||
? getHasPrevTitle(members?.users, "user")
|
||||
: false;
|
||||
|
||||
if (users.length && !hasPrevUsersTitle) {
|
||||
users.unshift({ id: "user", displayName: t("Users"), isTitle: true });
|
||||
}
|
||||
|
||||
let hasPrevExpectedTitle =
|
||||
members?.roomId === roomId
|
||||
? getHasPrevTitle(members?.expected, "expected")
|
||||
: false;
|
||||
|
||||
if (expectedMembers.length && !hasPrevExpectedTitle) {
|
||||
expectedMembers.unshift({
|
||||
id: "expected",
|
||||
displayName: t("ExpectUsers"),
|
||||
isTitle: true,
|
||||
isExpect: true,
|
||||
});
|
||||
}
|
||||
|
||||
setUpdateRoomMembers(false);
|
||||
|
||||
return {
|
||||
inRoom: inRoomMembers,
|
||||
users,
|
||||
administrators,
|
||||
expected: expectedMembers,
|
||||
roomId,
|
||||
};
|
||||
};
|
||||
|
||||
const updateSelectionParentRoomAction = useCallback(async () => {
|
||||
if (!selectionParentRoom) return;
|
||||
|
||||
if (selectionParentRoom.members) {
|
||||
setMembers(selectionParentRoom.members);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchedMembers = await fetchMembers(selectionParentRoom.id);
|
||||
setSelectionParentRoom({
|
||||
...selectionParentRoom,
|
||||
members: fetchedMembers,
|
||||
});
|
||||
}, [selectionParentRoom]);
|
||||
|
||||
useEffect(() => {
|
||||
updateSelectionParentRoomAction();
|
||||
}, [selectionParentRoom, updateSelectionParentRoomAction]);
|
||||
const getHasPrevTitle = (array, type) => {
|
||||
return array.findIndex((x) => x.id === type) > -1;
|
||||
};
|
||||
|
||||
const updateSelectionParentRoomActionSelection = useCallback(async () => {
|
||||
if (!selection.isRoom) return;
|
||||
|
||||
const fetchedMembers = await fetchMembers(selection.id);
|
||||
|
||||
setMembers(fetchedMembers);
|
||||
|
||||
setSelectionParentRoom({
|
||||
...selection,
|
||||
members: fetchedMembers,
|
||||
@ -129,6 +158,7 @@ const Members = ({
|
||||
...selectionParentRoom,
|
||||
members: fetchedMembers,
|
||||
});
|
||||
|
||||
setMembers(fetchedMembers);
|
||||
}, [selectionParentRoom, selection?.id, updateRoomMembers]);
|
||||
|
||||
@ -141,135 +171,74 @@ 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)
|
||||
resendEmailInvitations(selectionParentRoom.id, true)
|
||||
.then(() =>
|
||||
toastr.success(t("PeopleTranslations:SuccessSentMultipleInvitatios"))
|
||||
)
|
||||
.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];
|
||||
|
||||
const adminsTitleCount = administrators.length ? 1 : 0;
|
||||
const usersTitleCount = users.length ? 1 : 0;
|
||||
const expectedTitleCount = expected.length ? 1 : 0;
|
||||
|
||||
const headersCount = adminsTitleCount + usersTitleCount + expectedTitleCount;
|
||||
|
||||
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}
|
||||
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>
|
||||
|
||||
{!!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>
|
||||
<MembersList
|
||||
loadNextPage={loadNextPage}
|
||||
t={t}
|
||||
security={security}
|
||||
members={membersList}
|
||||
membersHelper={membersHelper}
|
||||
currentMember={currentMember}
|
||||
updateRoomMemberRole={updateRoomMemberRole}
|
||||
selectionParentRoom={selectionParentRoom}
|
||||
setSelectionParentRoom={setSelectionParentRoom}
|
||||
changeUserType={changeUserType}
|
||||
setIsScrollLocked={setIsScrollLocked}
|
||||
hasNextPage={membersList.length - headersCount < membersFilter.total}
|
||||
itemCount={membersFilter.total}
|
||||
onRepeatInvitation={onRepeatInvitation}
|
||||
isPublicRoomType={isPublicRoomType}
|
||||
withBanner={isPublicRoomType && externalLinks.length > 0}
|
||||
setMembers={setMembers}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(
|
||||
({
|
||||
auth,
|
||||
filesStore,
|
||||
peopleStore,
|
||||
dialogsStore,
|
||||
selectedFolderStore,
|
||||
publicRoomStore,
|
||||
}) => {
|
||||
({ auth, filesStore, peopleStore, selectedFolderStore, publicRoomStore }) => {
|
||||
const {
|
||||
setIsMobileHidden,
|
||||
selectionParentRoom,
|
||||
|
||||
selection,
|
||||
setSelectionParentRoom,
|
||||
setView,
|
||||
roomsView,
|
||||
@ -279,15 +248,17 @@ 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;
|
||||
const { roomLinks, setExternalLinks } = publicRoomStore;
|
||||
|
||||
const roomType =
|
||||
selectedFolderStore.roomType ?? selectionParentRoom?.roomType;
|
||||
@ -298,13 +269,13 @@ export default inject(
|
||||
return {
|
||||
setView,
|
||||
roomsView,
|
||||
setIsMobileHidden,
|
||||
selectionParentRoom,
|
||||
setSelectionParentRoom,
|
||||
|
||||
setIsScrollLocked,
|
||||
|
||||
getRoomMembers,
|
||||
getRoomLinks,
|
||||
updateRoomMemberRole,
|
||||
|
||||
updateRoomMembers,
|
||||
@ -312,13 +283,12 @@ export default inject(
|
||||
|
||||
selfId,
|
||||
|
||||
setInvitePanelOptions,
|
||||
setInviteUsersWarningDialogVisible,
|
||||
resendEmailInvitations,
|
||||
changeUserType,
|
||||
isGracePeriod,
|
||||
isPublicRoomType,
|
||||
setExternalLinks,
|
||||
membersFilter,
|
||||
externalLinks: roomLinks,
|
||||
};
|
||||
}
|
||||
)(
|
||||
|
@ -40,15 +40,8 @@ const LinkRow = (props) => {
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const {
|
||||
title,
|
||||
shareLink,
|
||||
id,
|
||||
password,
|
||||
disabled,
|
||||
expirationDate,
|
||||
isExpired,
|
||||
} = link.sharedTo;
|
||||
const { title, shareLink, password, disabled, expirationDate, isExpired } =
|
||||
link.sharedTo;
|
||||
|
||||
const isLocked = !!password;
|
||||
const expiryDate = !!expirationDate;
|
||||
@ -76,8 +69,8 @@ const LinkRow = (props) => {
|
||||
newLink.sharedTo.disabled = !newLink.sharedTo.disabled;
|
||||
|
||||
editExternalLink(roomId, newLink)
|
||||
.then((res) => {
|
||||
setExternalLink(id, res);
|
||||
.then((link) => {
|
||||
setExternalLink(link);
|
||||
|
||||
disabled
|
||||
? toastr.success(t("Files:LinkEnabledSuccessfully"))
|
||||
|
@ -30,7 +30,6 @@ const PublicRoomBar = (props) => {
|
||||
size={8}
|
||||
iconName={CrossReactSvg}
|
||||
onClick={onClose}
|
||||
color="#657077"
|
||||
/> */}
|
||||
</StyledPublicRoomBar>
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
) : (
|
||||
|
@ -10,7 +10,6 @@ const StyledRowContent = styled(RowContent)`
|
||||
.row-main-container-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
${(props) => props.isSettingNotPaid && UnavailableStyles}
|
||||
@ -27,8 +26,7 @@ export const AuditContent = ({ sectionWidth, item, isSettingNotPaid }) => {
|
||||
sideColor="#A3A9AE"
|
||||
nameColor="#D0D5DA"
|
||||
sectionWidth={sectionWidth}
|
||||
isSettingNotPaid={isSettingNotPaid}
|
||||
>
|
||||
isSettingNotPaid={isSettingNotPaid}>
|
||||
<div className="user-container-wrapper">
|
||||
<Text fontWeight={600} fontSize="14px" isTextOverflow={true}>
|
||||
{item.user}
|
||||
@ -40,16 +38,14 @@ export const AuditContent = ({ sectionWidth, item, isSettingNotPaid }) => {
|
||||
fontSize="12px"
|
||||
fontWeight={600}
|
||||
truncate={true}
|
||||
className="settings_unavailable"
|
||||
>
|
||||
className="settings_unavailable">
|
||||
{dateStr}
|
||||
</Text>
|
||||
<Text
|
||||
fontSize="12px"
|
||||
as="div"
|
||||
fontWeight={600}
|
||||
className="settings_unavailable"
|
||||
>
|
||||
className="settings_unavailable">
|
||||
{`${item.context ? item.context + " |" : ""} ${item.action}`}
|
||||
</Text>
|
||||
</StyledRowContent>
|
||||
|
@ -30,6 +30,7 @@ import { getContextMenuKeysByType } from "SRC_DIR/helpers/plugins";
|
||||
import { PluginContextMenuItemType } from "SRC_DIR/helpers/plugins/constants";
|
||||
import { CategoryType } from "SRC_DIR/helpers/constants";
|
||||
import debounce from "lodash.debounce";
|
||||
import clone from "lodash/clone";
|
||||
import Queue from "queue-promise";
|
||||
|
||||
const { FilesFilter, RoomsFilter } = api;
|
||||
@ -80,8 +81,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);
|
||||
|
||||
@ -2426,9 +2428,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 = clone(this.membersFilter);
|
||||
newFilter.total = res.total;
|
||||
this.setMembersFilter(newFilter);
|
||||
|
||||
return res.items;
|
||||
});
|
||||
};
|
||||
|
||||
setMembersFilter = (filter) => {
|
||||
this.membersFilter = filter;
|
||||
};
|
||||
|
||||
getRoomLinks = (id) => {
|
||||
return api.rooms
|
||||
.getRoomMembers(id, { filterType: 2 }) // 2 (External link)
|
||||
.then((res) => res.items);
|
||||
};
|
||||
|
||||
updateRoomMemberRole(id, data) {
|
||||
return api.rooms.updateRoomMemberRole(id, data);
|
||||
@ -3623,16 +3664,16 @@ 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);
|
||||
};
|
||||
|
||||
resendEmailInvitations = async (id, usersIds) => {
|
||||
return await api.rooms.resendEmailInvitations(id, usersIds);
|
||||
resendEmailInvitations = async (id, resendAll) => {
|
||||
return await api.rooms.resendEmailInvitations(id, resendAll);
|
||||
};
|
||||
|
||||
getRoomSecurityInfo = async (id) => {
|
||||
return await api.rooms.getRoomSecurityInfo(id);
|
||||
return await api.rooms.getRoomSecurityInfo(id).then((res) => res.items);
|
||||
};
|
||||
|
||||
setRoomSecurity = async (id, data) => {
|
||||
|
@ -45,12 +45,25 @@ class PublicRoomStore {
|
||||
this.externalLinks = externalLinks;
|
||||
};
|
||||
|
||||
setExternalLink = (linkId, data) => {
|
||||
const linkIndex = this.externalLinks.findIndex(
|
||||
(l) => l.sharedTo.id === linkId
|
||||
deleteExternalLink = (linkId) => {
|
||||
const externalLinks = this.externalLinks.filter(
|
||||
(l) => l.sharedTo.id !== linkId
|
||||
);
|
||||
const dataLink = data.find((l) => l.sharedTo.id === linkId);
|
||||
this.externalLinks[linkIndex] = dataLink;
|
||||
this.externalLinks = externalLinks;
|
||||
};
|
||||
|
||||
setExternalLink = (link) => {
|
||||
const linkIndex = this.externalLinks.findIndex(
|
||||
(l) => l.sharedTo.id === link.sharedTo.id
|
||||
);
|
||||
const externalLinks = this.externalLinks;
|
||||
|
||||
if (linkIndex === -1) {
|
||||
externalLinks.push(link);
|
||||
this.externalLinks = externalLinks;
|
||||
} else {
|
||||
externalLinks[linkIndex] = link;
|
||||
}
|
||||
};
|
||||
|
||||
setExternalLinks = (links) => {
|
||||
|
@ -358,3 +358,36 @@ export function getUsersByQuery(query) {
|
||||
url: `/people/search?query=${query}`,
|
||||
});
|
||||
}
|
||||
|
||||
export function getMembersList(roomId, filter = Filter.getDefault()) {
|
||||
let params = "";
|
||||
|
||||
if (filter) {
|
||||
checkFilterInstance(filter, Filter);
|
||||
|
||||
params = `?${filter.toApiUrlParams(
|
||||
"id,email,avatar,icon,displayName,hasAvatar,isOwner,isAdmin,isVisitor,isCollaborator,"
|
||||
)}`;
|
||||
}
|
||||
|
||||
const excludeShared = filter.excludeShared ? filter.excludeShared : false;
|
||||
|
||||
if (params) {
|
||||
params += `&excludeShared=${excludeShared}`;
|
||||
} else {
|
||||
params = `excludeShared=${excludeShared}`;
|
||||
}
|
||||
|
||||
return request({
|
||||
method: "get",
|
||||
url: `people/room/${roomId}${params}`,
|
||||
}).then((res) => {
|
||||
res.items = res.items.map((user) => {
|
||||
if (user && user.displayName) {
|
||||
user.displayName = Encoder.htmlDecode(user.displayName);
|
||||
}
|
||||
return user;
|
||||
});
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
@ -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) => {
|
||||
@ -288,12 +299,12 @@ export const setInvitationLinks = async (roomId, linkId, title, access) => {
|
||||
return res;
|
||||
};
|
||||
|
||||
export const resendEmailInvitations = async (id, usersIds) => {
|
||||
export const resendEmailInvitations = async (id, resendAll = true) => {
|
||||
const options = {
|
||||
method: "post",
|
||||
url: `/files/rooms/${id}/resend`,
|
||||
data: {
|
||||
usersIds,
|
||||
resendAll,
|
||||
},
|
||||
};
|
||||
|
||||
@ -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);
|
||||
|
@ -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>
|
||||
<StyledSubmenuBottomLine className="bottom-line" />
|
||||
{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,
|
||||
};
|
||||
|
@ -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`
|
||||
|
@ -28,9 +28,10 @@ namespace ASC.Files.Core.ApiModels.RequestDto;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public class UserInvintationRequestDto
|
||||
public class UserInvitationRequestDto
|
||||
{
|
||||
/// <summary>List of user IDs</summary>
|
||||
/// <type>System.Collections.Generic.IEnumerable{System.Guid}, System.Collections.Generic</type>
|
||||
public IEnumerable<Guid> UsersIds { get; set; }
|
||||
public bool ResendAll { get; set; }
|
||||
}
|
@ -74,7 +74,6 @@ public class FileShareLink
|
||||
public string Password { get; set; }
|
||||
public bool? Disabled { get; set; }
|
||||
public bool? DenyDownload { get; set; }
|
||||
public bool IsTemplate { get; set; }
|
||||
public bool? IsExpired { get; set; }
|
||||
}
|
||||
|
||||
@ -126,7 +125,6 @@ public class FileShareDtoHelper
|
||||
Password = aceWrapper.FileShareOptions?.Password,
|
||||
Disabled = aceWrapper.FileShareOptions?.Disabled is true ? true : expired,
|
||||
DenyDownload = aceWrapper.FileShareOptions?.DenyDownload,
|
||||
IsTemplate = aceWrapper.IsTemplate,
|
||||
LinkType = aceWrapper.SubjectType switch
|
||||
{
|
||||
SubjectType.InvitationLink => LinkType.Invitation,
|
||||
|
@ -24,6 +24,8 @@
|
||||
// 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
|
||||
|
||||
using User = ASC.Core.Common.EF.User;
|
||||
|
||||
namespace ASC.Files.Core.Data;
|
||||
|
||||
[Scope]
|
||||
@ -202,6 +204,164 @@ internal abstract class SecurityBaseDao<T> : AbstractDao
|
||||
return InternalGetPureShareRecordsAsync(entry);
|
||||
}
|
||||
|
||||
public async Task<int> GetPureSharesCountAsync(FileEntry<T> entry, ShareFilterType filterType, EmployeeActivationStatus? status)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
await using var filesDbContext = _dbContextFactory.CreateDbContext();
|
||||
|
||||
var q = await GetPureSharesQuery(entry, filterType, filesDbContext);
|
||||
|
||||
if (status.HasValue)
|
||||
{
|
||||
q = q.Join(filesDbContext.Users, s => s.Subject, u => u.Id,
|
||||
(s, u) => new SecurityUserRecord { Security = s, User = u })
|
||||
.Where(s => s.User.ActivationStatus == status.Value)
|
||||
.Select(r => r.Security);
|
||||
}
|
||||
|
||||
return await q.CountAsync();
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<FileShareRecord> GetPureSharesAsync(FileEntry<T> entry, ShareFilterType filterType, EmployeeActivationStatus? status, int offset = 0, int count = -1)
|
||||
{
|
||||
if (entry == null || count == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
await using var filesDbContext = _dbContextFactory.CreateDbContext();
|
||||
|
||||
var q = await GetPureSharesQuery(entry, filterType, filesDbContext);
|
||||
|
||||
if (filterType == ShareFilterType.User)
|
||||
{
|
||||
var predicate = ShareCompareHelper.GetCompareExpression<SecurityUserRecord>(s => s.Security.Share);
|
||||
|
||||
var q1 = q.Join(filesDbContext.Users, s => s.Subject, u => u.Id,
|
||||
(s, u) => new SecurityUserRecord { Security = s, User = u });
|
||||
|
||||
if (status.HasValue)
|
||||
{
|
||||
q = q1.Where(s => s.User.ActivationStatus == status.Value)
|
||||
.OrderBy(predicate)
|
||||
.Select(s => s.Security);
|
||||
}
|
||||
else
|
||||
{
|
||||
q = q1.OrderBy(s => s.User.ActivationStatus)
|
||||
.ThenBy(predicate)
|
||||
.Select(s => s.Security);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var predicate = ShareCompareHelper.GetCompareExpression<DbFilesSecurity>(s => s.Share);
|
||||
q = q.OrderBy(predicate);
|
||||
}
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
q = q.Skip(offset);
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
q = q.Take(count);
|
||||
}
|
||||
|
||||
await foreach (var r in q.ToAsyncEnumerable())
|
||||
{
|
||||
yield return await ToFileShareRecordAsync(r);
|
||||
}
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<UserInfoWithShared> GetUsersWithSharedAsync(FileEntry<T> entry, string text, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus,
|
||||
bool excludeShared, int offset, int count)
|
||||
{
|
||||
if (entry == null || count == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
await using var filesDbContext = _dbContextFactory.CreateDbContext();
|
||||
var tenantId = TenantID;
|
||||
var mappedId = (await MappingIDAsync(entry.Id)).ToString();
|
||||
|
||||
var q1 = GetUsersWithSharedQuery(tenantId, mappedId, entry, text, employeeStatus, activationStatus, excludeShared, filesDbContext);
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
q1 = q1.Skip(offset);
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
q1 = q1.Take(count);
|
||||
}
|
||||
|
||||
await foreach (var r in q1.ToAsyncEnumerable())
|
||||
{
|
||||
yield return new UserInfoWithShared { UserInfo = _mapper.Map<User, UserInfo>(r.User), Shared = r.Shared };
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> GetUsersWithSharedCountAsync(FileEntry<T> entry, string text, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus,
|
||||
bool excludeShared)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
await using var filesDbContext = _dbContextFactory.CreateDbContext();
|
||||
var tenantId = TenantID;
|
||||
var mappedId = (await MappingIDAsync(entry.Id)).ToString();
|
||||
|
||||
var q1 = GetUsersWithSharedQuery(tenantId, mappedId, entry, text, employeeStatus, activationStatus, excludeShared, filesDbContext);
|
||||
|
||||
return await q1.CountAsync();
|
||||
}
|
||||
|
||||
private static IQueryable<UserWithShared> GetUsersWithSharedQuery(int tenantId, string entryId, FileEntry entry, string text, EmployeeStatus? employeeStatus,
|
||||
EmployeeActivationStatus? activationStatus, bool excludeShared, FilesDbContext filesDbContext)
|
||||
{
|
||||
var q = filesDbContext.Users.AsNoTracking().Where(u => u.TenantId == tenantId);
|
||||
|
||||
if (employeeStatus.HasValue)
|
||||
{
|
||||
q = q.Where(u => u.Status == employeeStatus.Value);
|
||||
}
|
||||
|
||||
if (activationStatus.HasValue)
|
||||
{
|
||||
q = q.Where(u => u.ActivationStatus == activationStatus.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
q = q.Where(u => u.FirstName.Contains(text) || u.LastName.Contains(text) || u.Email.Contains(text));
|
||||
}
|
||||
|
||||
var q1 = excludeShared
|
||||
? q.Where(u => !filesDbContext.Security.Any(s => s.TenantId == tenantId && s.EntryType == entry.FileEntryType && s.EntryId == entryId && s.Subject == u.Id) &&
|
||||
u.Id != entry.CreateBy)
|
||||
.OrderBy(u => u.ActivationStatus)
|
||||
.ThenBy(u => u.FirstName)
|
||||
.Select(u => new UserWithShared { User = u, Shared = false })
|
||||
: from user in q
|
||||
join security in filesDbContext.Security.Where(s => s.TenantId == tenantId && s.EntryId == entryId && s.EntryType == entry.FileEntryType) on user.Id equals
|
||||
security.Subject into grouping
|
||||
from s in grouping.DefaultIfEmpty()
|
||||
orderby user.ActivationStatus, user.FirstName
|
||||
select new UserWithShared { User = user, Shared = s != null || user.Id == entry.CreateBy };
|
||||
|
||||
return q1;
|
||||
}
|
||||
|
||||
internal async IAsyncEnumerable<FileShareRecord> InternalGetPureShareRecordsAsync(FileEntry<T> entry)
|
||||
{
|
||||
var files = new List<string>();
|
||||
@ -234,6 +394,23 @@ internal abstract class SecurityBaseDao<T> : AbstractDao
|
||||
|
||||
await filesDbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<FileShareRecord> GetPureSharesAsync(FileEntry<T> entry, IEnumerable<Guid> subjects)
|
||||
{
|
||||
if (subjects == null || !subjects.Any())
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var entryId = await MappingIDAsync(entry.Id);
|
||||
|
||||
await using var filesDbContext = _dbContextFactory.CreateDbContext();
|
||||
|
||||
await foreach (var security in Queries.EntrySharesBySubjectsAsync(filesDbContext, TenantID, entryId.ToString(), entry.FileEntryType, subjects))
|
||||
{
|
||||
yield return await ToFileShareRecordAsync(security);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task SelectFilesAndFoldersForShareAsync(FileEntry<T> entry, ICollection<string> files, ICollection<string> folders, ICollection<int> foldersInt)
|
||||
{
|
||||
@ -294,6 +471,32 @@ internal abstract class SecurityBaseDao<T> : AbstractDao
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<IQueryable<DbFilesSecurity>> GetPureSharesQuery(FileEntry<T> entry, ShareFilterType filterType, FilesDbContext filesDbContext)
|
||||
{
|
||||
var entryId = await MappingIDAsync(entry.Id);
|
||||
|
||||
var q = filesDbContext.Security.AsNoTracking()
|
||||
.Where(s => s.TenantId == TenantID && s.EntryId == entryId.ToString() && s.EntryType == entry.FileEntryType);
|
||||
|
||||
switch (filterType)
|
||||
{
|
||||
case ShareFilterType.User:
|
||||
q = q.Where(s => s.SubjectType == SubjectType.User);
|
||||
break;
|
||||
case ShareFilterType.InvitationLink:
|
||||
q = q.Where(s => s.SubjectType == SubjectType.InvitationLink);
|
||||
break;
|
||||
case ShareFilterType.ExternalLink:
|
||||
q = q.Where(s => s.SubjectType == SubjectType.ExternalLink);
|
||||
break;
|
||||
case ShareFilterType.Link:
|
||||
q = q.Where(s => s.SubjectType == SubjectType.InvitationLink || s.SubjectType == SubjectType.ExternalLink);
|
||||
break;
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
}
|
||||
|
||||
[Scope]
|
||||
@ -547,6 +750,24 @@ internal class SecurityTreeRecord
|
||||
public int Level { get; set; }
|
||||
}
|
||||
|
||||
public class SecurityUserRecord
|
||||
{
|
||||
public DbFilesSecurity Security { get; init; }
|
||||
public User User { get; init; }
|
||||
}
|
||||
|
||||
public class UserInfoWithShared
|
||||
{
|
||||
public UserInfo UserInfo { get; set; }
|
||||
public bool Shared { get; set; }
|
||||
}
|
||||
|
||||
public class UserWithShared
|
||||
{
|
||||
public User User { get; set; }
|
||||
public bool Shared { get; set; }
|
||||
}
|
||||
|
||||
static file class Queries
|
||||
{
|
||||
public static readonly Func<FilesDbContext, FileShareRecord, IAsyncEnumerable<DbFilesSecurity>>
|
||||
@ -616,4 +837,10 @@ static file class Queries
|
||||
.Where(r => r.TenantId == tenantId
|
||||
&& (r.Subject == subject || r.Owner == subject))
|
||||
.ExecuteDelete());
|
||||
|
||||
public static readonly Func<FilesDbContext, int, string, FileEntryType, IEnumerable<Guid>, IAsyncEnumerable<DbFilesSecurity>> EntrySharesBySubjectsAsync =
|
||||
Microsoft.EntityFrameworkCore.EF.CompileAsyncQuery(
|
||||
(FilesDbContext ctx, int tenantId, string entryId, FileEntryType entryType, IEnumerable<Guid> subjects) =>
|
||||
ctx.Security
|
||||
.Where(r => r.TenantId == tenantId && r.EntryId == entryId && r.EntryType == entryType && subjects.Contains(r.Subject)));
|
||||
}
|
@ -24,6 +24,8 @@
|
||||
// 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
|
||||
|
||||
using User = ASC.Core.Common.EF.User;
|
||||
|
||||
namespace ASC.Files.Core.EF;
|
||||
|
||||
public class FilesDbContext : DbContext
|
||||
@ -42,6 +44,7 @@ public class FilesDbContext : DbContext
|
||||
public DbSet<DbFilesProperties> FilesProperties { get; set; }
|
||||
public DbSet<DbTenant> Tenants { get; set; }
|
||||
public DbSet<FilesConverts> FilesConverts { get; set; }
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
public FilesDbContext(DbContextOptions<FilesDbContext> dbContextOptions) : base(dbContextOptions) { }
|
||||
|
||||
@ -63,6 +66,7 @@ public class FilesDbContext : DbContext
|
||||
.AddDbFilesProperties()
|
||||
.AddDbTenant()
|
||||
.AddFilesConverts()
|
||||
.AddUser()
|
||||
.AddDbFunctions();
|
||||
}
|
||||
}
|
@ -533,7 +533,7 @@ public class FileStorageService //: IFileStorageService
|
||||
var room = await InternalCreateNewFolderAsync(parentId, title, FolderType.PublicRoom, @private);
|
||||
|
||||
_ = await SetAceLinkAsync(room, SubjectType.ExternalLink, Guid.NewGuid(), FilesCommonResource.DefaultExternalLinkTitle, FileShare.Read, _actions[SubjectType.ExternalLink]);
|
||||
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
@ -2513,17 +2513,47 @@ public class FileStorageService //: IFileStorageService
|
||||
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(
|
||||
IEnumerable<T> fileIds,
|
||||
IEnumerable<T> folderIds,
|
||||
IEnumerable<SubjectType> subjectTypes = null,
|
||||
bool withoutTemplates = false)
|
||||
IEnumerable<SubjectType> subjectTypes = null)
|
||||
{
|
||||
return await _fileSharing.GetSharedInfoAsync(fileIds, folderIds, subjectTypes, withoutTemplates);
|
||||
return await _fileSharing.GetSharedInfoAsync(fileIds, folderIds, subjectTypes);
|
||||
}
|
||||
|
||||
public async Task<List<AceShortWrapper>> GetSharedInfoShortFileAsync<T>(T fileId)
|
||||
{
|
||||
return await _fileSharing.GetSharedInfoShortFileAsync(fileId);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<AceWrapper> GetRoomSharedInfoAsync<T>(T roomId, ShareFilterType filterType, int offset, int count)
|
||||
{
|
||||
var room = await GetFolderDao<T>().GetFolderAsync(roomId).NotFoundIfNull();
|
||||
|
||||
await foreach (var ace in _fileSharing.GetRoomSharesAsync(room, filterType, null, offset, count))
|
||||
{
|
||||
yield return ace;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> GetRoomSharesCountAsync<T>(T roomId, ShareFilterType filterType)
|
||||
{
|
||||
var room = await GetFolderDao<T>().GetFolderAsync(roomId).NotFoundIfNull();
|
||||
|
||||
return await _fileSharing.GetRoomSharesCountAsync(room, filterType);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<AceWrapper> GetSharedInfoAsync<T>(T roomId, IEnumerable<Guid> subjects)
|
||||
{
|
||||
var room = await GetFolderDao<T>().GetFolderAsync(roomId).NotFoundIfNull();
|
||||
|
||||
await foreach (var ace in _fileSharing.GetPureSharesAsync(room, subjects))
|
||||
{
|
||||
yield return ace;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> SetAceObjectAsync<T>(AceCollection<T> aceCollection, bool notify)
|
||||
{
|
||||
var fileDao = GetFileDao<T>();
|
||||
var folderDao = GetFolderDao<T>();
|
||||
var securityDao = GetSecurityDao<T>();
|
||||
|
||||
var entries = new List<FileEntry<T>>();
|
||||
string warning = null;
|
||||
@ -2542,77 +2572,49 @@ public class FileStorageService //: IFileStorageService
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _fileSharingAceHelper.SetAceObjectAsync(aceCollection.Aces, entry, notify, aceCollection.Message, aceCollection.AdvancedSettings);
|
||||
warning ??= result.Warning;
|
||||
|
||||
var eventTypes = new List<(UserInfo User, EventType EventType, FileShare Access, string Email)>();
|
||||
foreach (var ace in aceCollection.Aces)
|
||||
if (!result.Changed)
|
||||
{
|
||||
var user = _userManager.GetUsers(ace.Id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (user == Constants.LostUser)
|
||||
foreach (var (eventType, ace) in result.HandledAces)
|
||||
{
|
||||
if (ace.IsLink)
|
||||
{
|
||||
eventTypes.Add((null, EventType.Create, ace.Access, ace.Email));
|
||||
continue;
|
||||
}
|
||||
|
||||
var userId = user.Id;
|
||||
var user = !string.IsNullOrEmpty(ace.Email)
|
||||
? await _userManager.GetUserByEmailAsync(ace.Email)
|
||||
: await _userManager.GetUsersAsync(ace.Id);
|
||||
|
||||
var name = user.DisplayUserName(false, _displayUserSettingsHelper);
|
||||
|
||||
var userSubjects = await _fileSecurity.GetUserSubjectsAsync(user.Id);
|
||||
var usersRecords = await securityDao.GetSharesAsync(userSubjects).ToListAsync();
|
||||
var recordEntrys = usersRecords.Select(r => r.EntryId.ToString());
|
||||
|
||||
EventType eventType;
|
||||
|
||||
if (usersRecords.Any() && ace.Access != FileShare.None && recordEntrys.Contains(entry.Id.ToString()))
|
||||
if (entry is Folder<T> folder && DocSpaceHelper.IsRoom(folder.FolderType))
|
||||
{
|
||||
eventType = EventType.Update;
|
||||
}
|
||||
else if (!usersRecords.Any() || !recordEntrys.Contains(entry.Id.ToString()))
|
||||
{
|
||||
eventType = EventType.Create;
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType.Create:
|
||||
_ = _filesMessageService.SendAsync(MessageAction.RoomCreateUser, entry, user.Id, name, GetAccessString(ace.Access));
|
||||
break;
|
||||
case EventType.Remove:
|
||||
_ = _filesMessageService.SendAsync(MessageAction.RoomRemoveUser, entry, user.Id, name, GetAccessString(ace.Access));
|
||||
break;
|
||||
case EventType.Update:
|
||||
_ = _filesMessageService.SendAsync(MessageAction.RoomUpdateAccessForUser, entry, user.Id, ace.Access, name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
eventType = EventType.Remove;
|
||||
}
|
||||
|
||||
eventTypes.Add((user, eventType, ace.Access, null));
|
||||
}
|
||||
|
||||
var (changed, warningMessage) = await _fileSharingAceHelper.SetAceObjectAsync(aceCollection.Aces, entry, notify, aceCollection.Message, aceCollection.AdvancedSettings);
|
||||
warning ??= warningMessage;
|
||||
|
||||
if (changed)
|
||||
{
|
||||
foreach (var e in eventTypes)
|
||||
{
|
||||
var user = e.User ?? await _userManager.GetUserByEmailAsync(e.Email);
|
||||
var name = user.DisplayUserName(false, _displayUserSettingsHelper);
|
||||
|
||||
var access = e.Access;
|
||||
|
||||
if (entry.FileEntryType == FileEntryType.Folder && DocSpaceHelper.IsRoom(((Folder<T>)entry).FolderType))
|
||||
{
|
||||
switch (e.EventType)
|
||||
{
|
||||
case EventType.Create:
|
||||
_ = _filesMessageService.SendAsync(MessageAction.RoomCreateUser, entry, user.Id, name, GetAccessString(access));
|
||||
break;
|
||||
case EventType.Remove:
|
||||
_ = _filesMessageService.SendAsync(MessageAction.RoomRemoveUser, entry, user.Id, name, GetAccessString(access));
|
||||
break;
|
||||
case EventType.Update:
|
||||
_ = _filesMessageService.SendAsync(MessageAction.RoomUpdateAccessForUser, entry, user.Id, access, name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
_ = _filesMessageService.SendAsync(
|
||||
entry.FileEntryType == FileEntryType.Folder ? MessageAction.FolderUpdatedAccessFor : MessageAction.FileUpdatedAccessFor,
|
||||
entry,
|
||||
entry.Title, name, GetAccessString(access));
|
||||
}
|
||||
_ = _filesMessageService.SendAsync(
|
||||
entry.FileEntryType == FileEntryType.Folder ? MessageAction.FolderUpdatedAccessFor : MessageAction.FileUpdatedAccessFor,
|
||||
entry,
|
||||
entry.Title, name, GetAccessString(ace.Access));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2666,7 +2668,7 @@ public class FileStorageService //: IFileStorageService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<AceWrapper>> SetInvitationLinkAsync<T>(T roomId, Guid linkId, string title, FileShare share)
|
||||
public async Task<AceWrapper> SetInvitationLinkAsync<T>(T roomId, Guid linkId, string title, FileShare share)
|
||||
{
|
||||
var expirationDate = DateTime.UtcNow.Add(_invitationLinkHelper.IndividualLinkExpirationInterval);
|
||||
|
||||
@ -2675,13 +2677,13 @@ public class FileStorageService //: IFileStorageService
|
||||
return await SetAceLinkAsync(room, SubjectType.InvitationLink, linkId, title, share, _actions[SubjectType.InvitationLink], expirationDate);
|
||||
}
|
||||
|
||||
public async Task<List<AceWrapper>> SetExternalLinkAsync<T>(T entryId, FileEntryType entryType, Guid linkId, string title, FileShare share, DateTime expirationDate = default,
|
||||
public async Task<AceWrapper> SetExternalLinkAsync<T>(T entryId, FileEntryType entryType, Guid linkId, string title, FileShare share, DateTime expirationDate = default,
|
||||
string password = null, bool disabled = false, bool denyDownload = false)
|
||||
{
|
||||
FileEntry<T> entry = entryType == FileEntryType.File ? (await GetFileDao<T>().GetFileAsync(entryId)).NotFoundIfNull()
|
||||
: (await GetFolderDao<T>().GetFolderAsync(entryId)).NotFoundIfNull();
|
||||
|
||||
return await SetAceLinkAsync(entry, SubjectType.ExternalLink, linkId, title, share, _actions[SubjectType.ExternalLink], expirationDate, password, disabled, denyDownload, 10);
|
||||
return await SetAceLinkAsync(entry, SubjectType.ExternalLink, linkId, title, share, _actions[SubjectType.ExternalLink], expirationDate, password, disabled, denyDownload);
|
||||
}
|
||||
|
||||
public async Task<bool> SetAceLinkAsync<T>(T fileId, FileShare share)
|
||||
@ -2701,8 +2703,8 @@ public class FileStorageService //: IFileStorageService
|
||||
|
||||
try
|
||||
{
|
||||
var (changed, _) = await _fileSharingAceHelper.SetAceObjectAsync(aces, file, false, null, null);
|
||||
if (changed)
|
||||
var result = await _fileSharingAceHelper.SetAceObjectAsync(aces, file, false, null, null);
|
||||
if (result.Changed)
|
||||
{
|
||||
_ = _filesMessageService.SendAsync(MessageAction.FileExternalLinkAccessUpdated, file, file.Title, GetAccessString(share));
|
||||
}
|
||||
@ -3119,10 +3121,13 @@ public class FileStorageService //: IFileStorageService
|
||||
|
||||
public async Task<bool> KeepNewFileNameAsync(bool set)
|
||||
{
|
||||
_filesSettingsHelper.KeepNewFileName = set;
|
||||
await _messageService.SendHeadersMessageAsync(MessageAction.DocumentsKeepNewFileNameSettingsUpdated);
|
||||
|
||||
return _filesSettingsHelper.KeepNewFileName;
|
||||
var current = _filesSettingsHelper.KeepNewFileName;
|
||||
if (current != set)
|
||||
{
|
||||
_filesSettingsHelper.KeepNewFileName = set;
|
||||
await _messageService.SendHeadersMessageAsync(MessageAction.DocumentsKeepNewFileNameSettingsUpdated);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public bool HideConfirmConvert(bool isForSave)
|
||||
@ -3262,36 +3267,62 @@ public class FileStorageService //: IFileStorageService
|
||||
return fileIds;
|
||||
}
|
||||
|
||||
public async Task ResendEmailInvitationsAsync<T>(T id, IEnumerable<Guid> usersIds)
|
||||
public async Task ResendEmailInvitationsAsync<T>(T id, IEnumerable<Guid> usersIds, bool resendAll)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(usersIds);
|
||||
if (!resendAll && (usersIds == null || !usersIds.Any()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var folderDao = _daoFactory.GetFolderDao<T>();
|
||||
var room = await folderDao.GetFolderAsync(id);
|
||||
var room = await folderDao.GetFolderAsync(id).NotFoundIfNull();
|
||||
|
||||
ErrorIf(room == null, FilesCommonResource.ErrorMassage_FolderNotFound);
|
||||
ErrorIf(!await _fileSecurity.CanEditRoomAsync(room), FilesCommonResource.ErrorMassage_SecurityException);
|
||||
|
||||
var shares = (await _fileSharing.GetSharedInfoAsync(room)).ToDictionary(k => k.Id, v => v);
|
||||
|
||||
foreach (var userId in usersIds)
|
||||
if (!resendAll)
|
||||
{
|
||||
if (!shares.TryGetValue(userId, out var share))
|
||||
await foreach (var ace in _fileSharing.GetPureSharesAsync(room, usersIds))
|
||||
{
|
||||
continue;
|
||||
var user = await _userManager.GetUsersAsync(ace.Id);
|
||||
|
||||
var link = await _invitationLinkService.GetInvitationLinkAsync(user.Email, ace.Access, _authContext.CurrentAccount.ID, room.Id.ToString());
|
||||
await _studioNotifyService.SendEmailRoomInviteAsync(user.Email, room.Title, link);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const int margin = 1;
|
||||
const int packSize = 1000;
|
||||
var offset = 0;
|
||||
var finish = false;
|
||||
|
||||
while (!finish)
|
||||
{
|
||||
var counter = 0;
|
||||
|
||||
await foreach (var ace in _fileSharing.GetRoomSharesAsync(room, ShareFilterType.User, EmployeeActivationStatus.Pending, offset, packSize + margin))
|
||||
{
|
||||
counter++;
|
||||
|
||||
if (counter > packSize)
|
||||
{
|
||||
offset += packSize;
|
||||
break;
|
||||
}
|
||||
|
||||
var user = await _userManager.GetUsersAsync(ace.Id);
|
||||
|
||||
var link = await _invitationLinkService.GetInvitationLinkAsync(user.Email, ace.Access, _authContext.CurrentAccount.ID, id.ToString());
|
||||
var shortenLink = await _urlShortener.GetShortenLinkAsync(link);
|
||||
|
||||
await _studioNotifyService.SendEmailRoomInviteAsync(user.Email, room.Title, shortenLink);
|
||||
}
|
||||
|
||||
var user = await _userManager.GetUserAsync(share.Id, null);
|
||||
|
||||
if (user.ActivationStatus != EmployeeActivationStatus.Pending)
|
||||
if (counter <= packSize)
|
||||
{
|
||||
continue;
|
||||
finish = true;
|
||||
}
|
||||
|
||||
var link = await _invitationLinkService.GetInvitationLinkAsync(user.Email, share.Access, _authContext.CurrentAccount.ID, id.ToString());
|
||||
var shortenLink = await _urlShortener.GetShortenLinkAsync(link);
|
||||
|
||||
await _studioNotifyService.SendEmailRoomInviteAsync(user.Email, room.Title, shortenLink);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3435,9 +3466,8 @@ public class FileStorageService //: IFileStorageService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<AceWrapper>> SetAceLinkAsync<T>(FileEntry<T> entry, SubjectType subjectType, Guid linkId, string title, FileShare share,
|
||||
IReadOnlyDictionary<EventType, MessageAction> messageActions, DateTime expirationDate = default, string password = null, bool disabled = false, bool denyDownload = false,
|
||||
int maxLinksCount = int.MaxValue)
|
||||
private async Task<AceWrapper> SetAceLinkAsync<T>(FileEntry<T> entry, SubjectType subjectType, Guid linkId, string title, FileShare share,
|
||||
IReadOnlyDictionary<EventType, MessageAction> messageActions, DateTime expirationDate = default, string password = null, bool disabled = false, bool denyDownload = false)
|
||||
{
|
||||
ArgumentNullOrEmptyException.ThrowIfNullOrEmpty(title);
|
||||
|
||||
@ -3446,25 +3476,6 @@ public class FileStorageService //: IFileStorageService
|
||||
linkId = Guid.NewGuid();
|
||||
}
|
||||
|
||||
var action = EventType.Create;
|
||||
|
||||
var links = (await _fileSecurity.GetSharesAsync(entry))
|
||||
.Where(r => r.SubjectType == subjectType).ToList();
|
||||
|
||||
if (share == FileShare.None)
|
||||
{
|
||||
action = EventType.Remove;
|
||||
}
|
||||
else if (links.Any(r => r.Subject == linkId))
|
||||
{
|
||||
action = EventType.Update;
|
||||
}
|
||||
|
||||
if (action == EventType.Create && links.Count == maxLinksCount)
|
||||
{
|
||||
throw GenerateException(new InvalidOperationException(FilesCommonResource.ErrorMessage_MaxLinksCount));
|
||||
}
|
||||
|
||||
var options = new FileShareOptions
|
||||
{
|
||||
Title = title,
|
||||
@ -3497,11 +3508,16 @@ public class FileStorageService //: IFileStorageService
|
||||
|
||||
try
|
||||
{
|
||||
var (changed, _) = await _fileSharingAceHelper.SetAceObjectAsync(aces, entry, false, null, null);
|
||||
var result = await _fileSharingAceHelper.SetAceObjectAsync(aces, entry, false, null, null);
|
||||
|
||||
if (changed)
|
||||
if (!string.IsNullOrEmpty(result.Warning))
|
||||
{
|
||||
_ = _filesMessageService.SendAsync(messageActions[action], entry, entry.Title, GetAccessString(share));
|
||||
throw GenerateException(new InvalidOperationException(result.Warning));
|
||||
}
|
||||
|
||||
if (result.Changed)
|
||||
{
|
||||
_ = _filesMessageService.SendAsync(messageActions[result.HandledAces[0].Event], entry, entry.Title, GetAccessString(share));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -3509,9 +3525,7 @@ public class FileStorageService //: IFileStorageService
|
||||
throw GenerateException(e);
|
||||
}
|
||||
|
||||
return entry.FileEntryType == FileEntryType.File ?
|
||||
await GetSharedInfoAsync(new[] { entry.Id }, Array.Empty<T>()) :
|
||||
await GetSharedInfoAsync(Array.Empty<T>(), new[] { entry.Id });
|
||||
return (await _fileSharing.GetPureSharesAsync(entry, new[] { linkId }).FirstOrDefaultAsync());
|
||||
}
|
||||
|
||||
private async Task<List<AceWrapper>> GetFullAceWrappersAsync(IEnumerable<FileShareParams> share)
|
||||
@ -3569,13 +3583,6 @@ public class FileStorageService //: IFileStorageService
|
||||
await SetAceObjectAsync(aceCollection, notify);
|
||||
}
|
||||
|
||||
private enum EventType
|
||||
{
|
||||
Update,
|
||||
Create,
|
||||
Remove
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyDictionary<SubjectType, IReadOnlyDictionary<EventType, MessageAction>> _actions =
|
||||
new Dictionary<SubjectType, IReadOnlyDictionary<EventType, MessageAction>>
|
||||
{
|
||||
|
@ -926,8 +926,9 @@ public class FileSecurity : IFileSecurity
|
||||
|
||||
FileShareRecord ace;
|
||||
|
||||
if (!isRoom && e.RootFolderType is FolderType.VirtualRooms or FolderType.Archive &&
|
||||
_cachedRecords.TryGetValue(GetCacheKey(e.ParentId, userId), out var value))
|
||||
if (!isRoom && e.RootFolderType is FolderType.VirtualRooms or FolderType.Archive &&
|
||||
(_cachedRecords.TryGetValue(GetCacheKey(e.ParentId, userId), out var value)) ||
|
||||
_cachedRecords.TryGetValue(GetCacheKey(e.ParentId, await _externalShare.GetLinkIdAsync()), out value))
|
||||
{
|
||||
ace = value.Clone();
|
||||
ace.EntryId = e.Id;
|
||||
@ -965,6 +966,14 @@ public class FileSecurity : IFileSecurity
|
||||
.ThenByDescending(r => r.Share, new FileShareRecord.ShareComparer())
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (!isRoom && e.RootFolderType is FolderType.VirtualRooms or FolderType.Archive &&
|
||||
ace is { SubjectType: SubjectType.User or SubjectType.ExternalLink })
|
||||
{
|
||||
var id = ace.SubjectType == SubjectType.ExternalLink ? ace.Subject : userId;
|
||||
|
||||
_cachedRecords.TryAdd(GetCacheKey(e.ParentId, id), ace);
|
||||
}
|
||||
}
|
||||
|
||||
if (ace is { SubjectType: SubjectType.ExternalLink } && ace.Subject != userId &&
|
||||
@ -984,11 +993,6 @@ public class FileSecurity : IFileSecurity
|
||||
e.Access = ace?.Share ?? defaultShare;
|
||||
e.Access = e.RootFolderType is FolderType.ThirdpartyBackup ? FileShare.Restrict : e.Access;
|
||||
|
||||
if (ace is { IsLink: false } && !isRoom && e.RootFolderType is FolderType.VirtualRooms or FolderType.Archive && e.Access != FileShare.None)
|
||||
{
|
||||
_cachedRecords.TryAdd(GetCacheKey(e.ParentId, userId), ace);
|
||||
}
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case FilesSecurityActions.Read:
|
||||
@ -996,7 +1000,8 @@ public class FileSecurity : IFileSecurity
|
||||
case FilesSecurityActions.Mute:
|
||||
return e.Access != FileShare.Restrict;
|
||||
case FilesSecurityActions.Comment:
|
||||
if (e.Access is FileShare.Comment or FileShare.Review ||
|
||||
if (e.Access is FileShare.Comment ||
|
||||
e.Access == FileShare.Review ||
|
||||
e.Access == FileShare.CustomFilter ||
|
||||
e.Access == FileShare.ReadWrite ||
|
||||
e.Access == FileShare.RoomAdmin ||
|
||||
@ -1149,7 +1154,7 @@ public class FileSecurity : IFileSecurity
|
||||
if (e.Access != FileShare.Restrict && ace?.Options is not { DenyDownload: true })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1193,6 +1198,33 @@ public class FileSecurity : IFileSecurity
|
||||
return await _daoFactory.GetSecurityDao<T>().GetSharesAsync(entry, subjects);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<FileShareRecord> GetPureSharesAsync<T>(FileEntry<T> entry, IEnumerable<Guid> subjects)
|
||||
{
|
||||
return _daoFactory.GetSecurityDao<T>().GetPureSharesAsync(entry, subjects);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<FileShareRecord> GetPureSharesAsync<T>(FileEntry<T> entry, ShareFilterType filterType, EmployeeActivationStatus? status, int offset = 0, int count = -1)
|
||||
{
|
||||
return _daoFactory.GetSecurityDao<T>().GetPureSharesAsync(entry, filterType, status, offset, count);
|
||||
}
|
||||
|
||||
public Task<int> GetPureSharesCountAsync<T>(FileEntry<T> entry, ShareFilterType filterType, EmployeeActivationStatus? status)
|
||||
{
|
||||
return _daoFactory.GetSecurityDao<T>().GetPureSharesCountAsync(entry, filterType, status);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<UserInfoWithShared> GetUsersWithSharedAsync<T>(FileEntry<T> entry, string text, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus,
|
||||
bool excludeShared, int offset, int count)
|
||||
{
|
||||
return _daoFactory.GetSecurityDao<T>().GetUsersWithSharedAsync(entry, text, employeeStatus, activationStatus, excludeShared, offset, count);
|
||||
}
|
||||
|
||||
public async Task<int> GetUsersWithSharedCountAsync<T>(FileEntry<T> entry, string text, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus,
|
||||
bool excludeShared)
|
||||
{
|
||||
return await _daoFactory.GetSecurityDao<T>().GetUsersWithSharedCountAsync(entry, text, employeeStatus, activationStatus, excludeShared);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<FileEntry> GetSharesForMeAsync(FilterType filterType, bool subjectGroup, Guid subjectID, string searchText = "", bool searchInContent = false, bool withSubfolders = false)
|
||||
{
|
||||
var securityDao = _daoFactory.GetSecurityDao<int>();
|
||||
|
@ -41,17 +41,23 @@ public class FileShareRecord
|
||||
|
||||
public class ShareComparer : IComparer<FileShare>
|
||||
{
|
||||
private static readonly int[] _shareOrder = new[]
|
||||
private static readonly int[] _shareOrder =
|
||||
{
|
||||
(int)FileShare.None,
|
||||
(int)FileShare.ReadWrite,
|
||||
(int)FileShare.CustomFilter,
|
||||
(int)FileShare.Review,
|
||||
(int)FileShare.FillForms,
|
||||
(int)FileShare.Comment,
|
||||
(int)FileShare.Read,
|
||||
(int)FileShare.Restrict,
|
||||
(int)FileShare.Varies
|
||||
(int)FileShare.None,
|
||||
(int)FileShare.RoomAdmin,
|
||||
(int)FileShare.Collaborator,
|
||||
(int)FileShare.Editing,
|
||||
(int)FileShare.FillForms,
|
||||
(int)FileShare.Review,
|
||||
(int)FileShare.Comment,
|
||||
(int)FileShare.Read,
|
||||
|
||||
// Not used
|
||||
|
||||
(int)FileShare.ReadWrite,
|
||||
(int)FileShare.CustomFilter,
|
||||
(int)FileShare.Varies,
|
||||
(int)FileShare.Restrict
|
||||
};
|
||||
|
||||
public int Compare(FileShare x, FileShare y)
|
||||
@ -70,3 +76,39 @@ public class SmallShareRecord
|
||||
public FileShare Share { get; set; }
|
||||
public SubjectType SubjectType { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public static class ShareCompareHelper
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, Expression> _predicates = new();
|
||||
|
||||
public static Expression<Func<TType, int>> GetCompareExpression<TType>(Expression<Func<TType, FileShare>> memberExpression)
|
||||
{
|
||||
var type = typeof(TType);
|
||||
var key = type.ToString();
|
||||
|
||||
if (_predicates.TryGetValue(key, out var value))
|
||||
{
|
||||
return (Expression<Func<TType, int>>)value;
|
||||
}
|
||||
|
||||
var shares = Enum.GetValues<FileShare>()
|
||||
.Order(new FileShareRecord.ShareComparer())
|
||||
.ToList();
|
||||
|
||||
ConditionalExpression expression = null;
|
||||
|
||||
for (var i = shares.Count - 1; i >= 0; i--)
|
||||
{
|
||||
expression = Expression.Condition(
|
||||
Expression.Equal(memberExpression.Body, Expression.Constant(shares[i])), Expression.Constant(i),
|
||||
expression != null ? expression : Expression.Constant(i + 1));
|
||||
}
|
||||
|
||||
var predicate = Expression.Lambda<Func<TType, int>>(expression!, memberExpression.Parameters[0]);
|
||||
|
||||
_predicates.TryAdd(key, predicate);
|
||||
|
||||
return predicate;
|
||||
}
|
||||
}
|
@ -38,4 +38,9 @@ public interface ISecurityDao<T>
|
||||
IAsyncEnumerable<FileShareRecord> GetPureShareRecordsAsync(FileEntry<T> entry);
|
||||
Task DeleteShareRecordsAsync(IEnumerable<FileShareRecord> records);
|
||||
Task<bool> IsSharedAsync(T entryId, FileEntryType type);
|
||||
IAsyncEnumerable<FileShareRecord> GetPureSharesAsync(FileEntry<T> entry, ShareFilterType filterType, EmployeeActivationStatus? status, int offset = 0, int count = -1);
|
||||
Task<int> GetPureSharesCountAsync(FileEntry<T> entry, ShareFilterType filterType, EmployeeActivationStatus? status);
|
||||
IAsyncEnumerable<FileShareRecord> GetPureSharesAsync(FileEntry<T> entry, IEnumerable<Guid> subjects);
|
||||
IAsyncEnumerable<UserInfoWithShared> GetUsersWithSharedAsync(FileEntry<T> entry, string text, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus, bool excludeShared, int offset = 0, int count = -1);
|
||||
Task<int> GetUsersWithSharedCountAsync(FileEntry<T> entry, string text, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus, bool excludeShared);
|
||||
}
|
||||
|
@ -26,6 +26,15 @@
|
||||
|
||||
namespace ASC.Files.Core.Security;
|
||||
|
||||
[Flags]
|
||||
public enum ShareFilterType
|
||||
{
|
||||
User = 0,
|
||||
InvitationLink = 1,
|
||||
ExternalLink = 2,
|
||||
Link = InvitationLink | ExternalLink
|
||||
}
|
||||
|
||||
public enum SubjectType
|
||||
{
|
||||
User = 0,
|
||||
|
@ -65,6 +65,6 @@ public class UsersInRoomStatistic : ITenantQuotaFeatureStat<UsersInRoomFeature,
|
||||
return 0;
|
||||
}
|
||||
|
||||
return await securityDao.GetPureShareRecordsAsync(folder).Where(r => !r.IsLink).CountAsync();
|
||||
return await securityDao.GetPureSharesCountAsync(folder, ShareFilterType.User, null);
|
||||
}
|
||||
}
|
@ -217,7 +217,7 @@ class FileDownloadOperation<T> : FileOperation<FileDownloadOperationData<T>, T>
|
||||
|
||||
PublishChanges();
|
||||
|
||||
var filesMessageService = _serviceProvider.GetRequiredService<FilesMessageService>();
|
||||
var filesMessageService = scope.ServiceProvider.GetRequiredService<FilesMessageService>();
|
||||
foreach (var file in filesForSend)
|
||||
{
|
||||
var key = file.Id;
|
||||
|
@ -42,7 +42,6 @@ public class AceWrapper : IMapFrom<RoomInvitation>
|
||||
public SubjectType SubjectType { get; set; }
|
||||
public FileShareOptions FileShareOptions { get; set; }
|
||||
public bool CanEditAccess { get; set; }
|
||||
public bool IsTemplate { get; set; }
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public string SubjectName { get; set; }
|
||||
|
@ -47,6 +47,9 @@ public class FileSharingAceHelper
|
||||
private readonly UserManagerWrapper _userManagerWrapper;
|
||||
private readonly CountPaidUserChecker _countPaidUserChecker;
|
||||
private readonly IUrlShortener _urlShortener;
|
||||
|
||||
private const int MaxInvitationLinks = 1;
|
||||
private const int MaxExternalLinks = 10;
|
||||
|
||||
public FileSharingAceHelper(
|
||||
FileSecurity fileSecurity,
|
||||
@ -86,23 +89,20 @@ public class FileSharingAceHelper
|
||||
_urlShortener = urlShortener;
|
||||
}
|
||||
|
||||
public async Task<(bool, string)> SetAceObjectAsync<T>(List<AceWrapper> aceWrappers, FileEntry<T> entry, bool notify, string message, AceAdvancedSettingsWrapper advancedSettings)
|
||||
public async Task<AceProcessingResult> SetAceObjectAsync<T>(List<AceWrapper> aceWrappers, FileEntry<T> entry, bool notify, string message, AceAdvancedSettingsWrapper advancedSettings)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
throw new ArgumentNullException(FilesCommonResource.ErrorMassage_BadRequest);
|
||||
}
|
||||
|
||||
if (!aceWrappers.All(r => r.Id == _authContext.CurrentAccount.ID && r.Access == FileShare.None) && !await _fileSharingHelper.CanSetAccessAsync(entry) && advancedSettings is not { InvitationLink: true })
|
||||
{
|
||||
throw new SecurityException(FilesCommonResource.ErrorMassage_SecurityException);
|
||||
}
|
||||
|
||||
if (entry is Folder<T> { Private: true } && advancedSettings is not { AllowSharingPrivateRoom: true })
|
||||
if (!aceWrappers.All(r => r.Id == _authContext.CurrentAccount.ID && r.Access == FileShare.None) &&
|
||||
!await _fileSharingHelper.CanSetAccessAsync(entry) && advancedSettings is not { InvitationLink: true })
|
||||
{
|
||||
throw new SecurityException(FilesCommonResource.ErrorMassage_SecurityException);
|
||||
}
|
||||
|
||||
var handledAces = new List<(EventType, AceWrapper)>(aceWrappers.Count);
|
||||
var ownerId = entry.RootFolderType == FolderType.USER ? entry.RootCreateBy : entry.CreateBy;
|
||||
var room = entry is Folder<T> folder && DocSpaceHelper.IsRoom(folder.FolderType) ? folder : null;
|
||||
var entryType = entry.FileEntryType;
|
||||
@ -110,16 +110,23 @@ public class FileSharingAceHelper
|
||||
var usersWithoutRight = new List<Guid>();
|
||||
var changed = false;
|
||||
string warning = null;
|
||||
var shares = (await _fileSecurity.GetSharesAsync(entry)).ToList();
|
||||
var shares = await _fileSecurity.GetPureSharesAsync(entry, aceWrappers.Select(a => a.Id))
|
||||
.ToDictionaryAsync(r => r.Subject);
|
||||
|
||||
foreach (var w in aceWrappers.OrderByDescending(ace => ace.SubjectGroup))
|
||||
{
|
||||
if (w.Id == _authContext.CurrentAccount.ID)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var emailInvite = !string.IsNullOrEmpty(w.Email);
|
||||
var currentUserType = await _userManager.GetUserTypeAsync(w.Id);
|
||||
var userType = EmployeeType.User;
|
||||
var existedShare = shares.FirstOrDefault(r => r.Subject == w.Id);
|
||||
var existedShare = shares.Get(w.Id);
|
||||
var rightIsAvailable = FileSecurity.AvailableUserRights.TryGetValue(currentUserType, out var userAccesses)
|
||||
&& userAccesses.Contains(w.Access);
|
||||
var eventType = existedShare != null ? w.Access == FileShare.None ? EventType.Remove : EventType.Update : EventType.Create;
|
||||
|
||||
if (room != null)
|
||||
{
|
||||
@ -127,15 +134,44 @@ public class FileSharingAceHelper
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (room.FolderType is not (FolderType.PublicRoom or FolderType.CustomRoom) && w.SubjectType == SubjectType.ExternalLink)
|
||||
|
||||
if (room.FolderType == FolderType.PublicRoom && w.Access == FileShare.Read && w.SubjectType != SubjectType.ExternalLink)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (room.FolderType == FolderType.PublicRoom && w.Access == FileShare.Read && w.SubjectType != SubjectType.ExternalLink)
|
||||
switch (w.SubjectType)
|
||||
{
|
||||
continue;
|
||||
case SubjectType.ExternalLink when room.FolderType is not (FolderType.PublicRoom or FolderType.CustomRoom):
|
||||
case SubjectType.ExternalLink when w.Access is not (FileShare.Read or FileShare.None):
|
||||
continue;
|
||||
case SubjectType.ExternalLink:
|
||||
{
|
||||
if (eventType == EventType.Create)
|
||||
{
|
||||
var linksCount = await _fileSecurity.GetPureSharesCountAsync(entry, ShareFilterType.ExternalLink, null);
|
||||
|
||||
if (linksCount >= MaxExternalLinks)
|
||||
{
|
||||
warning ??= string.Format(FilesCommonResource.ErrorMessage_MaxLinksCount, MaxExternalLinks);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case SubjectType.InvitationLink when eventType == EventType.Create:
|
||||
{
|
||||
var linksCount = await _fileSecurity.GetPureSharesCountAsync(entry, ShareFilterType.InvitationLink, null);
|
||||
|
||||
if (linksCount >= MaxInvitationLinks)
|
||||
{
|
||||
warning ??= string.Format(FilesCommonResource.ErrorMessage_MaxLinksCount, MaxInvitationLinks);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,6 +261,7 @@ public class FileSharingAceHelper
|
||||
|
||||
await _fileSecurity.ShareAsync(entry.Id, entryType, w.Id, share, w.SubjectType, w.FileShareOptions);
|
||||
changed = true;
|
||||
handledAces.Add((eventType, w));
|
||||
|
||||
if (emailInvite)
|
||||
{
|
||||
@ -271,9 +308,7 @@ public class FileSharingAceHelper
|
||||
|| (share == FileShare.None && entry.RootFolderType == FolderType.COMMON);
|
||||
|
||||
var removeNew = share == FileShare.Restrict || (share == FileShare.None
|
||||
&& (entry.RootFolderType == FolderType.USER ||
|
||||
entry.RootFolderType == FolderType.VirtualRooms ||
|
||||
entry.RootFolderType == FolderType.Archive));
|
||||
&& entry.RootFolderType is FolderType.USER or FolderType.VirtualRooms or FolderType.Archive);
|
||||
|
||||
listUsersId.ForEach(id =>
|
||||
{
|
||||
@ -303,8 +338,7 @@ public class FileSharingAceHelper
|
||||
await _fileMarker.MarkAsNewAsync(entry, recipients.Keys.ToList());
|
||||
}
|
||||
|
||||
if ((entry.RootFolderType == FolderType.USER
|
||||
|| entry.RootFolderType == FolderType.Privacy)
|
||||
if (entry.RootFolderType is FolderType.USER or FolderType.Privacy
|
||||
&& notify)
|
||||
{
|
||||
await _notifyClient.SendShareNoticeAsync(entry, recipients, message);
|
||||
@ -322,7 +356,7 @@ public class FileSharingAceHelper
|
||||
await _fileMarker.RemoveMarkAsNewAsync(entry, userId);
|
||||
}
|
||||
|
||||
return (changed, warning);
|
||||
return new AceProcessingResult(changed, warning, handledAces);
|
||||
}
|
||||
|
||||
public async Task RemoveAceAsync<T>(FileEntry<T> entry)
|
||||
@ -473,7 +507,72 @@ public class FileSharing
|
||||
return await _fileSharingHelper.CanSetAccessAsync(entry);
|
||||
}
|
||||
|
||||
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(FileEntry<T> entry, IEnumerable<SubjectType> subjectsTypes = null, bool withoutTemplates = false)
|
||||
public async IAsyncEnumerable<AceWrapper> GetPureSharesAsync<T>(FileEntry<T> entry, IEnumerable<Guid> subjects)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
throw new ArgumentNullException(FilesCommonResource.ErrorMassage_BadRequest);
|
||||
}
|
||||
|
||||
if (!await _fileSecurity.CanReadAsync(entry))
|
||||
{
|
||||
_logger.ErrorUserCanTGetSharedInfo(_authContext.CurrentAccount.ID, entry.FileEntryType, entry.Id.ToString()!);
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
var canEditAccess = await _fileSecurity.CanEditAccessAsync(entry);
|
||||
|
||||
await foreach (var record in _fileSecurity.GetPureSharesAsync(entry, subjects))
|
||||
{
|
||||
yield return await ToAceAsync(entry, record, canEditAccess);
|
||||
}
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<AceWrapper> GetRoomSharesAsync<T>(Folder<T> room, ShareFilterType filterType, EmployeeActivationStatus? status, int offset, int count)
|
||||
{
|
||||
if (room == null || !DocSpaceHelper.IsRoom(room.FolderType))
|
||||
{
|
||||
throw new ArgumentNullException(FilesCommonResource.ErrorMassage_BadRequest);
|
||||
}
|
||||
|
||||
if (!await _fileSecurity.CanReadAsync(room))
|
||||
{
|
||||
_logger.ErrorUserCanTGetSharedInfo(_authContext.CurrentAccount.ID, room.FileEntryType, room.Id.ToString()!);
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
var canEditAccess = await _fileSecurity.CanEditAccessAsync(room);
|
||||
|
||||
var allDefaultAces = await GetDefaultRoomAcesAsync(room, filterType, status).ToListAsync();
|
||||
var defaultAces = allDefaultAces.Skip(offset).Take(count).ToList();
|
||||
|
||||
offset = Math.Max(defaultAces.Count > 0 ? 0 : offset - allDefaultAces.Count, 0);
|
||||
count -= defaultAces.Count;
|
||||
|
||||
var records = _fileSecurity.GetPureSharesAsync(room, filterType, status, offset, count);
|
||||
|
||||
foreach (var record in defaultAces)
|
||||
{
|
||||
yield return record;
|
||||
}
|
||||
|
||||
await foreach (var record in records)
|
||||
{
|
||||
yield return await ToAceAsync(room, record, canEditAccess);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> GetRoomSharesCountAsync<T>(Folder<T> room, ShareFilterType filterType)
|
||||
{
|
||||
var defaultAces = await GetDefaultRoomAcesAsync(room, filterType, null).CountAsync();
|
||||
var sharesCount = await _fileSecurity.GetPureSharesCountAsync(room, filterType, null);
|
||||
|
||||
return defaultAces + sharesCount;
|
||||
}
|
||||
|
||||
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(FileEntry<T> entry, IEnumerable<SubjectType> subjectsTypes = null)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
@ -586,38 +685,6 @@ public class FileSharing
|
||||
result.Add(w);
|
||||
}
|
||||
|
||||
if (isRoom && canEditAccess && !withoutTemplates)
|
||||
{
|
||||
var invitationId = Guid.NewGuid();
|
||||
|
||||
var invitationAceTemplate = new AceWrapper
|
||||
{
|
||||
Id = invitationId,
|
||||
Link = _invitationLinkService.GetInvitationLink(invitationId, _authContext.CurrentAccount.ID),
|
||||
SubjectGroup = true,
|
||||
Access = ((Folder<T>)entry).FolderType == FolderType.PublicRoom ? FileShare.RoomAdmin : FileShare.Read,
|
||||
Owner = false,
|
||||
IsTemplate = true,
|
||||
SubjectType = SubjectType.InvitationLink
|
||||
};
|
||||
|
||||
var externalId = Guid.NewGuid();
|
||||
|
||||
var externalAceTemplate = new AceWrapper
|
||||
{
|
||||
Id = externalId,
|
||||
Link = await _externalShare.GetLinkAsync(externalId),
|
||||
SubjectGroup = true,
|
||||
Access = FileShare.Read,
|
||||
Owner = false,
|
||||
IsTemplate = true,
|
||||
SubjectType = SubjectType.ExternalLink
|
||||
};
|
||||
|
||||
result.Add(invitationAceTemplate);
|
||||
result.Add(externalAceTemplate);
|
||||
}
|
||||
|
||||
if (entry.FileEntryType == FileEntryType.File && result.All(w => w.Id != FileConstant.ShareLinkId)
|
||||
&& entry.FileEntryType == FileEntryType.File
|
||||
&& !((File<T>)entry).Encrypted)
|
||||
@ -696,8 +763,7 @@ public class FileSharing
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(IEnumerable<T> fileIds, IEnumerable<T> folderIds, IEnumerable<SubjectType> subjectTypes = null,
|
||||
bool withoutTemplates = false)
|
||||
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(IEnumerable<T> fileIds, IEnumerable<T> folderIds, IEnumerable<SubjectType> subjectTypes = null)
|
||||
{
|
||||
if (!_authContext.IsAuthenticated)
|
||||
{
|
||||
@ -719,7 +785,7 @@ public class FileSharing
|
||||
IEnumerable<AceWrapper> acesForObject;
|
||||
try
|
||||
{
|
||||
acesForObject = await GetSharedInfoAsync(entry, subjectTypes, withoutTemplates);
|
||||
acesForObject = await GetSharedInfoAsync(entry, subjectTypes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -821,4 +887,94 @@ public class FileSharing
|
||||
.Where(aceWrapper => !aceWrapper.Id.Equals(FileConstant.ShareLinkId) || aceWrapper.Access != FileShare.Restrict)
|
||||
.Select(aceWrapper => new AceShortWrapper(aceWrapper)));
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<AceWrapper> GetDefaultRoomAcesAsync<T>(Folder<T> room, ShareFilterType filterType, EmployeeActivationStatus? status)
|
||||
{
|
||||
if (filterType != ShareFilterType.User)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (status.HasValue)
|
||||
{
|
||||
var user = await _userManager.GetUsersAsync(room.CreateBy);
|
||||
|
||||
if (user.ActivationStatus != status.Value)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
var owner = new AceWrapper
|
||||
{
|
||||
Id = room.CreateBy,
|
||||
SubjectName = await _global.GetUserNameAsync(room.CreateBy),
|
||||
SubjectGroup = false,
|
||||
Access = FileShare.ReadWrite,
|
||||
Owner = true,
|
||||
CanEditAccess = false,
|
||||
};
|
||||
|
||||
yield return owner;
|
||||
}
|
||||
|
||||
private async Task<AceWrapper> ToAceAsync(FileEntry entry, FileShareRecord record, bool canEditAccess)
|
||||
{
|
||||
var w = new AceWrapper
|
||||
{
|
||||
Id = record.Subject,
|
||||
SubjectGroup = false,
|
||||
Access = record.Share,
|
||||
FileShareOptions = record.Options,
|
||||
SubjectType = record.SubjectType
|
||||
};
|
||||
|
||||
w.CanEditAccess = _authContext.CurrentAccount.ID != w.Id && (w.SubjectType is SubjectType.User or SubjectType.Group) && canEditAccess;
|
||||
|
||||
if (record.IsLink)
|
||||
{
|
||||
var link = record.SubjectType == SubjectType.InvitationLink ?
|
||||
_invitationLinkService.GetInvitationLink(record.Subject, _authContext.CurrentAccount.ID) :
|
||||
await _externalShare.GetLinkAsync(record.Subject);
|
||||
|
||||
w.Link = await _urlShortener.GetShortenLinkAsync(link);
|
||||
w.SubjectGroup = true;
|
||||
w.CanEditAccess = false;
|
||||
w.FileShareOptions.Password = await _externalShare.GetPasswordAsync(w.FileShareOptions.Password);
|
||||
w.SubjectType = record.SubjectType;
|
||||
}
|
||||
else
|
||||
{
|
||||
var user = await _userManager.GetUsersAsync(record.Subject);
|
||||
|
||||
w.SubjectName = user.DisplayUserName(false, _displayUserSettingsHelper);
|
||||
w.Owner = entry.RootFolderType == FolderType.USER
|
||||
? entry.RootCreateBy == record.Subject
|
||||
: entry.CreateBy == record.Subject;
|
||||
w.LockedRights = record.Subject == _authContext.CurrentAccount.ID;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
}
|
||||
|
||||
public class AceProcessingResult
|
||||
{
|
||||
public bool Changed { get; }
|
||||
public string Warning { get; }
|
||||
public IReadOnlyList<(EventType Event, AceWrapper Ace)> HandledAces { get; }
|
||||
|
||||
public AceProcessingResult(bool changed, string warning, IReadOnlyList<(EventType Event, AceWrapper Ace)> handledAces)
|
||||
{
|
||||
Changed = changed;
|
||||
Warning = warning;
|
||||
HandledAces = handledAces;
|
||||
}
|
||||
}
|
||||
|
||||
public enum EventType
|
||||
{
|
||||
Update,
|
||||
Create,
|
||||
Remove
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// (c) Copyright Ascensio System SIA 2010-2022
|
||||
// (c) Copyright Ascensio System SIA 2010-2022
|
||||
//
|
||||
// This program is a free software product.
|
||||
// You can redistribute it and/or modify it under the terms
|
||||
@ -40,7 +40,8 @@ public class VirtualRoomsInternalController : VirtualRoomsController<int>
|
||||
FileDtoHelper fileDtoHelper,
|
||||
FileShareDtoHelper fileShareDtoHelper,
|
||||
IMapper mapper,
|
||||
SocketManager socketManager) : base(
|
||||
SocketManager socketManager,
|
||||
ApiContext apiContext) : base(
|
||||
globalFolderHelper,
|
||||
fileOperationDtoHelper,
|
||||
coreBaseSettings,
|
||||
@ -51,7 +52,8 @@ public class VirtualRoomsInternalController : VirtualRoomsController<int>
|
||||
fileDtoHelper,
|
||||
fileShareDtoHelper,
|
||||
mapper,
|
||||
socketManager)
|
||||
socketManager,
|
||||
apiContext)
|
||||
{
|
||||
}
|
||||
|
||||
@ -88,7 +90,8 @@ public class VirtualRoomsThirdPartyController : VirtualRoomsController<string>
|
||||
FileDtoHelper fileDtoHelper,
|
||||
FileShareDtoHelper fileShareDtoHelper,
|
||||
IMapper mapper,
|
||||
SocketManager socketManager) : base(
|
||||
SocketManager socketManager,
|
||||
ApiContext apiContext) : base(
|
||||
globalFolderHelper,
|
||||
fileOperationDtoHelper,
|
||||
coreBaseSettings,
|
||||
@ -99,7 +102,8 @@ public class VirtualRoomsThirdPartyController : VirtualRoomsController<string>
|
||||
fileDtoHelper,
|
||||
fileShareDtoHelper,
|
||||
mapper,
|
||||
socketManager)
|
||||
socketManager,
|
||||
apiContext)
|
||||
{
|
||||
}
|
||||
|
||||
@ -135,6 +139,7 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
|
||||
private readonly FileShareDtoHelper _fileShareDtoHelper;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly SocketManager _socketManager;
|
||||
private readonly ApiContext _apiContext;
|
||||
|
||||
protected VirtualRoomsController(
|
||||
GlobalFolderHelper globalFolderHelper,
|
||||
@ -147,7 +152,8 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
|
||||
FileDtoHelper fileDtoHelper,
|
||||
FileShareDtoHelper fileShareDtoHelper,
|
||||
IMapper mapper,
|
||||
SocketManager socketManager) : base(folderDtoHelper, fileDtoHelper)
|
||||
SocketManager socketManager,
|
||||
ApiContext apiContext) : base(folderDtoHelper, fileDtoHelper)
|
||||
{
|
||||
_globalFolderHelper = globalFolderHelper;
|
||||
_fileOperationDtoHelper = fileOperationDtoHelper;
|
||||
@ -158,6 +164,7 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
|
||||
_fileShareDtoHelper = fileShareDtoHelper;
|
||||
_mapper = mapper;
|
||||
_socketManager = socketManager;
|
||||
_apiContext = apiContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -286,22 +293,25 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
|
||||
|
||||
var result = new RoomSecurityDto();
|
||||
|
||||
if (inDto.Invitations != null && inDto.Invitations.Any())
|
||||
if (inDto.Invitations == null || !inDto.Invitations.Any())
|
||||
{
|
||||
var wrappers = _mapper.Map<IEnumerable<RoomInvitation>, List<AceWrapper>>(inDto.Invitations);
|
||||
|
||||
var aceCollection = new AceCollection<T>
|
||||
{
|
||||
Files = Array.Empty<T>(),
|
||||
Folders = new[] { id },
|
||||
Aces = wrappers,
|
||||
Message = inDto.Message
|
||||
};
|
||||
|
||||
result.Warning = await _fileStorageService.SetAceObjectAsync(aceCollection, inDto.Notify);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.Members = await GetRoomSecurityInfoAsync(id).ToListAsync();
|
||||
var wrappers = _mapper.Map<IEnumerable<RoomInvitation>, List<AceWrapper>>(inDto.Invitations);
|
||||
|
||||
var aceCollection = new AceCollection<T>
|
||||
{
|
||||
Files = Array.Empty<T>(),
|
||||
Folders = new[] { id },
|
||||
Aces = wrappers,
|
||||
Message = inDto.Message
|
||||
};
|
||||
|
||||
result.Warning = await _fileStorageService.SetAceObjectAsync(aceCollection, inDto.Notify);
|
||||
result.Members = await _fileStorageService.GetSharedInfoAsync(id, inDto.Invitations.Select(s => s.Id))
|
||||
.SelectAwait(async a => await _fileShareDtoHelper.Get(a))
|
||||
.ToListAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -312,19 +322,30 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
|
||||
/// <short>Get room access rights</short>
|
||||
/// <category>Rooms</category>
|
||||
/// <param type="System.Int32, System" method="url" name="id">Room ID</param>
|
||||
/// <param name="filterType">
|
||||
/// Share type filter
|
||||
/// </param>
|
||||
/// <returns type="ASC.Files.Core.ApiModels.ResponseDto.FileShareDto, ASC.Files.Core">Security information of room files</returns>
|
||||
/// <path>api/2.0/files/rooms/{id}/share</path>
|
||||
/// <httpMethod>GET</httpMethod>
|
||||
/// <collection>list</collection>
|
||||
[HttpGet("rooms/{id}/share")]
|
||||
public async IAsyncEnumerable<FileShareDto> GetRoomSecurityInfoAsync(T id)
|
||||
public async IAsyncEnumerable<FileShareDto> GetRoomSecurityInfoAsync(T id, ShareFilterType filterType = ShareFilterType.User)
|
||||
{
|
||||
var fileShares = await _fileStorageService.GetSharedInfoAsync(Array.Empty<T>(), new[] { id });
|
||||
var offset = Convert.ToInt32(_apiContext.StartIndex);
|
||||
var count = Convert.ToInt32(_apiContext.Count);
|
||||
var counter = 0;
|
||||
|
||||
foreach (var fileShareDto in fileShares)
|
||||
var totalCountTask = _fileStorageService.GetRoomSharesCountAsync(id, filterType);
|
||||
|
||||
await foreach (var ace in _fileStorageService.GetRoomSharedInfoAsync(id, filterType, offset, count))
|
||||
{
|
||||
yield return await _fileShareDtoHelper.Get(fileShareDto);
|
||||
counter++;
|
||||
|
||||
yield return await _fileShareDtoHelper.Get(ace);
|
||||
}
|
||||
|
||||
_apiContext.SetCount(counter).SetTotalCount(await totalCountTask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -339,9 +360,9 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
|
||||
/// <httpMethod>PUT</httpMethod>
|
||||
/// <collection>list</collection>
|
||||
[HttpPut("rooms/{id}/links")]
|
||||
public async IAsyncEnumerable<FileShareDto> SetLinkAsync(T id, LinkRequestDto inDto)
|
||||
public async Task<FileShareDto> SetLinkAsync(T id, LinkRequestDto inDto)
|
||||
{
|
||||
var fileShares = inDto.LinkType switch
|
||||
var linkAce = inDto.LinkType switch
|
||||
{
|
||||
LinkType.Invitation => await _fileStorageService.SetInvitationLinkAsync(id, inDto.LinkId, inDto.Title, inDto.Access),
|
||||
LinkType.External => await _fileStorageService.SetExternalLinkAsync(id, FileEntryType.Folder, inDto.LinkId, inDto.Title,
|
||||
@ -349,10 +370,7 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
foreach (var fileShareDto in fileShares)
|
||||
{
|
||||
yield return await _fileShareDtoHelper.Get(fileShareDto);
|
||||
}
|
||||
return linkAce != null ? await _fileShareDtoHelper.Get(linkAce) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -369,21 +387,24 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
|
||||
[HttpGet("rooms/{id}/links")]
|
||||
public async IAsyncEnumerable<FileShareDto> GetLinksAsync(T id, LinkType? type)
|
||||
{
|
||||
var subjectTypes = type.HasValue
|
||||
? type.Value switch
|
||||
var filterType = type.HasValue ? type.Value switch
|
||||
{
|
||||
LinkType.Invitation => new[] { SubjectType.InvitationLink },
|
||||
LinkType.External => new[] { SubjectType.ExternalLink },
|
||||
_ => new[] { SubjectType.InvitationLink, SubjectType.ExternalLink }
|
||||
}
|
||||
: new[] { SubjectType.InvitationLink, SubjectType.ExternalLink };
|
||||
LinkType.Invitation => ShareFilterType.InvitationLink,
|
||||
LinkType.External => ShareFilterType.ExternalLink,
|
||||
_ => ShareFilterType.Link
|
||||
}
|
||||
: ShareFilterType.Link;
|
||||
|
||||
var fileShares = await _fileStorageService.GetSharedInfoAsync(Array.Empty<T>(), new[] { id }, subjectTypes, true);
|
||||
|
||||
foreach (var fileShareDto in fileShares)
|
||||
var counter = 0;
|
||||
|
||||
await foreach (var ace in _fileStorageService.GetRoomSharedInfoAsync(id, filterType, 0, 100))
|
||||
{
|
||||
yield return await _fileShareDtoHelper.Get(fileShareDto);
|
||||
counter++;
|
||||
|
||||
yield return await _fileShareDtoHelper.Get(ace);
|
||||
}
|
||||
|
||||
_apiContext.SetCount(counter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -518,9 +539,9 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
|
||||
/// <path>api/2.0/files/rooms/{id}/resend</path>
|
||||
/// <httpMethod>POST</httpMethod>
|
||||
[HttpPost("rooms/{id}/resend")]
|
||||
public async Task ResendEmailInvitationsAsync(T id, UserInvintationRequestDto inDto)
|
||||
public async Task ResendEmailInvitationsAsync(T id, UserInvitationRequestDto inDto)
|
||||
{
|
||||
await _fileStorageService.ResendEmailInvitationsAsync(id, inDto.UsersIds);
|
||||
await _fileStorageService.ResendEmailInvitationsAsync(id, inDto.UsersIds, inDto.ResendAll);
|
||||
}
|
||||
|
||||
protected void ErrorIfNotDocSpace()
|
||||
|
@ -1889,3 +1889,72 @@ public class UserController : PeopleControllerBase
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
[ConstraintRoute("int")]
|
||||
public class UserControllerAdditionalInternal : UserControllerAdditional<int>
|
||||
{
|
||||
public UserControllerAdditionalInternal(
|
||||
EmployeeFullDtoHelper employeeFullDtoHelper,
|
||||
FileSecurity fileSecurity,
|
||||
ApiContext apiContext,
|
||||
IDaoFactory daoFactory)
|
||||
: base(employeeFullDtoHelper, fileSecurity, apiContext, daoFactory)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class UserControllerAdditionalThirdParty : UserControllerAdditional<string>
|
||||
{
|
||||
public UserControllerAdditionalThirdParty(
|
||||
EmployeeFullDtoHelper employeeFullDtoHelper,
|
||||
FileSecurity fileSecurity,
|
||||
ApiContext apiContext,
|
||||
IDaoFactory daoFactory)
|
||||
: base(employeeFullDtoHelper, fileSecurity, apiContext, daoFactory)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class UserControllerAdditional<T> : ApiControllerBase
|
||||
{
|
||||
private readonly EmployeeFullDtoHelper _employeeFullDtoHelper;
|
||||
private readonly FileSecurity _fileSecurity;
|
||||
private readonly ApiContext _apiContext;
|
||||
private readonly IDaoFactory _daoFactory;
|
||||
|
||||
public UserControllerAdditional(
|
||||
EmployeeFullDtoHelper employeeFullDtoHelper,
|
||||
FileSecurity fileSecurity,
|
||||
ApiContext apiContext,
|
||||
IDaoFactory daoFactory)
|
||||
{
|
||||
_employeeFullDtoHelper = employeeFullDtoHelper;
|
||||
_fileSecurity = fileSecurity;
|
||||
_apiContext = apiContext;
|
||||
_daoFactory = daoFactory;
|
||||
}
|
||||
|
||||
[HttpGet("room/{id}")]
|
||||
public async IAsyncEnumerable<EmployeeFullDto> GetUsersWithRoomSharedAsync(T id, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus, bool? excludeShared)
|
||||
{
|
||||
var offset = Convert.ToInt32(_apiContext.StartIndex);
|
||||
var count = Convert.ToInt32(_apiContext.Count);
|
||||
|
||||
var room = (await _daoFactory.GetFolderDao<T>().GetFolderAsync(id)).NotFoundIfNull();
|
||||
var totalCountTask = _fileSecurity.GetUsersWithSharedCountAsync(room, _apiContext.FilterValue, employeeStatus, activationStatus, excludeShared ?? false);
|
||||
|
||||
var counter = 0;
|
||||
|
||||
await foreach (var u in _fileSecurity.GetUsersWithSharedAsync(room, _apiContext.FilterValue, employeeStatus, activationStatus, excludeShared ?? false, offset,
|
||||
count))
|
||||
{
|
||||
counter++;
|
||||
|
||||
yield return await _employeeFullDtoHelper.GetFullAsync(u.UserInfo, u.Shared);
|
||||
}
|
||||
|
||||
_apiContext.SetCount(counter).SetTotalCount(await totalCountTask);
|
||||
}
|
||||
}
|
@ -24,23 +24,24 @@
|
||||
// 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
|
||||
|
||||
global using System.Globalization;
|
||||
global using System.Globalization;
|
||||
global using System.Net.Mail;
|
||||
global using System.Security;
|
||||
global using System.Security.Claims;
|
||||
global using System.Security.Claims;
|
||||
global using System.ServiceModel.Security;
|
||||
global using System.Web;
|
||||
|
||||
|
||||
global using ASC.Api.Core;
|
||||
global using ASC.Api.Core.Convention;
|
||||
global using ASC.Api.Core.Extensions;
|
||||
global using ASC.Api.Core.Model;
|
||||
global using ASC.Api.Core.Security;
|
||||
global using ASC.Api.Core.Security;
|
||||
global using ASC.Api.Core.Routing;
|
||||
global using ASC.Api.Utils;
|
||||
global using ASC.Common;
|
||||
global using ASC.Common.Caching;
|
||||
global using ASC.Common.Caching;
|
||||
global using ASC.Common.Log;
|
||||
global using ASC.Common.Threading;
|
||||
global using ASC.Common.Threading;
|
||||
global using ASC.Common.Utils;
|
||||
global using ASC.Common.Web;
|
||||
global using ASC.Core;
|
||||
@ -54,19 +55,19 @@ global using ASC.Data.Reassigns;
|
||||
global using ASC.FederatedLogin;
|
||||
global using ASC.FederatedLogin.LoginProviders;
|
||||
global using ASC.FederatedLogin.Profile;
|
||||
global using ASC.Files.Core;
|
||||
global using ASC.Files.Core.Core;
|
||||
global using ASC.Files.Core.EF;
|
||||
global using ASC.Files.Core;
|
||||
global using ASC.Files.Core.Core;
|
||||
global using ASC.Files.Core.EF;
|
||||
global using ASC.Files.Core.Resources;
|
||||
global using ASC.Files.Core.Security;
|
||||
global using ASC.Files.Core.VirtualRooms;
|
||||
global using ASC.Files.Core.VirtualRooms;
|
||||
global using ASC.MessagingSystem.Core;
|
||||
global using ASC.MessagingSystem.EF.Model;
|
||||
global using ASC.People;
|
||||
global using ASC.People.Api;
|
||||
global using ASC.People.ApiModels.RequestDto;
|
||||
global using ASC.People.ApiModels.ResponseDto;
|
||||
global using ASC.People.Log;
|
||||
global using ASC.People.Log;
|
||||
global using ASC.People.Resources;
|
||||
global using ASC.Security.Cryptography;
|
||||
global using ASC.Web.Api.Models;
|
||||
@ -74,30 +75,30 @@ global using ASC.Web.Api.Routing;
|
||||
global using ASC.Web.Core;
|
||||
global using ASC.Web.Core.Mobile;
|
||||
global using ASC.Web.Core.PublicResources;
|
||||
global using ASC.Web.Core.Quota;
|
||||
global using ASC.Web.Core.Quota;
|
||||
global using ASC.Web.Core.Users;
|
||||
global using ASC.Web.Core.Utility;
|
||||
global using ASC.Web.Files;
|
||||
global using ASC.Web.Studio.Core;
|
||||
global using ASC.Web.Studio.Core.Notify;
|
||||
global using ASC.Web.Studio.Utility;
|
||||
|
||||
|
||||
global using Autofac;
|
||||
|
||||
|
||||
global using Microsoft.AspNetCore.Http.Extensions;
|
||||
global using Microsoft.AspNetCore.Mvc;
|
||||
global using Microsoft.AspNetCore.RateLimiting;
|
||||
global using Microsoft.EntityFrameworkCore;
|
||||
global using Microsoft.EntityFrameworkCore;
|
||||
global using Microsoft.Extensions.Hosting.WindowsServices;
|
||||
|
||||
|
||||
global using SixLabors.ImageSharp;
|
||||
global using SixLabors.ImageSharp.Formats;
|
||||
|
||||
global using SixLabors.ImageSharp.Formats;
|
||||
|
||||
global using Module = ASC.Api.Core.Module;
|
||||
global using SecurityContext = ASC.Core.SecurityContext;
|
||||
global using AllowAnonymousAttribute = Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute;
|
||||
global using AuthorizeAttribute = Microsoft.AspNetCore.Authorization.AuthorizeAttribute;
|
||||
global using HttpDeleteAttribute = Microsoft.AspNetCore.Mvc.HttpDeleteAttribute;
|
||||
global using HttpGetAttribute = Microsoft.AspNetCore.Mvc.HttpGetAttribute;
|
||||
global using HttpPostAttribute = Microsoft.AspNetCore.Mvc.HttpPostAttribute;
|
||||
global using SecurityContext = ASC.Core.SecurityContext;
|
||||
global using AllowAnonymousAttribute = Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute;
|
||||
global using AuthorizeAttribute = Microsoft.AspNetCore.Authorization.AuthorizeAttribute;
|
||||
global using HttpDeleteAttribute = Microsoft.AspNetCore.Mvc.HttpDeleteAttribute;
|
||||
global using HttpGetAttribute = Microsoft.AspNetCore.Mvc.HttpGetAttribute;
|
||||
global using HttpPostAttribute = Microsoft.AspNetCore.Mvc.HttpPostAttribute;
|
||||
global using HttpPutAttribute = Microsoft.AspNetCore.Mvc.HttpPutAttribute;
|
@ -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>
|
||||
</svg>
|
Before Width: | Height: | Size: 710 B After Width: | Height: | Size: 739 B |
@ -164,19 +164,17 @@ public class UserPhotoManagerCache
|
||||
|
||||
public string SearchInCache(Guid userId, Size size)
|
||||
{
|
||||
string fileName = null;
|
||||
|
||||
if (!_photofiles.TryGetValue(userId, out var val))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!val.TryGetValue(UserPhotoManager.ToCache(size), out fileName))
|
||||
if (!val.TryGetValue(UserPhotoManager.ToCache(size), out var fileName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(fileName))
|
||||
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
fileName = val.Values.FirstOrDefault(x => !string.IsNullOrEmpty(x) && x.Contains("_orig_"));
|
||||
}
|
||||
|
297
yarn.lock
297
yarn.lock
@ -108,13 +108,13 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/client-cloudwatch-logs@npm:^3.297.0":
|
||||
version: 3.418.0
|
||||
resolution: "@aws-sdk/client-cloudwatch-logs@npm:3.418.0"
|
||||
version: 3.421.0
|
||||
resolution: "@aws-sdk/client-cloudwatch-logs@npm:3.421.0"
|
||||
dependencies:
|
||||
"@aws-crypto/sha256-browser": 3.0.0
|
||||
"@aws-crypto/sha256-js": 3.0.0
|
||||
"@aws-sdk/client-sts": 3.418.0
|
||||
"@aws-sdk/credential-provider-node": 3.418.0
|
||||
"@aws-sdk/client-sts": 3.421.0
|
||||
"@aws-sdk/credential-provider-node": 3.421.0
|
||||
"@aws-sdk/middleware-host-header": 3.418.0
|
||||
"@aws-sdk/middleware-logger": 3.418.0
|
||||
"@aws-sdk/middleware-recursion-detection": 3.418.0
|
||||
@ -149,13 +149,13 @@ __metadata:
|
||||
"@smithy/util-utf8": ^2.0.0
|
||||
tslib: ^2.5.0
|
||||
uuid: ^8.3.2
|
||||
checksum: cf250588753d42dbbd4605425aeb0a7a1838a0b46f0054f0f2e18c7075abfde3509d7ca9f25236a4188bff6e640da44d47d4236b8d479e209e5633c04ab55665
|
||||
checksum: 8335143173c8c26e4c36af9ef323c42afe002acdeeade86b4ca2b0877e4382b0e18abdd66e9a8dcb2a5e308e450b0eb4002ef9d8f056cc612466337ae955b500
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/client-sso@npm:3.418.0":
|
||||
version: 3.418.0
|
||||
resolution: "@aws-sdk/client-sso@npm:3.418.0"
|
||||
"@aws-sdk/client-sso@npm:3.421.0":
|
||||
version: 3.421.0
|
||||
resolution: "@aws-sdk/client-sso@npm:3.421.0"
|
||||
dependencies:
|
||||
"@aws-crypto/sha256-browser": 3.0.0
|
||||
"@aws-crypto/sha256-js": 3.0.0
|
||||
@ -191,17 +191,17 @@ __metadata:
|
||||
"@smithy/util-retry": ^2.0.2
|
||||
"@smithy/util-utf8": ^2.0.0
|
||||
tslib: ^2.5.0
|
||||
checksum: 54c17953ac1143f07fff8a462bddeb87a5cb10d5ffb269ed93e837da7b37d4d3ae81aa0986b23c0e687712c58320bb28b5ac0ab4a8cb2841fbb0ce2436df648d
|
||||
checksum: 2dcfbd104668f49fc5b18af6a5bced3845e75c1facab7fd26e97c40ea447e9c29d4f3a5e14d90e66a008db251a6aa27fc8af7652e3c1d8916853ff37df28c72c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/client-sts@npm:3.418.0":
|
||||
version: 3.418.0
|
||||
resolution: "@aws-sdk/client-sts@npm:3.418.0"
|
||||
"@aws-sdk/client-sts@npm:3.421.0":
|
||||
version: 3.421.0
|
||||
resolution: "@aws-sdk/client-sts@npm:3.421.0"
|
||||
dependencies:
|
||||
"@aws-crypto/sha256-browser": 3.0.0
|
||||
"@aws-crypto/sha256-js": 3.0.0
|
||||
"@aws-sdk/credential-provider-node": 3.418.0
|
||||
"@aws-sdk/credential-provider-node": 3.421.0
|
||||
"@aws-sdk/middleware-host-header": 3.418.0
|
||||
"@aws-sdk/middleware-logger": 3.418.0
|
||||
"@aws-sdk/middleware-recursion-detection": 3.418.0
|
||||
@ -237,7 +237,7 @@ __metadata:
|
||||
"@smithy/util-utf8": ^2.0.0
|
||||
fast-xml-parser: 4.2.5
|
||||
tslib: ^2.5.0
|
||||
checksum: 594ce1a04a1c5aa6b21e7ddf0342d9705e27a60bfba3b30b192152987aba6bd6227d8e98e6ccddae1ccf7090de9e12737f8cd996918302ca574b69fe6f93ad37
|
||||
checksum: c21a28288341a7f6749e61391846b7a6754de3594137a7c436b1d96d82a8043adc921b45f8d22e2894e50cdf3799f51143a42f214ae552bcd0e0088c85d2de09
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -253,13 +253,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/credential-provider-ini@npm:3.418.0":
|
||||
version: 3.418.0
|
||||
resolution: "@aws-sdk/credential-provider-ini@npm:3.418.0"
|
||||
"@aws-sdk/credential-provider-ini@npm:3.421.0":
|
||||
version: 3.421.0
|
||||
resolution: "@aws-sdk/credential-provider-ini@npm:3.421.0"
|
||||
dependencies:
|
||||
"@aws-sdk/credential-provider-env": 3.418.0
|
||||
"@aws-sdk/credential-provider-process": 3.418.0
|
||||
"@aws-sdk/credential-provider-sso": 3.418.0
|
||||
"@aws-sdk/credential-provider-sso": 3.421.0
|
||||
"@aws-sdk/credential-provider-web-identity": 3.418.0
|
||||
"@aws-sdk/types": 3.418.0
|
||||
"@smithy/credential-provider-imds": ^2.0.0
|
||||
@ -267,18 +267,18 @@ __metadata:
|
||||
"@smithy/shared-ini-file-loader": ^2.0.6
|
||||
"@smithy/types": ^2.3.3
|
||||
tslib: ^2.5.0
|
||||
checksum: c519d15523d872c3d3249df48e513e7c648f1668bb9baec6af895613fcdf14f552d917337585bed744260796b5f0bd033215102c1bbceb588d46738b7d5093d1
|
||||
checksum: 038e115250a966a28b43faa6a70ef4e9d543d51e0eed8446d0939c44a7a0c3333a3dcb0a80aa5c2b328cc225228aef8f8aa5b31ba1725316b3019b88ad091ad8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/credential-provider-node@npm:3.418.0":
|
||||
version: 3.418.0
|
||||
resolution: "@aws-sdk/credential-provider-node@npm:3.418.0"
|
||||
"@aws-sdk/credential-provider-node@npm:3.421.0":
|
||||
version: 3.421.0
|
||||
resolution: "@aws-sdk/credential-provider-node@npm:3.421.0"
|
||||
dependencies:
|
||||
"@aws-sdk/credential-provider-env": 3.418.0
|
||||
"@aws-sdk/credential-provider-ini": 3.418.0
|
||||
"@aws-sdk/credential-provider-ini": 3.421.0
|
||||
"@aws-sdk/credential-provider-process": 3.418.0
|
||||
"@aws-sdk/credential-provider-sso": 3.418.0
|
||||
"@aws-sdk/credential-provider-sso": 3.421.0
|
||||
"@aws-sdk/credential-provider-web-identity": 3.418.0
|
||||
"@aws-sdk/types": 3.418.0
|
||||
"@smithy/credential-provider-imds": ^2.0.0
|
||||
@ -286,7 +286,7 @@ __metadata:
|
||||
"@smithy/shared-ini-file-loader": ^2.0.6
|
||||
"@smithy/types": ^2.3.3
|
||||
tslib: ^2.5.0
|
||||
checksum: bbaf9d9f4c7b7ab0c9b5772e3abcb6642021d8d10081f31ad4de34d1ba0522d9dc817aced7f28e2b8fff443177cfe90c295c0263f96be61d8c4e3849b44cb7c7
|
||||
checksum: 0b0249ebc58383e7149f8b5398ca8d6af93df397fae4c0feaea9debd4617a90d7c9a01486709173799edaf06c6c34e9c979969da1a41bdb681d01c6661e6e0e3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -303,18 +303,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/credential-provider-sso@npm:3.418.0":
|
||||
version: 3.418.0
|
||||
resolution: "@aws-sdk/credential-provider-sso@npm:3.418.0"
|
||||
"@aws-sdk/credential-provider-sso@npm:3.421.0":
|
||||
version: 3.421.0
|
||||
resolution: "@aws-sdk/credential-provider-sso@npm:3.421.0"
|
||||
dependencies:
|
||||
"@aws-sdk/client-sso": 3.418.0
|
||||
"@aws-sdk/client-sso": 3.421.0
|
||||
"@aws-sdk/token-providers": 3.418.0
|
||||
"@aws-sdk/types": 3.418.0
|
||||
"@smithy/property-provider": ^2.0.0
|
||||
"@smithy/shared-ini-file-loader": ^2.0.6
|
||||
"@smithy/types": ^2.3.3
|
||||
tslib: ^2.5.0
|
||||
checksum: e666dd10f50b9510b09f64bdac66f97ac18e1521bdb6fd642c1446740fbeaf5d7a609dfb5c7166561cb5cecd5149b0ae08d9b9df919641168c022657f297414d
|
||||
checksum: 7fd59a74fe28602ac0877c9e845a95a95a3be91869be33776576c2cbe41cce8b5921480e769a1fdf02db41f67d792e9f1ea93d9e5f0256736cea8f24d8839b24
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3774,9 +3774,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/regexpp@npm:^4.6.1":
|
||||
version: 4.8.1
|
||||
resolution: "@eslint-community/regexpp@npm:4.8.1"
|
||||
checksum: 82d62c845ef42b810f268cfdc84d803a2da01735fb52e902fd34bdc09f92464a094fd8e4802839874b000b2f73f67c972859e813ba705233515d3e954f234bf2
|
||||
version: 4.9.0
|
||||
resolution: "@eslint-community/regexpp@npm:4.9.0"
|
||||
checksum: 82411f0643ab9bfd271bf12c8c75031266b13595d9371585ee3b0d680d918d4abf37c7e94d0da22e45817c9bbc59b79dfcbd672050dfb00af88fb89c80fd420f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -4254,12 +4254,12 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@grpc/grpc-js@npm:^1.3.2":
|
||||
version: 1.9.3
|
||||
resolution: "@grpc/grpc-js@npm:1.9.3"
|
||||
version: 1.9.4
|
||||
resolution: "@grpc/grpc-js@npm:1.9.4"
|
||||
dependencies:
|
||||
"@grpc/proto-loader": ^0.7.8
|
||||
"@types/node": ">=12.12.47"
|
||||
checksum: 09634de9f871a17c95db95338fe472522d5dca0f77b622e3a497ef806262813445ba1a1f3261f03461d84ac985e420317d5638b3314fcb524ee2e47e512ffa64
|
||||
checksum: 83616d3727f16d9caf57fb16f8525e6884856569624a244ef8dde2873b7d7a439d302fd0a82d28bbbf9ba9777d3b25a58c706e35daa30120abb04f1646aa0e11
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -8386,11 +8386,11 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/istanbul-lib-report@npm:*":
|
||||
version: 3.0.0
|
||||
resolution: "@types/istanbul-lib-report@npm:3.0.0"
|
||||
version: 3.0.1
|
||||
resolution: "@types/istanbul-lib-report@npm:3.0.1"
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage": "*"
|
||||
checksum: 656398b62dc288e1b5226f8880af98087233cdb90100655c989a09f3052b5775bf98ba58a16c5ae642fb66c61aba402e07a9f2bff1d1569e3b306026c59f3f36
|
||||
checksum: cfc66de48577bb7b2636a6afded7056483693c3ea70916276518cdfaa0d4b51bf564ded88fb13e75716665c3af3d4d54e9c2de042c0219dcabad7e81c398688b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -8405,11 +8405,11 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/istanbul-reports@npm:^3.0.0":
|
||||
version: 3.0.1
|
||||
resolution: "@types/istanbul-reports@npm:3.0.1"
|
||||
version: 3.0.2
|
||||
resolution: "@types/istanbul-reports@npm:3.0.2"
|
||||
dependencies:
|
||||
"@types/istanbul-lib-report": "*"
|
||||
checksum: f1ad54bc68f37f60b30c7915886b92f86b847033e597f9b34f2415acdbe5ed742fa559a0a40050d74cdba3b6a63c342cac1f3a64dba5b68b66a6941f4abd7903
|
||||
checksum: f52028d6fe4d28f0085dd7ed66ccfa6af632579e9a4091b90928ffef93d4dbec0bacd49e9caf1b939d05df9eafc5ac1f5939413cdf8ac59fbe4b29602d4d0939
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -8460,46 +8460,46 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/mdast@npm:^3.0.0":
|
||||
version: 3.0.12
|
||||
resolution: "@types/mdast@npm:3.0.12"
|
||||
version: 3.0.13
|
||||
resolution: "@types/mdast@npm:3.0.13"
|
||||
dependencies:
|
||||
"@types/unist": ^2
|
||||
checksum: 83adb8679b9d139f69f63554d120af921e9f1289e9903a2c99e0554a327c8524a6c0beccdc0721e4fdbccc606e81964fecb0d390d53df0f74360938e22f1a469
|
||||
checksum: f13fa17a2931ed1492a2f0012a3abd6de3a2d1128145981321909e03fedba80162668f284a4af92aca3732b27e933c5f4772336d96b9ae660bfde696d07abbe6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/mdurl@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "@types/mdurl@npm:1.0.2"
|
||||
checksum: 79c7e523b377f53cf1f5a240fe23d0c6cae856667692bd21bf1d064eafe5ccc40ae39a2aa0a9a51e8c94d1307228c8f6b121e847124591a9a828c3baf65e86e2
|
||||
version: 1.0.3
|
||||
resolution: "@types/mdurl@npm:1.0.3"
|
||||
checksum: 5bbed4f0eb9f60040fa26be77aa2158ca468b6423876cec0d2043e7f8298e83b8e5b95fb66056327b02d747c4d376aed16c11ff3fdc4cb3dca327a6931a71f18
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/mdx@npm:^2.0.0":
|
||||
version: 2.0.7
|
||||
resolution: "@types/mdx@npm:2.0.7"
|
||||
checksum: c746659ebea471535d99a49cc935dc9f831fac22def3fa8c8a0883ad2cae71c4cca1d8563fa60d0e2730b14cb13e95c32af8dfda455a4937476a7d9e2748d641
|
||||
version: 2.0.8
|
||||
resolution: "@types/mdx@npm:2.0.8"
|
||||
checksum: 4a7c2241c37e87aaab044c561f24874fabcd5cd2d6feb28dc665e2c80562afa7ddf94391a3b8ab3f76041199c8bafcff9131acf6d060f1aca45d763b171bbc29
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/mime-types@npm:^2.1.0":
|
||||
version: 2.1.1
|
||||
resolution: "@types/mime-types@npm:2.1.1"
|
||||
checksum: 106b5d556add46446a579ad25ff15d6b421851790d887edcad558c90c1e64b1defc72bfbaf4b08f208916e21d9cc45cdb951d77be51268b18221544cfe054a3c
|
||||
version: 2.1.2
|
||||
resolution: "@types/mime-types@npm:2.1.2"
|
||||
checksum: 9e3c78f1c63211e0450901212566a046da68d4438a5e543333ec9b0be3259bd5d01532734dc51ead40104889b98d12c7663b65212a318aafad3e34c98204e9e1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/mime@npm:*":
|
||||
version: 3.0.1
|
||||
resolution: "@types/mime@npm:3.0.1"
|
||||
checksum: 4040fac73fd0cea2460e29b348c1a6173da747f3a87da0dbce80dd7a9355a3d0e51d6d9a401654f3e5550620e3718b5a899b2ec1debf18424e298a2c605346e7
|
||||
version: 3.0.2
|
||||
resolution: "@types/mime@npm:3.0.2"
|
||||
checksum: 09cf74f6377d1b27f4a24512cb689ad30af59880ac473ed6f7bc5285ecde88bbe8fe500789340ad57810da9d6fe1704f86e8bfe147b9ea76d58925204a60b906
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/mime@npm:^1":
|
||||
version: 1.3.2
|
||||
resolution: "@types/mime@npm:1.3.2"
|
||||
checksum: 0493368244cced1a69cb791b485a260a422e6fcc857782e1178d1e6f219f1b161793e9f87f5fae1b219af0f50bee24fcbe733a18b4be8fdd07a38a8fb91146fd
|
||||
version: 1.3.3
|
||||
resolution: "@types/mime@npm:1.3.3"
|
||||
checksum: 7e27dede6517c1d604821a8a5412d6b7131decc8397ad4bac9216fc90dea26c9571426623ebeea2a9b89dbfb89ad98f7370a3c62cd2be8896c6e897333b117c9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -8511,18 +8511,18 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/morgan@npm:^1.9.4":
|
||||
version: 1.9.5
|
||||
resolution: "@types/morgan@npm:1.9.5"
|
||||
version: 1.9.6
|
||||
resolution: "@types/morgan@npm:1.9.6"
|
||||
dependencies:
|
||||
"@types/node": "*"
|
||||
checksum: f98deb4c7f2ad6049ad34ed7b0f0d427546bdf2358011070af9d597de1b0a03b38cc10cfe65ef2e7673e569c384303d949e76df701acefe288d547f614142973
|
||||
checksum: 6525248325a74342f929c958be69c0840c8f3a288e003a8904319cae92e531f17a8aa2700701e66775adcca7f9506dd630fec2f95dc04a3e73add04fde42aab8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/ms@npm:*":
|
||||
version: 0.7.31
|
||||
resolution: "@types/ms@npm:0.7.31"
|
||||
checksum: daadd354aedde024cce6f5aa873fefe7b71b22cd0e28632a69e8b677aeb48ae8caa1c60e5919bb781df040d116b01cb4316335167a3fc0ef6a63fa3614c0f6da
|
||||
version: 0.7.32
|
||||
resolution: "@types/ms@npm:0.7.32"
|
||||
checksum: 610744605c5924aa2657c8a62d307052af4f0e38e2aa015f154ef03391fabb4fd903f9c9baacb41f6e5798b8697e898463c351e5faf638738603ed29137b5254
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -8537,9 +8537,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0":
|
||||
version: 20.6.5
|
||||
resolution: "@types/node@npm:20.6.5"
|
||||
checksum: b849e849cf7631458a65c5019c81962028e306d8c4455a48422277b240f5a7eb8a1f1dafa60306bd4c773b77263bb8b05c074b1026e868bd137bb2022cf63ea2
|
||||
version: 20.7.1
|
||||
resolution: "@types/node@npm:20.7.1"
|
||||
checksum: 3140bd6c9130f1ed73a78ce7a1765ee43e155c1eea50eea45e18faeb31d11d97a84fffdc5e3a97582101d2f57d2652a50f510ede6c702780267bad74c822d56c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -8551,9 +8551,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:^18.15.5":
|
||||
version: 18.17.19
|
||||
resolution: "@types/node@npm:18.17.19"
|
||||
checksum: 6ab47127cd7534511aa199550659d56b44e5a6dbec9df054d0cde279926b4d43f0e6438f92c8392b039ab4e2a85aa0f698b95926430aff860e23bfc36c96576c
|
||||
version: 18.18.0
|
||||
resolution: "@types/node@npm:18.18.0"
|
||||
checksum: 61bcffa28eb713e7a4c66fd369df603369c3f834a783faeced95fe3e78903faa25f1a704d49e054f41d71b7915eeb066d10a37cc699421fcf5dd267f96ad5808
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -8616,27 +8616,27 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/range-parser@npm:*":
|
||||
version: 1.2.4
|
||||
resolution: "@types/range-parser@npm:1.2.4"
|
||||
checksum: b7c0dfd5080a989d6c8bb0b6750fc0933d9acabeb476da6fe71d8bdf1ab65e37c136169d84148034802f48378ab94e3c37bb4ef7656b2bec2cb9c0f8d4146a95
|
||||
version: 1.2.5
|
||||
resolution: "@types/range-parser@npm:1.2.5"
|
||||
checksum: db9aaa04a02d019395a9a4346475669a2864a32a6477ad0fc457bd2ef39a167cabe742f55a8a3fa8bc90abac795b716c22b37348bc3e19313ebe6c9310815233
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/reach__router@npm:^1.2.3":
|
||||
version: 1.3.11
|
||||
resolution: "@types/reach__router@npm:1.3.11"
|
||||
version: 1.3.12
|
||||
resolution: "@types/reach__router@npm:1.3.12"
|
||||
dependencies:
|
||||
"@types/react": "*"
|
||||
checksum: 6bcf40714e0dafff66cbf10a534320eae35dae8344f616d2ddf077937287a0df188dfbfb32fb8045cbc641139d1ab69ee5bb258a51642823cadefbcda020a044
|
||||
checksum: df838ba6bc3463c50ae35e704e11a9073bb0617c04676887a9ad87177b078d6593aa3cd8b63de07e791ac8b86183dbb0f20072c22dd40754487a47f0064c3fb7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-dom@npm:*, @types/react-dom@npm:^18.0.11":
|
||||
version: 18.2.7
|
||||
resolution: "@types/react-dom@npm:18.2.7"
|
||||
version: 18.2.8
|
||||
resolution: "@types/react-dom@npm:18.2.8"
|
||||
dependencies:
|
||||
"@types/react": "*"
|
||||
checksum: e02ea908289a7ad26053308248d2b87f6aeafd73d0e2de2a3d435947bcea0422599016ffd1c3e38ff36c42f5e1c87c7417f05b0a157e48649e4a02f21727d54f
|
||||
checksum: d36264631028d021b73cd9e015f10b95c4959ae1ce8f7a7419f318d1f05b1d063e6afffcd2a349a6bccd64ccc9ee9d2d976e1f0437643f0e7db621fa035bca65
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -8659,13 +8659,13 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/react@npm:*, @types/react@npm:>=16, @types/react@npm:^18.0.28":
|
||||
version: 18.2.22
|
||||
resolution: "@types/react@npm:18.2.22"
|
||||
version: 18.2.23
|
||||
resolution: "@types/react@npm:18.2.23"
|
||||
dependencies:
|
||||
"@types/prop-types": "*"
|
||||
"@types/scheduler": "*"
|
||||
csstype: ^3.0.2
|
||||
checksum: 44289523dabaadcd3fd85689abb98f9ebcc8492d7e978348d1c986138acef4801030b279e89a19e38a6319e294bcea77559e37e0c803e4bacf2b8ae3a56ba587
|
||||
checksum: efb9d1ed1940c0e7ba08a21ffba5e266d8dbbb8fe618cfb97bc902dfc96385fdd8189e3f7f64b4aa13134f8e61947d60560deb23be151253c3a97b0d070897ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -8677,55 +8677,55 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/scheduler@npm:*":
|
||||
version: 0.16.3
|
||||
resolution: "@types/scheduler@npm:0.16.3"
|
||||
checksum: 2b0aec39c24268e3ce938c5db2f2e77f5c3dd280e05c262d9c2fe7d890929e4632a6b8e94334017b66b45e4f92a5aa42ba3356640c2a1175fa37bef2f5200767
|
||||
version: 0.16.4
|
||||
resolution: "@types/scheduler@npm:0.16.4"
|
||||
checksum: a57b0f10da1b021e6bd5eeef8a1917dd3b08a8715bd8029e2ded2096d8f091bb1bb1fef2d66e139588a983c4bfbad29b59e48011141725fa83c76e986e1257d7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/semver@npm:^7.3.12, @types/semver@npm:^7.3.4":
|
||||
version: 7.5.2
|
||||
resolution: "@types/semver@npm:7.5.2"
|
||||
checksum: 743aa8a2b58e20b329c19bd2459152cb049d12fafab7279b90ac11e0f268c97efbcb606ea0c681cca03f79015381b40d9b1244349b354270bec3f939ed49f6e9
|
||||
version: 7.5.3
|
||||
resolution: "@types/semver@npm:7.5.3"
|
||||
checksum: 349fdd1ab6c213bac5c991bac766bd07b8b12e63762462bb058740dcd2eb09c8193d068bb226f134661275f2022976214c0e727a4e5eb83ec1b131127c980d3e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/send@npm:*":
|
||||
version: 0.17.1
|
||||
resolution: "@types/send@npm:0.17.1"
|
||||
version: 0.17.2
|
||||
resolution: "@types/send@npm:0.17.2"
|
||||
dependencies:
|
||||
"@types/mime": ^1
|
||||
"@types/node": "*"
|
||||
checksum: 10b620a5960058ef009afbc17686f680d6486277c62f640845381ec4baa0ea683fdd77c3afea4803daf5fcddd3fb2972c8aa32e078939f1d4e96f83195c89793
|
||||
checksum: 1ff5b1bd6a4f6fdc6402c7024781ff5dbd0e1f51a43c69529fb67c710943c7416d2f0d77c57c70fccf6616f25f838f32f960284526e408d4edae2e91e1fce95a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/serve-index@npm:^1.9.1":
|
||||
version: 1.9.1
|
||||
resolution: "@types/serve-index@npm:1.9.1"
|
||||
version: 1.9.2
|
||||
resolution: "@types/serve-index@npm:1.9.2"
|
||||
dependencies:
|
||||
"@types/express": "*"
|
||||
checksum: 026f3995fb500f6df7c3fe5009e53bad6d739e20b84089f58ebfafb2f404bbbb6162bbe33f72d2f2af32d5b8d3799c8e179793f90d9ed5871fb8591190bb6056
|
||||
checksum: 4fd0a8fcdd6e2b2d7704a539b7c1e0d143e9e00be4c3992394fe2ef7e9b67283d74b43db3f92b0e0717d779aa51184168bbae617d30456357cb95ec58aa59ea8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/serve-static@npm:*, @types/serve-static@npm:^1.13.10":
|
||||
version: 1.15.2
|
||||
resolution: "@types/serve-static@npm:1.15.2"
|
||||
version: 1.15.3
|
||||
resolution: "@types/serve-static@npm:1.15.3"
|
||||
dependencies:
|
||||
"@types/http-errors": "*"
|
||||
"@types/mime": "*"
|
||||
"@types/node": "*"
|
||||
checksum: 15c261dbfc57890f7cc17c04d5b22b418dfa0330c912b46c5d8ae2064da5d6f844ef7f41b63c7f4bbf07675e97ebe6ac804b032635ec742ae45d6f1274259b3e
|
||||
checksum: afa52252f0ba94cdb5391e80f23e17fd629bdf2a31be8876e2c4490312ed6b0570822dd7de7cea04c9002049e207709563568b7f4ee10bb9f456321db1e83e40
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/sockjs@npm:^0.3.33":
|
||||
version: 0.3.33
|
||||
resolution: "@types/sockjs@npm:0.3.33"
|
||||
version: 0.3.34
|
||||
resolution: "@types/sockjs@npm:0.3.34"
|
||||
dependencies:
|
||||
"@types/node": "*"
|
||||
checksum: b9bbb2b5c5ead2fb884bb019f61a014e37410bddd295de28184e1b2e71ee6b04120c5ba7b9954617f0bdf962c13d06249ce65004490889c747c80d3f628ea842
|
||||
checksum: 1d38b1976a97f5895a6be00cead1b2a59d842f023b6e35450b7eec8a3131c860c447aba3f7ea3c880c066d8277b0c76fae9d080be1aad475811b568ed6079d49
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -8737,13 +8737,13 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/styled-components@npm:^5.1.26":
|
||||
version: 5.1.27
|
||||
resolution: "@types/styled-components@npm:5.1.27"
|
||||
version: 5.1.28
|
||||
resolution: "@types/styled-components@npm:5.1.28"
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics": "*"
|
||||
"@types/react": "*"
|
||||
csstype: ^3.0.2
|
||||
checksum: 6f8d99642f09f28a233a5bf93d3139389c3bf5e4c643620881cb286f581d6844f3c1c43be5bd9405d3b199b6a858bd8b0ec6ccbb17b2304a88d70e2a6f91a996
|
||||
checksum: 2e12be4fdc2d60f63e16f8f5cac98d8305e0686b3332a160fec39df2a277f16715b878e25384ddda29e48acf101110a29ca108b716296e9a06bf2740f1257385
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -8798,9 +8798,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/webpack-env@npm:^1.16.0":
|
||||
version: 1.18.1
|
||||
resolution: "@types/webpack-env@npm:1.18.1"
|
||||
checksum: 3173c069763e51a96565d602af7e6dac9d772ae4aa6f26cac187cbf599a7f0b88f790b4b050b9dbdb0485daed3061b4a337863f3b8ce66f8a4e51f75ad387c6a
|
||||
version: 1.18.2
|
||||
resolution: "@types/webpack-env@npm:1.18.2"
|
||||
checksum: 883908ade827d35a10efc574fb6f2728a7c520d4296cf1507633ac7457204ccd697bc6c8cadac99bc5d96074a6109c658ebfde59f42ba5ba0fdfffc538892b0f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -8814,11 +8814,11 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/ws@npm:*, @types/ws@npm:^8.5.1, @types/ws@npm:^8.5.4":
|
||||
version: 8.5.5
|
||||
resolution: "@types/ws@npm:8.5.5"
|
||||
version: 8.5.6
|
||||
resolution: "@types/ws@npm:8.5.6"
|
||||
dependencies:
|
||||
"@types/node": "*"
|
||||
checksum: d00bf8070e6938e3ccf933010921c6ce78ac3606696ce37a393b27a9a603f7bd93ea64f3c5fa295a2f743575ba9c9a9fdb904af0f5fe2229bf2adf0630386e4a
|
||||
checksum: 7addb0c5fa4e7713d5209afb8a90f1852b12c02cb537395adf7a05fbaf21205dc5f7c110fd5ad6f3dbf147112cbff33fb11d8633059cb344f0c14f595b1ea1fb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -10813,16 +10813,16 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"browserslist@npm:^4.14.5, browserslist@npm:^4.21.10, browserslist@npm:^4.21.9, browserslist@npm:^4.9.1":
|
||||
version: 4.21.11
|
||||
resolution: "browserslist@npm:4.21.11"
|
||||
version: 4.22.0
|
||||
resolution: "browserslist@npm:4.22.0"
|
||||
dependencies:
|
||||
caniuse-lite: ^1.0.30001538
|
||||
electron-to-chromium: ^1.4.526
|
||||
caniuse-lite: ^1.0.30001539
|
||||
electron-to-chromium: ^1.4.530
|
||||
node-releases: ^2.0.13
|
||||
update-browserslist-db: ^1.0.13
|
||||
bin:
|
||||
browserslist: cli.js
|
||||
checksum: 89377745428d32c7bbec37055bc4b9e48aa859418ea3886b13218d825f8ea3053640f90d8652844ae855941fec0bffd2d69cf933035d6f9224b427d48d31eddf
|
||||
checksum: 14fc119bbfb85b65e2ee4a82205fabf9327520d010c4c586f1176ceaf9136cfdb391397045a4eafaa9defe52b6dbdf875916714695826c69091a936d5838f9ec
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -11040,10 +11040,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"caniuse-lite@npm:^1.0.30001538":
|
||||
version: 1.0.30001539
|
||||
resolution: "caniuse-lite@npm:1.0.30001539"
|
||||
checksum: 8595905d6c7234173a4915439fdd69fa2c06b0272d56af63aee0df6114e1ee4727758af160c0db9844055f778e0ea27ed4714facf2e472a2e74d9f567f111f41
|
||||
"caniuse-lite@npm:^1.0.30001539":
|
||||
version: 1.0.30001541
|
||||
resolution: "caniuse-lite@npm:1.0.30001541"
|
||||
checksum: 972f6c223cf4ea2c6821b817b419249285006bbf67ebe415fe58097cf07551e3bae898586736d92f7c40b9f0ac28638dbf760631c23742b780affd0254f44d17
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -13061,10 +13061,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"electron-to-chromium@npm:^1.4.526":
|
||||
version: 1.4.528
|
||||
resolution: "electron-to-chromium@npm:1.4.528"
|
||||
checksum: e9b249929e012ce6c25f8d1e1965e7fb19a426cc72fdf72026a0ce010846ec1b0efe2730b1a1f3d1c98bd11200b94b73431bc5279211a9f4a28f2389e690c39c
|
||||
"electron-to-chromium@npm:^1.4.530":
|
||||
version: 1.4.532
|
||||
resolution: "electron-to-chromium@npm:1.4.532"
|
||||
checksum: e9f77b5d6df84aa1f7598359ec2c988c3758e58106e63f2a0a6dc4756a6733b126316e61a79a2a6643aa2a0f9a1cf9ebe66c817dcb970a3fc9d8190342ef070a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -14793,9 +14793,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"fs-monkey@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "fs-monkey@npm:1.0.4"
|
||||
checksum: 8b254c982905c0b7e028eab22b410dc35a5c0019c1c860456f5f54ae6a61666e1cb8c6b700d6c88cc873694c00953c935847b9959cc4dcf274aacb8673c1e8bf
|
||||
version: 1.0.5
|
||||
resolution: "fs-monkey@npm:1.0.5"
|
||||
checksum: 424b67f65b37fe66117ae2bb061f790fe6d4b609e1d160487c74b3d69fbf42f262c665ccfba32e8b5f113f96f92e9a58fcdebe42d5f6649bdfc72918093a3119
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -15109,17 +15109,17 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"glob@npm:^10.2.2":
|
||||
version: 10.3.7
|
||||
resolution: "glob@npm:10.3.7"
|
||||
version: 10.3.10
|
||||
resolution: "glob@npm:10.3.10"
|
||||
dependencies:
|
||||
foreground-child: ^3.1.0
|
||||
jackspeak: ^2.0.3
|
||||
jackspeak: ^2.3.5
|
||||
minimatch: ^9.0.1
|
||||
minipass: ^5.0.0 || ^6.0.2 || ^7.0.0
|
||||
path-scurry: ^1.10.1
|
||||
bin:
|
||||
glob: dist/esm/bin.mjs
|
||||
checksum: 9a27f1fa8774c3a8ab8f05c26a77276edaf5418aac29aff70c5d847ef75dabf536554cb113e945193323fb769fbe32edde12559d2d52266f38662595cbc7a031
|
||||
checksum: 4f2fe2511e157b5a3f525a54092169a5f92405f24d2aed3142f4411df328baca13059f4182f1db1bf933e2c69c0bd89e57ae87edd8950cba8c7ccbe84f721cf3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -16941,16 +16941,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jackspeak@npm:^2.0.3":
|
||||
version: 2.3.3
|
||||
resolution: "jackspeak@npm:2.3.3"
|
||||
"jackspeak@npm:^2.3.5":
|
||||
version: 2.3.6
|
||||
resolution: "jackspeak@npm:2.3.6"
|
||||
dependencies:
|
||||
"@isaacs/cliui": ^8.0.2
|
||||
"@pkgjs/parseargs": ^0.11.0
|
||||
dependenciesMeta:
|
||||
"@pkgjs/parseargs":
|
||||
optional: true
|
||||
checksum: 4313a7c0cc44c7753c4cb9869935f0b06f4cf96827515f63f58ff46b3d2f6e29aba6b3b5151778397c3f5ae67ef8bfc48871967bd10343c27e90cff198ec7808
|
||||
checksum: 57d43ad11eadc98cdfe7496612f6bbb5255ea69fe51ea431162db302c2a11011642f50cfad57288bd0aea78384a0612b16e131944ad8ecd09d619041c8531b54
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -21563,8 +21563,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"rc-virtual-list@npm:^3.5.1":
|
||||
version: 3.11.1
|
||||
resolution: "rc-virtual-list@npm:3.11.1"
|
||||
version: 3.11.2
|
||||
resolution: "rc-virtual-list@npm:3.11.2"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.20.0
|
||||
classnames: ^2.2.6
|
||||
@ -21573,7 +21573,7 @@ __metadata:
|
||||
peerDependencies:
|
||||
react: "*"
|
||||
react-dom: "*"
|
||||
checksum: bd6b41b2ca452313534d4d5d1dc0efcb5baf1e26b815acf3553e69f96bb7eeb0980319284adf389f4bcc3f934265d08e8d7e193fb4677a3643bbd896080bced6
|
||||
checksum: b642e55ca2c4b56fb2048b5151a17e2f0f1c15cf3f4d8a26a4f1f370c95a6f4476743886b6633f083f107c82fbe86c5548bbf37b7e2f8b79615d90b62777824e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -21794,15 +21794,15 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"react-draggable@npm:^4.4.5":
|
||||
version: 4.4.5
|
||||
resolution: "react-draggable@npm:4.4.5"
|
||||
version: 4.4.6
|
||||
resolution: "react-draggable@npm:4.4.6"
|
||||
dependencies:
|
||||
clsx: ^1.1.1
|
||||
prop-types: ^15.8.1
|
||||
peerDependencies:
|
||||
react: ">= 16.3.0"
|
||||
react-dom: ">= 16.3.0"
|
||||
checksum: 21c3775db086e13020967627c20acd41d1ddbc7c7d7fca51491a5bbb54a0aa7e1730a4bc9af17141eb50a4954e547a5e25b2368f5f54b70db6f2686a897bacf2
|
||||
checksum: 9b15aac59244873ac4561c5a2bead43a56e18d406e0a5f242bd4f9d151c074530c02b99387983104bf43417292f9cf8d063e554ed08d88792235e3fbc965f1b8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -22283,15 +22283,15 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"react-tooltip@npm:^5.21.1":
|
||||
version: 5.21.4
|
||||
resolution: "react-tooltip@npm:5.21.4"
|
||||
version: 5.21.5
|
||||
resolution: "react-tooltip@npm:5.21.5"
|
||||
dependencies:
|
||||
"@floating-ui/dom": ^1.0.0
|
||||
classnames: ^2.3.0
|
||||
peerDependencies:
|
||||
react: ">=16.14.0"
|
||||
react-dom: ">=16.14.0"
|
||||
checksum: 9b663c5d9a1472b96d3ed708ecce01052aed267dc739520c9b55389a5e42c195a6bcd0717219f260bf0dadee2452d80a7c5fa14c5c3f8b5cdc6a9174d7f42d4a
|
||||
checksum: 067d6a5bc7c6b12b2ae054f855416b068bcc9fe59864b83c655e775066d6f81728b2ec13e80565968da7ce35c7c0286f8e154c635a1e79bb7a8233df8a356c51
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -23300,7 +23300,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sax@npm:^1.2.4, sax@npm:~1.2.1, sax@npm:~1.2.4":
|
||||
"sax@npm:^1.2.4":
|
||||
version: 1.3.0
|
||||
resolution: "sax@npm:1.3.0"
|
||||
checksum: 238ab3a9ba8c8f8aaf1c5ea9120386391f6ee0af52f1a6a40bbb6df78241dd05d782f2359d614ac6aae08c4c4125208b456548a6cf68625aa4fe178486e63ecd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sax@npm:~1.2.1, sax@npm:~1.2.4":
|
||||
version: 1.2.4
|
||||
resolution: "sax@npm:1.2.4"
|
||||
checksum: d3df7d32b897a2c2f28e941f732c71ba90e27c24f62ee918bd4d9a8cfb3553f2f81e5493c7f0be94a11c1911b643a9108f231dd6f60df3fa9586b5d2e3e9e1fe
|
||||
|
Loading…
Reference in New Issue
Block a user