Web: Files: Templates: added TemplateAccessSettingsPanel

This commit is contained in:
Nikita Gopienko 2024-05-06 17:37:27 +03:00
parent c8c7d29588
commit 8f64f6cc2d
18 changed files with 2008 additions and 15 deletions

View File

@ -195,5 +195,11 @@
"WantToRestoreTheRoom": "All shared links in this room will become active, and its contents will be available to everyone with the link. Do you want to restore the room?",
"WantToRestoreTheRooms": "All shared links in restored rooms will become active, and their contents will be available to everyone with the room links. Do you want to restore the rooms?",
"WithSubfolders": "With subfolders",
"YouLeftTheRoom": "You have left the room"
"YouLeftTheRoom": "You have left the room",
"TemplateAvailable": "Template available to everyone",
"TemplateAvailableDescription": "All DocSpace and Room admins will be able to create rooms using this template.",
"AddUsersOrGroups": "Add Users or Groups",
"AddUsersOrGroupsDescription": "The added administrators will be able to create rooms using this template.",
"AddUsersOrGroupsInfo": "Only DocSpace and Room admins are shown her",
"AddUsersOrGroupsInfoGroups": "Only DocSpace and Room admins from the selected groups will be able to create rooms using this template."
}

View File

@ -100,6 +100,10 @@ export default function withQuickButtons(WrappedComponent) {
}
};
onCreateRoom = () => {
this.props.onCreateRoomFromTemplate(this.props.item);
};
render() {
const { isLoading } = this.state;
@ -115,7 +119,6 @@ export default function withQuickButtons(WrappedComponent) {
isPersonalRoom,
isArchiveFolder,
isTemplatesFolder,
onCreateRoomFromTemplate,
} = this.props;
const quickButtonsComponent = (
@ -136,7 +139,7 @@ export default function withQuickButtons(WrappedComponent) {
folderCategory={folderCategory}
onCopyPrimaryLink={this.onCopyPrimaryLink}
isArchiveFolder={isArchiveFolder}
onCreateRoom={onCreateRoomFromTemplate}
onCreateRoom={this.onCreateRoom}
isTemplatesFolder={isTemplatesFolder}
/>
);

View File

@ -41,6 +41,7 @@ import {
InvitePanel,
EditLinkPanel,
EmbeddingPanel,
TemplateAccessSettingsPanel,
} from "../panels";
import {
ConnectDialog,
@ -131,6 +132,7 @@ const Panels = (props) => {
shareFolderDialogVisible,
pdfFormEditVisible,
createRoomTemplateDialogVisible,
templateAccessSettingsVisible,
} = props;
const [createPDFFormFile, setCreatePDFFormFile] = useState({
@ -320,6 +322,9 @@ const Panels = (props) => {
createRoomTemplateDialogVisible && (
<CreateRoomTemplateDialog key="create-room-template-dialog" />
),
templateAccessSettingsVisible && (
<TemplateAccessSettingsPanel key="template-access-settings" />
),
];
};
@ -380,6 +385,7 @@ export default inject(
shareFolderDialogVisible,
pdfFormEditVisible,
createRoomTemplateDialogVisible,
templateAccessSettingsVisible,
} = dialogsStore;
const { preparationPortalDialogVisible } = backup;
@ -447,6 +453,7 @@ export default inject(
shareFolderDialogVisible,
pdfFormEditVisible,
createRoomTemplateDialogVisible,
templateAccessSettingsVisible,
};
},
)(observer(Panels));

View File

@ -83,8 +83,15 @@ const StyledButtonContainer = styled.div`
`;
const CreateRoomTemplate = (props) => {
const { visible, onClose, item, fetchedTags, folderFormValidation, isEdit } =
props;
const {
visible,
onClose,
item,
fetchedTags,
folderFormValidation,
isEdit,
setAccessSettingsIsVisible,
} = props;
const { roomType, title, logo, createdBy } = item;
console.log("item", item);
@ -148,6 +155,10 @@ const CreateRoomTemplate = (props) => {
setOpenCreatedIsChecked(!openCreatedIsChecked);
};
const onOpenAccessSettings = () => {
setAccessSettingsIsVisible(true);
};
const tagHandler = new TagHandler(tags, setRoomTags, fetchedTags);
return (
@ -198,7 +209,7 @@ const CreateRoomTemplate = (props) => {
<ChangeRoomOwner
roomOwner={createdBy}
isTemplate
onOwnerChange={() => console.log("Access settings")}
onOwnerChange={onOpenAccessSettings}
/>
<div>
@ -262,7 +273,10 @@ const CreateRoomTemplate = (props) => {
);
};
export default inject(({ settingsStore }) => {
export default inject(({ settingsStore, dialogsStore }) => {
const { folderFormValidation } = settingsStore;
return { folderFormValidation };
const { setTemplateAccessSettingsVisible: setAccessSettingsIsVisible } =
dialogsStore;
return { folderFormValidation, setAccessSettingsIsVisible };
})(observer(CreateRoomTemplate));

View File

@ -0,0 +1,553 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import styled, { css } from "styled-components";
import { Heading } from "@docspace/shared/components/heading";
import { TextInput } from "@docspace/shared/components/text-input";
import { ComboBox } from "@docspace/shared/components/combobox";
import { Box } from "@docspace/shared/components/box";
import { DropDown } from "@docspace/shared/components/drop-down";
import { Text } from "@docspace/shared/components/text";
import { Button } from "@docspace/shared/components/button";
import { HelpButton } from "@docspace/shared/components/help-button";
import { Link } from "@docspace/shared/components/link";
import { ToggleButton } from "@docspace/shared/components/toggle-button";
import { mobile, commonIconsStyles } from "@docspace/shared/utils";
import CheckIcon from "PUBLIC_DIR/images/check.edit.react.svg";
import CrossIcon from "PUBLIC_DIR/images/cross.edit.react.svg";
import CrossIconMobile from "PUBLIC_DIR/images/cross.react.svg";
import DeleteIcon from "PUBLIC_DIR/images/mobile.actions.remove.react.svg";
import { isMobile, desktop, commonInputStyles } from "@docspace/shared/utils";
import Base from "@docspace/shared/themes/base";
const fillAvailableWidth = css`
width: 100%;
width: -moz-available;
width: -webkit-fill-available;
width: fill-available;
`;
const StyledInvitePanel = styled.div`
@media ${mobile} {
user-select: none;
height: auto;
width: auto;
background: ${(props) => props.theme.infoPanel.blurColor};
backdrop-filter: blur(3px);
z-index: 309;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
.invite_panel {
background-color: ${(props) => props.theme.infoPanel.backgroundColor};
border-left: ${(props) =>
`1px solid ${props.theme.infoPanel.borderColor}`};
position: absolute;
border: none;
right: 0;
bottom: 0;
height: calc(100% - 64px);
width: 100vw;
max-width: 100vw;
}
}
.invite-panel-body {
height: calc(100% - 55px - 73px);
.scroll-body {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-left: 0px !important;
`
: css`
padding-right: 0px !important;
`}
@media ${desktop} {
width: 480px;
min-width: auto !important;
}
}
${(props) =>
!props.addUsersPanelVisible &&
isMobile() &&
props.theme.interfaceDirection !== "rtl" &&
css`
.trackYVisible {
.scroller {
margin-right: -20px !important;
}
}
`}
}
`;
const ScrollList = styled.div`
width: 100%;
height: ${(props) =>
props.scrollAllPanelContent && props.isTotalListHeight
? "auto"
: props.offsetTop && `calc(100% - ${props.offsetTop}px)`};
${!isMobile() &&
css`
.row-item {
width: 448px !important;
}
`}
`;
const StyledBlock = styled.div`
padding: ${(props) => (props.noPadding ? "0px" : "0 16px")};
border-bottom: ${(props) => props.theme.filesPanels.sharing.borderBottom};
`;
StyledBlock.defaultProps = { theme: Base };
const StyledInviteUserBody = styled.div`
display: flex;
flex-direction: column;
overflow: auto;
`;
const StyledHeading = styled(Heading)`
font-weight: 700;
font-size: 21px;
`;
const StyledSubHeader = styled(Heading)`
font-weight: 700;
font-size: 16px;
padding-left: 16px;
padding-right: 16px;
margin: 16px 0 8px 0;
${(props) =>
props.inline &&
css`
display: inline-flex;
align-items: center;
gap: 16px;
`};
`;
const StyledDescription = styled(Text)`
padding-left: 16px;
padding-right: 16px;
color: ${(props) =>
props.theme.createEditRoomDialog.commonParam.descriptionColor};
margin-bottom: 16px;
font-weight: 400;
font-size: 12px;
line-height: 16px;
`;
StyledDescription.defaultProps = { theme: Base };
const StyledRow = styled.div`
width: calc(100% - 32px) !important;
display: inline-flex;
align-items: center;
gap: 8px;
min-height: 41px;
margin-inline-start: 16px;
box-sizing: border-box;
border-bottom: none;
a {
font-weight: 600;
font-size: 14px;
line-height: 16px;
}
.invite-panel_access-selector {
margin-inline-start: auto;
margin-inline-end: 0;
${({ hasWarning }) => hasWarning && `margin-inline-start: 0;`}
}
.warning {
margin-inline-start: auto;
}
.combo-button-label {
color: ${(props) => props.theme.text.disableColor};
}
.combo-buttons_expander-icon path {
fill: ${(props) => props.theme.text.disableColor};
}
`;
const StyledInviteInput = styled.div`
${fillAvailableWidth}
margin: 0px 16px;
.input-link {
height: 32px;
border: 0px;
> input {
height: 30px;
}
}
display: flex;
border: 1px solid rgb(208, 213, 218);
border-radius: 3px;
.copy-link-icon {
padding: 0;
&:hover {
svg path {
fill: ${(props) => props.theme.inputBlock.hoverIconColor} !important;
}
}
svg path {
fill: ${(props) => props.theme.inputBlock.iconColor} !important;
}
}
input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-results-button,
input[type="search"]::-webkit-search-results-decoration {
-webkit-appearance: none;
appearance: none;
}
.append {
display: ${(props) => (props.isShowCross ? "flex" : "none")};
align-items: center;
padding-right: 8px;
cursor: default;
}
${commonInputStyles}
:focus-within {
border-color: ${(props) => props.theme.inputBlock.borderColor};
}
`;
const StyledAccessSelector = styled.div`
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 16px;
`
: css`
margin-right: 16px;
`}
`;
const StyledEditInput = styled(TextInput)`
width: 100%;
`;
const StyledInviteInputContainer = styled.div`
position: relative;
display: flex;
align-items: center;
width: 100%;
margin-bottom: 20px;
.header_aside-panel {
max-width: 100% !important;
}
`;
const StyledDropDown = styled(DropDown)`
${(props) => props.width && `width: ${props.width}px`};
.list-item {
display: flex;
align-items: center;
gap: 8px;
height: 48px;
.list-item_content {
text-overflow: ellipsis;
overflow: hidden;
}
.email-list_avatar {
display: flex;
align-items: center;
gap: 8px;
overflow: hidden;
}
.email-list_add-button {
display: flex;
margin-left: auto;
align-items: center;
gap: 4px;
p {
color: #4781d1;
}
svg path {
fill: #4781d1;
}
}
}
`;
const SearchItemText = styled(Text)`
line-height: ${({ theme }) =>
theme.interfaceDirection === "rtl" ? `20px` : `16px`};
text-overflow: ellipsis;
overflow: hidden;
font-size: ${(props) =>
props.primary ? "14px" : props.info ? "11px" : "12px"};
font-weight: ${(props) => (props.primary || props.info ? "600" : "400")};
color: ${(props) =>
(props.primary && !props.disabled) || props.info
? props.theme.text.color
: props.theme.text.emailColor};
${(props) => props.info && `margin-inline-start: auto`}
`;
SearchItemText.defaultProps = { theme: Base };
const StyledEditButton = styled(Button)`
width: 32px;
height: 32px;
padding: 0px;
`;
const iconStyles = css`
${commonIconsStyles}
path {
fill: ${(props) => props.theme.filesEditingWrapper.fill} !important;
}
:hover {
fill: ${(props) => props.theme.filesEditingWrapper.hoverFill} !important;
}
`;
const StyledCheckIcon = styled(CheckIcon)`
${iconStyles}
`;
StyledCheckIcon.defaultProps = { theme: Base };
const StyledCrossIcon = styled(CrossIcon)`
${iconStyles}
`;
StyledCrossIcon.defaultProps = { theme: Base };
const StyledDeleteIcon = styled(DeleteIcon)`
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: auto;
`
: css`
margin-left: auto;
`}
${iconStyles}
`;
StyledDeleteIcon.defaultProps = { theme: Base };
const StyledHelpButton = styled(HelpButton)`
margin-inline-start: 8px;
`;
const StyledButtons = styled(Box)`
padding: 16px;
display: flex;
align-items: center;
gap: 10px;
position: absolute;
bottom: 0px;
width: 100%;
background: ${(props) => props.theme.filesPanels.sharing.backgroundButtons};
border-top: ${(props) => props.theme.filesPanels.sharing.borderTop};
`;
const StyledLink = styled(Link)`
float: ${({ theme }) =>
theme.interfaceDirection === "rtl" ? `left` : `right`};
`;
const ResetLink = styled(Link)`
float: ${({ theme }) =>
theme.interfaceDirection === "rtl" ? `right` : `left`};
padding: 0 16px;
margin-bottom: 16px;
font-size: 13px;
color: ${(props) => props.theme.createEditRoomDialog.commonParam.textColor};
font-style: normal;
line-height: 15px;
`;
StyledButtons.defaultProps = { theme: Base };
const StyledToggleButton = styled(ToggleButton)`
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
left: 8px;
`
: css`
right: 8px;
`}
margin-top: -4px;
`;
const StyledControlContainer = styled.div`
width: 17px;
height: 17px;
position: absolute;
cursor: pointer;
align-items: center;
justify-content: center;
z-index: 450;
@media ${mobile} {
display: flex;
top: -27px;
right: 10px;
left: unset;
}
`;
const StyledInviteLanguage = styled.div`
padding-left: 16px;
padding-right: 16px;
margin-top: -12px;
display: flex;
align-items: center;
justify-content: start;
height: 28px;
color: ${(props) =>
props.theme.createEditRoomDialog.commonParam.descriptionColor};
margin-bottom: 4px;
font-size: 13px;
font-style: normal;
font-weight: 600;
line-height: 20px;
.list-link {
margin-left: 4px;
color: ${(props) => props.theme.createEditRoomDialog.commonParam.textColor};
}
.invitation-language {
color: ${(props) =>
props.theme.createEditRoomDialog.commonParam.descriptionColor};
}
.language-combo-box {
.combo-button {
padding-left: 6px;
padding-right: 6px;
}
.combo-buttons_arrow-icon {
margin-left: 0px;
}
.combo-button_closed:not(:hover) .combo-button-label {
color: ${(props) =>
props.theme.createEditRoomDialog.commonParam.descriptionColor};
}
.combo-button_closed:not(:hover) .combo-buttons_arrow-icon {
svg {
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.commonParam.descriptionColor};
}
}
}
}
.language-combo-box-wrapper {
display: flex;
align-items: center;
gap: 2px;
}
`;
const StyledCrossIconMobile = styled(CrossIconMobile)`
width: 17px;
height: 17px;
z-index: 455;
path {
fill: ${(props) => props.theme.catalog.control.fill};
}
`;
StyledCrossIcon.defaultProps = { theme: Base };
export {
StyledBlock,
StyledHeading,
StyledInvitePanel,
StyledRow,
StyledSubHeader,
StyledInviteInput,
StyledInviteInputContainer,
StyledDropDown,
SearchItemText,
StyledEditInput,
StyledEditButton,
StyledCheckIcon,
StyledCrossIcon,
StyledHelpButton,
StyledDeleteIcon,
StyledButtons,
StyledLink,
ResetLink,
ScrollList,
StyledAccessSelector,
StyledToggleButton,
StyledDescription,
StyledInviteLanguage,
StyledControlContainer,
StyledCrossIconMobile,
StyledInviteUserBody,
};

View File

@ -0,0 +1,309 @@
// (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 { useEffect, useState, useMemo, useRef } from "react";
import { observer, inject } from "mobx-react";
import { withTranslation } from "react-i18next";
import { DeviceType } from "@docspace/shared/enums";
import { Backdrop } from "@docspace/shared/components/backdrop";
import { Aside } from "@docspace/shared/components/aside";
import { Button } from "@docspace/shared/components/button";
import { toastr } from "@docspace/shared/components/toast";
import { Portal } from "@docspace/shared/components/portal";
import { isDesktop, isMobile, size } from "@docspace/shared/utils";
import {
StyledBlock,
StyledHeading,
StyledInvitePanel,
StyledButtons,
StyledControlContainer,
StyledCrossIconMobile,
StyledSubHeader,
StyledToggleButton,
StyledDescription,
} from "./StyledInvitePanel";
import ItemsList from "./sub-components/ItemsList";
import InviteInput from "./sub-components/InviteInput";
import { Scrollbar } from "@docspace/shared/components/scrollbar";
import { IconButton } from "@docspace/shared/components/icon-button";
import ArrowPathReactSvgUrl from "PUBLIC_DIR/images/arrow.path.react.svg?url";
const TemplateAccessSettingsPanel = ({
t,
visible,
setIsVisible,
setInfoPanelIsMobileHidden,
currentDeviceType,
}) => {
const [isAvailable, setIsAvailable] = useState(false);
const [inviteItems, setInviteItems] = useState([]);
const [hasErrors, setHasErrors] = useState(false);
const [scrollAllPanelContent, setScrollAllPanelContent] = useState(false);
const [addUsersPanelVisible, setAddUsersPanelVisible] = useState(false);
const [isMobileView, setIsMobileView] = useState(isMobile());
const invitePanelBodyRef = useRef();
const zIndex = 311;
useEffect(() => {
const hasErrors = inviteItems.some((item) => !!item.errors?.length);
setHasErrors(hasErrors);
}, [inviteItems]);
useEffect(() => {
onCheckHeight();
window.addEventListener("resize", onCheckHeight);
return () => {
window.removeEventListener("resize", onCheckHeight);
window.removeEventListener("mousedown", onMouseDown);
};
}, []);
useEffect(() => {
isMobileView && window.addEventListener("mousedown", onMouseDown);
}, [isMobileView]);
const onMouseDown = (e) => {
if (e.target.id === "InvitePanelWrapper") onClose();
};
const onCheckHeight = () => {
setScrollAllPanelContent(!isDesktop());
setIsMobileView(isMobile());
};
const onAvailableChange = () => {
setIsAvailable(!isAvailable);
};
const onClose = () => {
setInfoPanelIsMobileHidden(false);
setIsVisible(false);
};
const onKeyPress = (e) =>
(e.key === "Esc" || e.key === "Escape") && onClose();
useEffect(() => {
document.addEventListener("keyup", onKeyPress);
return () => document.removeEventListener("keyup", onKeyPress);
});
const roomType = 2; //TODO: Templates
// const roomType = -1;
const hasInvitedUsers = !!inviteItems.length;
const bodyInvitePanel = useMemo(() => {
return (
<>
<StyledBlock noPadding>
<StyledSubHeader inline>
{t("Files:TemplateAvailable")}
<StyledToggleButton
className="invite-via-link"
isChecked={isAvailable}
onChange={onAvailableChange}
/>
</StyledSubHeader>
<StyledDescription>
{t("Files:TemplateAvailableDescription")}
</StyledDescription>
</StyledBlock>
<InviteInput
t={t}
onClose={onClose}
inviteItems={inviteItems}
setInviteItems={setInviteItems}
roomType={roomType}
addUsersPanelVisible={addUsersPanelVisible}
setAddUsersPanelVisible={setAddUsersPanelVisible}
isMobileView={isMobileView}
/>
<StyledSubHeader>{t("Files:AccessToTemplate")}</StyledSubHeader>
{hasInvitedUsers && (
<ItemsList
t={t}
inviteItems={inviteItems}
setInviteItems={setInviteItems}
setHasErrors={setHasErrors}
roomType={roomType}
scrollAllPanelContent={scrollAllPanelContent}
invitePanelBodyRef={invitePanelBodyRef}
isMobileView={isMobileView}
/>
)}
</>
);
}, [
t,
roomType,
onClose,
setHasErrors,
scrollAllPanelContent,
hasInvitedUsers,
invitePanelBodyRef,
]);
const invitePanelNode = (
<>
<StyledBlock
style={{
display: "flex",
alignItems: "center",
flexDirection: "row",
gap: 12,
}}
>
<IconButton
size={17}
iconName={ArrowPathReactSvgUrl}
className="sharing_panel-arrow"
onClick={() => console.log("onArrowClick")} //TODO: Templates
/>
<StyledHeading>{t("Files:AccessSettings")}</StyledHeading>
</StyledBlock>
{
<>
{scrollAllPanelContent ? (
<div className="invite-panel-body" ref={invitePanelBodyRef}>
<Scrollbar>{bodyInvitePanel}</Scrollbar>
</div>
) : (
bodyInvitePanel
)}
<StyledButtons>
<Button
className="send-invitation"
scale={true}
size={"normal"}
isDisabled={hasErrors || !hasInvitedUsers}
primary
onClick={() => console.log("onSave")} //TODO: Templates
label={t("Common:SaveButton")}
/>
<Button
className="cancel-button"
scale={true}
size={"normal"}
onClick={onClose}
label={t("Common:CancelButton")}
/>
</StyledButtons>
</>
}
</>
);
const invitePanelComponent = (
<StyledInvitePanel
id="InvitePanelWrapper"
hasInvitedUsers={hasInvitedUsers}
scrollAllPanelContent={scrollAllPanelContent}
addUsersPanelVisible={addUsersPanelVisible}
>
{isMobileView ? (
<div className="invite_panel">
<StyledControlContainer onClick={onClose}>
<StyledCrossIconMobile />
</StyledControlContainer>
{invitePanelNode}
</div>
) : (
<>
<Backdrop
onClick={onClose}
visible={visible}
isAside={true}
zIndex={currentDeviceType === DeviceType.mobile ? 11 : zIndex}
/>
<Aside
className="invite_panel"
visible={visible}
onClose={onClose}
withoutBodyScroll
zIndex={zIndex}
>
{invitePanelNode}
</Aside>
</>
)}
</StyledInvitePanel>
);
const renderPortalInvitePanel = () => {
const rootElement = document.getElementById("root");
return (
<Portal
element={invitePanelComponent}
appendTo={rootElement}
visible={visible}
/>
);
};
return currentDeviceType === DeviceType.mobile
? renderPortalInvitePanel()
: invitePanelComponent;
};
export default inject(
({ settingsStore, peopleStore, dialogsStore, infoPanelStore }) => {
const { theme, currentDeviceType } = settingsStore;
const { getUsersByQuery } = peopleStore.usersStore;
const { setIsMobileHidden: setInfoPanelIsMobileHidden } = infoPanelStore;
const { templateAccessSettingsVisible, setTemplateAccessSettingsVisible } =
dialogsStore;
return {
getUsersByQuery,
theme,
visible: templateAccessSettingsVisible,
setIsVisible: setTemplateAccessSettingsVisible,
setInfoPanelIsMobileHidden,
currentDeviceType,
};
},
)(
withTranslation([
"InviteDialog",
"SharingPanel",
"Translations",
"Common",
"InfoPanel",
"PeopleSelector",
])(observer(TemplateAccessSettingsPanel)),
);

View File

@ -0,0 +1,371 @@
// (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 debounce from "lodash.debounce";
import { inject, observer } from "mobx-react";
import { withTranslation } from "react-i18next";
import { useMemo, useState, useCallback, useEffect, useRef } from "react";
import { Avatar } from "@docspace/shared/components/avatar";
import { TextInput } from "@docspace/shared/components/text-input";
import { DropDownItem } from "@docspace/shared/components/drop-down-item";
import { toastr } from "@docspace/shared/components/toast";
import { Aside } from "@docspace/shared/components/aside";
import { Backdrop } from "@docspace/shared/components/backdrop";
import PeopleSelector from "@docspace/shared/selectors/People";
import Filter from "@docspace/shared/api/people/filter";
import { getMembersList } from "@docspace/shared/api/people";
import {
AccountsSearchArea,
EmployeeType,
RoomsType,
ShareAccessRights,
} from "@docspace/shared/enums";
import withCultureNames from "SRC_DIR/HOCs/withCultureNames";
import { checkIfAccessPaid } from "SRC_DIR/helpers";
import AddUsersPanel from "../../AddUsersPanel";
import { getTopFreeRole } from "../utils";
import {
StyledSubHeader,
StyledLink,
StyledInviteInput,
StyledInviteInputContainer,
StyledDropDown,
SearchItemText,
StyledDescription,
StyledCrossIcon,
} from "../StyledInvitePanel";
const minSearchValue = 1;
const PEOPLE_TAB_ID = "0";
const InviteInput = ({
t,
roomId = 254, //TODO: Templates
onClose,
roomType,
inviteItems,
setInviteItems,
addUsersPanelVisible,
setAddUsersPanelVisible,
isMobileView,
}) => {
const [inputValue, setInputValue] = useState("");
const [selectedTab, setSelectedTab] = useState("");
const [usersList, setUsersList] = useState([]);
const [isAddEmailPanelBlocked, setIsAddEmailPanelBlocked] = useState(true);
const [dropDownWidth, setDropDownWidth] = useState(0);
const searchRef = useRef();
console.log("roomId", roomId);
const isPublicRoomType = roomType === RoomsType.PublicRoom;
const dropDownMaxHeight = usersList.length > 5 ? { maxHeight: 240 } : {};
const foundUsers = usersList.map((user) => getItemContent(user));
useEffect(() => {
setTimeout(() => {
const width = searchRef?.current?.offsetWidth ?? 0;
if (width !== dropDownWidth) setDropDownWidth(width);
}, 0);
});
const searchByQuery = async (value) => {
console.log("searchByQuery");
return;
const query = value.trim();
if (query.length >= minSearchValue) {
const searchArea = isPublicRoomType
? AccountsSearchArea.People
: AccountsSearchArea.Any;
const filter = Filter.getFilterWithOutDisabledUser();
filter.role = [EmployeeType.Admin, EmployeeType.User];
filter.search = query;
const users = await getMembersList(searchArea, roomId, filter);
setUsersList(users.items);
if (users.total) setIsAddEmailPanelBlocked(false);
}
if (!query) {
setInputValue("");
setUsersList([]);
setIsAddEmailPanelBlocked(true);
}
};
const debouncedSearch = useCallback(
debounce((value) => searchByQuery(value), 300),
[],
);
const onChange = (e) => {
const value = e.target.value;
onChangeInput(value);
};
const onChangeInput = (value) => {
const clearValue = value.trim();
setInputValue(value);
if (clearValue.length < minSearchValue) {
setUsersList([]);
setIsAddEmailPanelBlocked(true);
return;
}
if (roomId !== -1) {
debouncedSearch(clearValue);
}
setIsAddEmailPanelBlocked(true);
};
const removeExist = (items) => {
const filtered = items.reduce((unique, o) => {
!unique.some((obj) =>
obj.isGroup ? obj.id === o.id : obj.email === o.email,
) && unique.push(o);
return unique;
}, []);
if (items.length > filtered.length) toastr.warning(t("UsersAlreadyAdded"));
return filtered;
};
const getItemContent = (item) => {
const {
avatar,
displayName,
name: groupName,
email,
id,
shared,
isGroup = false,
} = item;
const addUser = () => {
console.log("addUser");
if (item.isOwner || item.isAdmin)
item.access = ShareAccessRights.RoomManager;
// if (isGroup && checkIfAccessPaid(item.access)) {
// const topFreeRole = getTopFreeRole(t, roomType);
// item.access = topFreeRole.access;
// item.warning = t("GroupMaxAvailableRoleWarning", {
// role: topFreeRole.label,
// });
// }
const items = removeExist([item, ...inviteItems]);
setInviteItems(items);
setInputValue("");
setUsersList([]);
setIsAddEmailPanelBlocked(true);
};
return (
<DropDownItem
key={id}
onClick={addUser}
height={48}
heightTablet={48}
className="list-item"
>
<Avatar
size="min"
role="user"
source={avatar}
userName={groupName}
isGroup={isGroup}
/>
<div className="list-item_content">
<SearchItemText primary disabled={shared}>
{displayName || groupName}
</SearchItemText>
<SearchItemText>{email}</SearchItemText>
</div>
{shared && <SearchItemText info>{t("Common:Invited")}</SearchItemText>}
</DropDownItem>
);
};
const addItems = (users) => {
console.log("addItems", users);
const topFreeRole = getTopFreeRole(t, roomType);
users.forEach((u) => {
if (u.isGroup && checkIfAccessPaid(u.access)) {
u.access = topFreeRole.access;
u.warning = t("GroupMaxAvailableRoleWarning", {
role: topFreeRole.label,
});
}
});
const items = [...users, ...inviteItems];
const filtered = removeExist(items);
setInviteItems(filtered);
setInputValue("");
setUsersList([]);
};
const getSelectedTab = (item) => setSelectedTab(item);
const openUsersPanel = () => {
setInputValue("");
setAddUsersPanelVisible(true);
setIsAddEmailPanelBlocked(true);
};
const closeUsersPanel = () => {
setAddUsersPanelVisible(false);
};
const onClearInput = () => onChangeInput("");
const invitedUsers = useMemo(
() => inviteItems.map((item) => item.id),
[inviteItems],
);
const filter = new Filter();
filter.role = [EmployeeType.Admin, EmployeeType.User]; // 1(EmployeeType.User) - RoomAdmin | 3(EmployeeType.Admin) - DocSpaceAdmin
return (
<>
<StyledSubHeader>
{t("Files:AddUsersOrGroups")}
<StyledLink
className="link-list"
fontWeight="600"
type="action"
isHovered
onClick={openUsersPanel}
>
{t("Translations:ChooseFromList")}
</StyledLink>
</StyledSubHeader>
<StyledDescription>
{t("Files:AddUsersOrGroupsDescription")}
</StyledDescription>
<StyledInviteInputContainer>
<StyledInviteInput ref={searchRef} isShowCross={!!inputValue}>
<TextInput
className="invite-input"
scale
onChange={onChange}
placeholder={
roomId === -1
? t("InviteAccountSearchPlaceholder")
: t("InviteRoomSearchPlaceholder")
}
value={inputValue}
isAutoFocussed={true}
type="search"
withBorder={false}
/>
<div className="append" onClick={onClearInput}>
<StyledCrossIcon />
</div>
</StyledInviteInput>
{isAddEmailPanelBlocked ? (
<></>
) : (
<StyledDropDown
width={dropDownWidth}
isDefaultMode={false}
open
manualX="16px"
showDisabledItems
eventTypes="click"
withBackdrop={false}
zIndex={399}
{...dropDownMaxHeight}
>
{foundUsers}
</StyledDropDown>
)}
{addUsersPanelVisible && (
<AddUsersPanel
onParentPanelClose={onClose}
onClose={closeUsersPanel}
visible={addUsersPanelVisible}
tempDataItems={inviteItems}
setDataItems={addItems}
isMultiSelect
withoutBackground={isMobileView}
withBlur={!isMobileView}
roomId={roomId} // fixed groups request // need template id for correct working
withGroups={!isPublicRoomType}
withInfo
infoText={
selectedTab === PEOPLE_TAB_ID
? t("Files:AddUsersOrGroupsInfo")
: t("Files:AddUsersOrGroupsInfoGroups")
}
withInfoBadge
invitedUsers={invitedUsers}
disableDisabledUsers
filter={filter}
setActiveTabId={getSelectedTab}
isUsersList
/>
)}
</StyledInviteInputContainer>
</>
);
};
export default inject(({ dialogsStore }) => {
const { invitePanelOptions } = dialogsStore;
return {
roomId: invitePanelOptions.roomId,
};
})(
withCultureNames(
withTranslation(["InviteDialog", "Common", "Translations"])(
observer(InviteInput),
),
),
);

View File

@ -0,0 +1,267 @@
// (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 InfoEditReactSvgUrl from "PUBLIC_DIR/images/info.edit.react.svg?url";
import AtReactSvgUrl from "PUBLIC_DIR/images/@.react.svg?url";
import AlertSvgUrl from "PUBLIC_DIR/images/icons/12/alert.react.svg?url";
import { useState, useEffect } from "react";
import { Avatar } from "@docspace/shared/components/avatar";
import { Text } from "@docspace/shared/components/text";
import { parseAddresses } from "@docspace/shared/utils";
import { getAccessOptions } from "../utils";
import { getUserTypeLabel } from "@docspace/shared/utils/common";
import {
StyledEditInput,
StyledEditButton,
StyledCheckIcon,
StyledCrossIcon,
StyledHelpButton,
StyledDeleteIcon,
StyledInviteUserBody,
} from "../StyledInvitePanel";
import { filterGroupRoleOptions, filterUserRoleOptions } from "SRC_DIR/helpers";
const Item = ({
t,
item,
setInviteItems,
inviteItems,
changeInviteItem,
setHasErrors,
roomType,
isOwner,
standalone,
}) => {
const {
avatar,
displayName,
email,
id,
errors,
access,
isGroup,
name: groupName,
warning,
} = item;
const name = isGroup
? groupName
: !!avatar
? displayName !== ""
? displayName
: email
: email;
const source = !!avatar ? avatar : isGroup ? "" : AtReactSvgUrl;
const [edit, setEdit] = useState(false);
const [inputValue, setInputValue] = useState(name);
const [parseErrors, setParseErrors] = useState(errors);
console.log("roomType", roomType);
const accesses = getAccessOptions(
t,
roomType,
true,
true,
isOwner,
standalone,
);
const filteredAccesses = item.isGroup
? filterGroupRoleOptions(accesses)
: filterUserRoleOptions(accesses, item, true);
console.log("filteredAccesses", filteredAccesses);
const defaultAccess = filteredAccesses.find(
(option) => option.access === +access,
);
const getUserType = (item) => {
if (item.isOwner) return "owner";
if (item.isAdmin) return "admin";
if (item.isRoomAdmin) return "manager";
if (item.isCollaborator) return "collaborator";
return "user";
};
const type = getUserType(item);
const typeLabel = item?.isEmailInvite
? getUserTypeLabel(defaultAccess.type, t)
: (type === "user" && defaultAccess?.type !== type) ||
(defaultAccess?.type === "manager" && type !== "admin")
? getUserTypeLabel(defaultAccess.type, t)
: getUserTypeLabel(type, t);
const errorsInList = () => {
const hasErrors = inviteItems.some((item) => !!item.errors?.length);
setHasErrors(hasErrors);
};
const onEdit = (e) => {
if (e.detail === 2) {
setEdit(true);
}
};
const cancelEdit = (e) => {
setInputValue(name);
setEdit(false);
};
const saveEdit = (e) => {
const value = inputValue === "" ? name : inputValue;
setEdit(false);
validateValue(value);
};
const onKeyPress = (e) => {
if (edit) {
if (e.key === "Enter") {
saveEdit();
}
}
};
useEffect(() => {
document.addEventListener("keyup", onKeyPress);
return () => document.removeEventListener("keyup", onKeyPress);
});
const validateValue = (value) => {
const email = parseAddresses(value);
const parseErrors = email[0].parseErrors;
const errors = !!parseErrors.length ? parseErrors : [];
setParseErrors(errors);
changeInviteItem({ id, email: value, errors }).then(() => errorsInList());
};
const changeValue = (e) => {
const value = e.target.value.trim();
setInputValue(value);
};
const hasError = parseErrors && !!parseErrors.length;
const removeItem = () => {
const newItems = inviteItems.filter((item) => item.id !== id);
setInviteItems(newItems);
};
const selectItemAccess = (selected) => {
if (selected.key === "remove") return removeItem();
changeInviteItem({ id, access: selected.access });
};
const textProps = !!avatar || isGroup ? {} : { onClick: onEdit };
const displayBody = (
<>
<StyledInviteUserBody>
<Text {...textProps} truncate noSelect>
{inputValue}
</Text>
{!isGroup && (
<Text
className="label"
fontWeight={400}
fontSize="12px"
noSelect
color="#A3A9AE"
truncate
>
{`${typeLabel} | ${email}`}
</Text>
)}
</StyledInviteUserBody>
{hasError ? (
<>
<StyledHelpButton
iconName={InfoEditReactSvgUrl}
displayType="auto"
offsetRight={0}
tooltipContent={t("EmailErrorMessage")}
openOnClick={false}
size={16}
color="#F21C0E"
/>
<StyledDeleteIcon
className="delete-icon"
size="medium"
onClick={removeItem}
/>
</>
) : (
<>
{warning && (
<div className="warning">
<StyledHelpButton
tooltipContent={warning}
iconName={AlertSvgUrl}
/>
</div>
)}
</>
)}
</>
);
const okIcon = <StyledCheckIcon size="scale" />;
const cancelIcon = <StyledCrossIcon size="scale" />;
const editBody = (
<>
<StyledEditInput value={inputValue} onChange={changeValue} />
<StyledEditButton icon={okIcon} onClick={saveEdit} />
<StyledEditButton icon={cancelIcon} onClick={cancelEdit} />
</>
);
return (
<>
<Avatar
size="min"
role={type}
source={source}
isGroup={isGroup}
userName={groupName}
/>
{edit ? editBody : displayBody}
</>
);
};
export default Item;

View File

@ -0,0 +1,202 @@
// (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, { useState, useEffect, useRef, memo, useCallback } from "react";
import { inject, observer } from "mobx-react";
import { FixedSizeList as List } from "react-window";
import { CustomScrollbarsVirtualList } from "@docspace/shared/components/scrollbar";
import useResizeObserver from "use-resize-observer";
import Item from "./Item";
import { StyledRow, ScrollList } from "../StyledInvitePanel";
import { useTheme } from "styled-components";
const FOOTER_HEIGHT = 73;
const USER_ITEM_HEIGHT = 48;
const Row = memo(({ data, index, style }) => {
const {
inviteItems,
setInviteItems,
changeInviteItem,
t,
setHasErrors,
roomType,
isOwner,
setIsOpenItemAccess,
isMobileView,
standalone,
} = data;
if (inviteItems === undefined) return;
const item = inviteItems[index];
return (
<StyledRow
key={item.id}
style={style}
className="row-item"
hasWarning={!!item.warning}
>
<Item
t={t}
item={item}
setInviteItems={setInviteItems}
changeInviteItem={changeInviteItem}
inviteItems={inviteItems}
setHasErrors={setHasErrors}
roomType={roomType}
isOwner={isOwner}
setIsOpenItemAccess={setIsOpenItemAccess}
isMobileView={isMobileView}
standalone={standalone}
/>
</StyledRow>
);
});
const ItemsList = ({
t,
setInviteItems,
inviteItems,
changeInviteItem,
setHasErrors,
roomType,
isOwner,
externalLinksVisible,
scrollAllPanelContent,
invitePanelBodyRef,
isMobileView,
standalone,
}) => {
const [bodyHeight, setBodyHeight] = useState(0);
const [offsetTop, setOffsetTop] = useState(0);
const [isTotalListHeight, setIsTotalListHeight] = useState(false);
const [isOpenItemAccess, setIsOpenItemAccess] = useState(false);
const bodyRef = useRef();
const { height } = useResizeObserver({ ref: bodyRef });
const { interfaceDirection } = useTheme();
const onBodyResize = useCallback(() => {
const scrollHeight = bodyRef?.current?.firstChild.scrollHeight;
const heightList = height ? height : bodyRef.current.offsetHeight;
const totalHeightItems = inviteItems.length * USER_ITEM_HEIGHT;
const listAreaHeight = heightList;
const heightBody = invitePanelBodyRef?.current?.clientHeight;
const fullHeightList = heightBody - bodyRef.current.offsetTop;
const heightWitchOpenItemAccess = Math.max(scrollHeight, fullHeightList);
const calculatedHeight = scrollAllPanelContent
? Math.max(
totalHeightItems,
listAreaHeight,
isOpenItemAccess ? heightWitchOpenItemAccess : 0,
)
: heightList - FOOTER_HEIGHT;
const finalHeight = scrollAllPanelContent
? isOpenItemAccess
? calculatedHeight
: totalHeightItems
: calculatedHeight;
setBodyHeight(finalHeight);
setOffsetTop(bodyRef.current.offsetTop);
if (scrollAllPanelContent && totalHeightItems && listAreaHeight)
setIsTotalListHeight(
totalHeightItems >= listAreaHeight && totalHeightItems >= scrollHeight,
);
}, [
height,
bodyRef?.current?.offsetHeight,
inviteItems.length,
scrollAllPanelContent,
isOpenItemAccess,
]);
useEffect(() => {
onBodyResize();
}, [
bodyRef.current,
externalLinksVisible,
height,
inviteItems.length,
scrollAllPanelContent,
isOpenItemAccess,
]);
const overflowStyle = scrollAllPanelContent ? "hidden" : "scroll";
const willChangeStyle =
isMobileView && isOpenItemAccess ? "auto" : "transform";
return (
<ScrollList
offsetTop={offsetTop}
ref={bodyRef}
scrollAllPanelContent={scrollAllPanelContent}
isTotalListHeight={isTotalListHeight}
>
<List
style={{ overflow: overflowStyle, willChange: willChangeStyle }}
direction={interfaceDirection}
height={bodyHeight}
width="auto"
itemCount={inviteItems.length}
itemSize={USER_ITEM_HEIGHT}
itemData={{
inviteItems,
setInviteItems,
changeInviteItem,
setHasErrors,
roomType,
isOwner,
setIsOpenItemAccess,
isMobileView,
t,
standalone,
}}
outerElementType={!scrollAllPanelContent && CustomScrollbarsVirtualList}
>
{Row}
</List>
</ScrollList>
);
};
export default inject(({ userStore, dialogsStore, settingsStore }) => {
const { changeInviteItem } = dialogsStore;
const { isOwner } = userStore.user;
const { standalone } = settingsStore;
return {
changeInviteItem,
isOwner,
standalone,
};
})(observer(ItemsList));

View File

@ -0,0 +1,215 @@
// (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 {
ShareAccessRights,
RoomsType,
EmployeeType,
} from "@docspace/shared/enums";
import { checkIfAccessPaid } from "SRC_DIR/helpers";
export const getAccessOptions = (
t,
roomType = RoomsType.CustomRoom,
withRemove = false,
withSeparator = false,
isOwner = false,
standalone = false,
) => {
let options = [];
const accesses = {
docSpaceAdmin: {
key: "docSpaceAdmin",
label: t("Common:DocSpaceAdmin"),
description: t("Translations:RoleDocSpaceAdminDescription"),
...(!standalone && { quota: t("Common:Paid") }),
color: "#EDC409",
access:
roomType === -1 ? EmployeeType.Admin : ShareAccessRights.FullAccess,
type: "admin",
},
roomAdmin: {
key: "roomAdmin",
label: t("Common:RoomAdmin"),
description: t("Translations:RoleRoomAdminDescription"),
...(!standalone && { quota: t("Common:Paid") }),
color: "#EDC409",
access:
roomType === -1 ? EmployeeType.User : ShareAccessRights.RoomManager,
type: "manager",
},
collaborator: {
key: "collaborator",
label: t("Common:PowerUser"),
description: t("Translations:RolePowerUserDescription"),
...(!standalone && { quota: t("Common:Paid") }),
color: "#EDC409",
access:
roomType === -1
? EmployeeType.Collaborator
: ShareAccessRights.Collaborator,
type: "collaborator",
},
user: {
key: "user",
label: t("Common:User"),
description: t("Translations:RoleUserDescription"),
access: EmployeeType.Guest,
type: "user",
},
editor: {
key: "editor",
label: t("Common:Editor"),
description: t("Translations:RoleEditorDescription"),
access: ShareAccessRights.Editing,
type: "user",
},
formFiller: {
key: "formFiller",
label: t("Translations:RoleFormFiller"),
description: t("Translations:RoleFormFillerDescription"),
access: ShareAccessRights.FormFilling,
type: "user",
},
reviewer: {
key: "reviewer",
label: t("Translations:RoleReviewer"),
description: t("Translations:RoleReviewerDescription"),
access: ShareAccessRights.Review,
type: "user",
},
commentator: {
key: "commentator",
label: t("Translations:RoleCommentator"),
description: t("Translations:RoleCommentatorDescription"),
access: ShareAccessRights.Comment,
type: "user",
},
viewer: {
key: "viewer",
label: t("Translations:RoleViewer"),
description: t("Translations:RoleViewerDescription"),
access: ShareAccessRights.ReadOnly,
type: "user",
},
};
switch (roomType) {
case RoomsType.FillingFormsRoom:
options = [
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.formFiller,
accesses.viewer,
];
break;
case RoomsType.EditingRoom:
options = [
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.editor,
accesses.viewer,
];
break;
case RoomsType.ReviewRoom:
options = [
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.reviewer,
accesses.commentator,
accesses.viewer,
];
break;
case RoomsType.ReadOnlyRoom:
options = [
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.viewer,
];
break;
case RoomsType.CustomRoom:
options = [
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.editor,
accesses.formFiller,
accesses.reviewer,
accesses.commentator,
accesses.viewer,
];
break;
case RoomsType.PublicRoom:
options = [accesses.roomAdmin, accesses.collaborator];
break;
case RoomsType.FormRoom:
options = [
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.viewer,
accesses.formFiller,
];
break;
case -1:
if (isOwner) options.push(accesses.docSpaceAdmin);
options = [
...options,
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.user,
];
break;
}
const removeOption = [
{
key: "s2",
isSeparator: true,
},
{
key: "remove",
label: t("Common:Remove"),
},
];
return withRemove ? [...options, ...removeOption] : options;
};
export const getTopFreeRole = (t, roomType) => {
const accesses = getAccessOptions(t, roomType);
const freeAccesses = accesses.filter(
(item) => !checkIfAccessPaid(item.access) && item.key !== "s1",
);
return freeAccesses[0];
};

View File

@ -33,6 +33,7 @@ import UploadPanel from "./UploadPanel";
import HotkeyPanel from "./HotkeysPanel";
import InvitePanel from "./InvitePanel";
import EditLinkPanel from "./EditLinkPanel";
import TemplateAccessSettingsPanel from "./TemplateAccessSettingsPanel";
export {
AddUsersPanel,
@ -44,4 +45,5 @@ export {
HotkeyPanel,
InvitePanel,
EditLinkPanel,
TemplateAccessSettingsPanel,
};

View File

@ -682,6 +682,10 @@ class ContextOptionsStore {
this.filesActionsStore.onCreateRoomFromTemplate({ ...item, isEdit: true });
};
onOpenTemplateAccessOptions = () => {
this.dialogsStore.setTemplateAccessSettingsVisible(true);
};
// onLoadLinks = async (t, item) => {
// const promise = new Promise(async (resolve, reject) => {
// let linksArray = [];
@ -1339,6 +1343,14 @@ class ContextOptionsStore {
onClick: () => this.onEditRoomTemplate(item),
disabled: false,
},
{
id: "option_access-settings",
key: "access-settings",
label: t("AccessSettings"),
icon: PersonReactSvgUrl,
onClick: () => this.onOpenTemplateAccessOptions(),
disabled: false,
},
{
id: "option_invite-users-to-room",
key: "invite-users-to-room",

View File

@ -116,6 +116,7 @@ class DialogsStore {
shareFolderDialogVisible = false;
cancelUploadDialogVisible = false;
createRoomTemplateDialogVisible = false;
templateAccessSettingsVisible = true;
selectFileFormRoomFilterParam = FilesSelectorFilterTypes.DOCX;
@ -523,6 +524,10 @@ class DialogsStore {
setCreateRoomTemplateDialogVisible = (visible) => {
this.createRoomTemplateDialogVisible = visible;
};
setTemplateAccessSettingsVisible = (isVisible) => {
this.templateAccessSettingsVisible = isVisible;
};
}
export default DialogsStore;

View File

@ -438,6 +438,11 @@ const StyledInfo = styled.div`
.text {
color: ${(props) => props.theme.selector.info.color};
}
.selector-info-text-wrapper {
display: flex;
gap: 8px;
}
`;
StyledSelector.defaultProps = { theme: Base };

View File

@ -134,6 +134,7 @@ const Selector = ({
withInfo,
infoText,
withInfoBadge,
}: SelectorProps) => {
const [footerVisible, setFooterVisible] = React.useState<boolean>(false);
const [isSearch, setIsSearch] = React.useState<boolean>(false);
@ -530,6 +531,7 @@ const Selector = ({
? {
withInfo,
infoText,
withInfoBadge,
}
: {};

View File

@ -295,8 +295,12 @@ export type TSelectorFooterCheckbox = TSelectorCheckbox & {
};
export type TSelectorInfo =
| { withInfo: true; infoText: string }
| { withInfo?: undefined; infoText?: undefined };
| { withInfo: true; infoText: string; withInfoBadge?: boolean }
| {
withInfo?: undefined;
infoText?: undefined;
withInfoBadge?: undefined;
};
export type SelectorProps = TSelectorHeader &
TSelectorInfo &
@ -377,6 +381,7 @@ export type BodyProps = TSelectorBreadCrumbs &
withFooterInput?: boolean;
withFooterCheckbox?: boolean;
descriptionText?: string;
withInfoBadge?: boolean;
};
export type FooterProps = TSelectorFooterSubmitButton &

View File

@ -101,6 +101,7 @@ const Body = ({
withInfo,
infoText,
withInfoBadge,
}: BodyProps) => {
const [bodyHeight, setBodyHeight] = React.useState(0);
@ -212,7 +213,11 @@ const Body = ({
) : null}
{withInfo && !isLoading && (
<Info withInfo={withInfo} infoText={infoText} />
<Info
withInfo={withInfo}
infoText={infoText}
withInfoBadge={withInfoBadge}
/>
)}
{isLoading ? (

View File

@ -26,17 +26,27 @@
import React from "react";
import { ReactSVG } from "react-svg";
import InfoIcon from "PUBLIC_DIR/images/info.outline.react.svg?url";
import { Text } from "../../text";
import { TSelectorInfo } from "../Selector.types";
import { StyledInfo } from "../Selector.styled";
export const Info = ({ infoText }: TSelectorInfo) => {
export const Info = ({ infoText, withInfoBadge }: TSelectorInfo) => {
return (
<StyledInfo id="selector-info-text">
<Text fontSize="12px" fontWeight={400} lineHeight="16px" className="text">
{infoText}
</Text>
<div className="selector-info-text-wrapper">
{withInfoBadge && <ReactSVG src={InfoIcon} />}
<Text
fontSize="12px"
fontWeight={400}
lineHeight="16px"
className="text"
>
{infoText}
</Text>
</div>
</StyledInfo>
);
};