DocSpace-client/packages/shared/components/share/index.tsx
2024-05-29 16:58:46 +05:00

366 lines
11 KiB
TypeScript

// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import moment from "moment";
import InfoIcon from "PUBLIC_DIR/images/info.outline.react.svg?url";
import LinksToViewingIconUrl from "PUBLIC_DIR/images/links-to-viewing.react.svg?url";
import { ShareAccessRights } from "../../enums";
import { LINKS_LIMIT_COUNT } from "../../constants";
import {
addExternalLink,
editExternalLink,
getExternalLinks,
getPrimaryLink,
} from "../../api/files";
import { TAvailableExternalRights, TFileLink } from "../../api/files/types";
import { isDesktop } from "../../utils";
import { copyShareLink } from "../../utils/copy";
import { TOption } from "../combobox";
import { Text } from "../text";
import { IconButton } from "../icon-button";
import { Tooltip } from "../tooltip";
import { toastr } from "../toast";
import { TData } from "../toast/Toast.type";
import PublicRoomBar from "../public-room-bar";
import ShareLoader from "../../skeletons/share";
import LinkRow from "./sub-components/LinkRow";
import { StyledLinks } from "./Share.styled";
import { ShareProps, TLink } from "./Share.types";
const Share = (props: ShareProps) => {
const {
isRooms,
setView,
infoPanelSelection,
getPrimaryFileLink,
editFileLink,
addFileLink,
shareChanged,
setShareChanged,
} = props;
const { t } = useTranslation(["Common"]);
const [fileLinks, setFileLinks] = useState<TLink[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [loadingLinks, setLoadingLinks] = useState<(string | number)[]>([]);
const requestRunning = React.useRef(false);
const [isLoadedAddLinks, setIsLoadedAddLinks] = useState(true);
const hideSharePanel = isRooms || !infoPanelSelection?.canShare;
const fetchLinks = React.useCallback(async () => {
if (requestRunning.current || hideSharePanel) return;
requestRunning.current = true;
const res = await getExternalLinks(infoPanelSelection.id);
setFileLinks(res.items);
setIsLoading(false);
requestRunning.current = false;
}, [infoPanelSelection.id, hideSharePanel]);
useEffect(() => {
if (hideSharePanel) {
setView?.("info_details");
} else {
fetchLinks();
}
}, [fetchLinks, hideSharePanel, setView]);
useEffect(() => {
fetchLinks();
setShareChanged?.(false);
}, [fetchLinks, setShareChanged, shareChanged]);
const addLoaderLink = () => {
const link = { isLoaded: true };
setFileLinks([...fileLinks, ...[link]]);
};
const addGeneralLink = async () => {
try {
addLoaderLink();
const link = getPrimaryFileLink
? await getPrimaryFileLink(infoPanelSelection.id)
: await getPrimaryLink(infoPanelSelection.id);
if (link) {
setFileLinks([link]);
copyShareLink(link.sharedTo.shareLink);
toastr.success(t("Common:GeneralAccessLinkCopied"));
} else {
setFileLinks([]);
}
} catch (error) {
const message = (error as { message: string }).message
? ((error as { message: string }).message as TData)
: (error as string);
toastr.error(message);
setFileLinks([]);
}
};
const addAdditionalLinks = async () => {
if (!isLoadedAddLinks) return;
setIsLoadedAddLinks(false);
addLoaderLink();
const newLink = addFileLink
? await addFileLink(
infoPanelSelection.id,
ShareAccessRights.ReadOnly,
false,
false,
)
: await addExternalLink(
infoPanelSelection.id,
ShareAccessRights.ReadOnly,
false,
false,
);
setFileLinks((links) => {
const newLinks: TLink[] = [...links];
const idx = newLinks.findIndex((l) => "isLoaded" in l && l.isLoaded);
if (typeof idx !== "undefined") newLinks[idx] = { ...newLink };
return newLinks;
});
setIsLoadedAddLinks(true);
};
const updateLink = (link: TFileLink, newItem: TFileLink) => {
const newArr = fileLinks.map((item) => {
if ("sharedTo" in item && item.sharedTo.id === newItem.sharedTo.id) {
return newItem || null;
}
return item;
});
setFileLinks(newArr);
const newLoadingLinks = loadingLinks.filter(
(item) => item !== link.sharedTo.id,
);
setLoadingLinks(newLoadingLinks);
};
const deleteLink = (id: string | number) => {
const newArr = fileLinks.filter(
(item) => "sharedTo" in item && item.sharedTo.id !== id,
);
setFileLinks(newArr);
};
const changeShareOption = async (item: TOption, link: TFileLink) => {
try {
setLoadingLinks((val) => [...val, link.sharedTo.id]);
const expDate = moment(link.sharedTo.expirationDate);
const res = editFileLink
? await editFileLink(
infoPanelSelection.id,
link.sharedTo.id,
link.access,
link.sharedTo.primary,
item.internal || false,
expDate,
)
: await editExternalLink(
infoPanelSelection.id,
link.sharedTo.id,
link.access,
link.sharedTo.primary,
item.internal || false,
expDate,
);
updateLink(link, res);
copyShareLink(link.sharedTo.shareLink);
toastr.success(t("Common:LinkSuccessfullyCopied"));
} catch (e) {
toastr.error(e as TData);
}
};
const changeAccessOption = async (item: TOption, link: TFileLink) => {
try {
setLoadingLinks([...loadingLinks, link.sharedTo.id]);
const expDate = moment(link.sharedTo.expirationDate);
const res = editFileLink
? await editFileLink(
infoPanelSelection.id,
link.sharedTo.id,
item.access ?? ({} as ShareAccessRights),
link.sharedTo.primary,
link.sharedTo.internal || false,
expDate,
)
: await editExternalLink(
infoPanelSelection.id,
link.sharedTo.id,
item.access ?? ({} as ShareAccessRights),
link.sharedTo.primary,
link.sharedTo.internal || false,
expDate,
);
if (item.access === ShareAccessRights.None) {
deleteLink(link.sharedTo.id);
if (link.sharedTo.primary) {
toastr.success(t("Common:GeneralAccessLinkRemove"));
} else {
toastr.success(t("Common:AdditionalLinkRemove"));
}
} else {
updateLink(link, res);
if (item.access === ShareAccessRights.DenyAccess) {
toastr.success(t("Common:LinkAccessDenied"));
} else {
copyShareLink(link.sharedTo.shareLink);
toastr.success(t("Common:LinkSuccessfullyCopied"));
}
}
} catch (e) {
toastr.error(e as TData);
}
};
const changeExpirationOption = async (
link: TFileLink,
expirationDate: moment.Moment | null,
) => {
try {
setLoadingLinks([...loadingLinks, link.sharedTo.id]);
const expDate = moment(expirationDate);
const res = editFileLink
? await editFileLink(
infoPanelSelection.id,
link.sharedTo.id,
link.access,
link.sharedTo.primary,
link.sharedTo.internal || false,
expDate,
)
: await editExternalLink(
infoPanelSelection.id,
link.sharedTo.id,
link.access,
link.sharedTo.primary,
link.sharedTo.internal || false,
expDate,
);
updateLink(link, res);
copyShareLink(link.sharedTo.shareLink);
toastr.success(t("Common:LinkSuccessfullyCopied"));
} catch (e) {
toastr.error(e as TData);
}
};
const getTextTooltip = () => {
return (
<Text fontSize="12px" noSelect>
{t("Common:MaximumNumberOfExternalLinksCreated")}
</Text>
);
};
if (hideSharePanel) return null;
return (
<div>
<PublicRoomBar
headerText={t("Common:ShareDocument")}
bodyText={t("Common:ShareDocumentDescription")}
iconName={InfoIcon}
/>
{isLoading ? (
<ShareLoader t={t} />
) : (
<StyledLinks>
<div className="additional-link">
<Text fontSize="14px" fontWeight={600} className="title-link">
{t("Common:SharedLinks")}
</Text>
{fileLinks.length > 0 && (
<div data-tooltip-id="file-links-tooltip" data-tip="tooltip">
<IconButton
className="link-to-viewing-icon"
iconName={LinksToViewingIconUrl}
onClick={addAdditionalLinks}
size={16}
isDisabled={fileLinks.length > LINKS_LIMIT_COUNT}
/>
{fileLinks.length > LINKS_LIMIT_COUNT && (
<Tooltip
float={isDesktop()}
id="file-links-tooltip"
getContent={getTextTooltip}
place="bottom"
/>
)}
</div>
)}
</div>
<LinkRow
onAddClick={addGeneralLink}
links={fileLinks}
changeShareOption={changeShareOption}
changeAccessOption={changeAccessOption}
changeExpirationOption={changeExpirationOption}
availableExternalRights={
infoPanelSelection.availableExternalRights ??
({} as TAvailableExternalRights)
}
loadingLinks={loadingLinks}
/>
</StyledLinks>
)}
</div>
);
};
export default Share;