Web: Files: Templates: fixed TemplateAccessSettingsPanel

This commit is contained in:
Nikita Gopienko 2024-05-14 14:44:33 +03:00
parent f18602ac44
commit f141269ec2
6 changed files with 138 additions and 407 deletions

View File

@ -143,11 +143,20 @@ const toListItem = (
} as TSelectorItem;
};
type AddUsersPanelProps = {
export type TSelectorInfo =
| { withInfo: true; infoText: string; withInfoBadge?: boolean }
| {
withInfo?: undefined;
infoText?: undefined;
withInfoBadge?: undefined;
};
type AddUsersPanelProps = TSelectorInfo & {
isEncrypted: boolean;
defaultAccess: ShareAccessRights;
onClose: () => void;
onParentPanelClose: () => void;
setActiveTabId: (tab: string) => void;
setDataItems: (items: TSelectorItem[]) => void;
@ -189,6 +198,9 @@ const AddUsersPanel = ({
invitedUsers,
disableDisabledUsers,
infoText,
withInfo,
setActiveTabId: setActiveTabIdProp,
}: AddUsersPanelProps) => {
const theme = useTheme();
const { t } = useTranslation([
@ -301,10 +313,14 @@ const AddUsersPanel = ({
(access) => access.access === accessRight,
)[0];
const changeActiveTab = useCallback((tab: number | string) => {
setActiveTabId(`${tab}`);
isFirstLoad.current = true;
}, []);
const changeActiveTab = useCallback(
(tab: number | string) => {
setActiveTabIdProp(`${tab}`);
setActiveTabId(`${tab}`);
isFirstLoad.current = true;
},
[setActiveTabIdProp],
);
const onSearch = useCallback(
(value: string, callback?: Function) => {
@ -565,6 +581,8 @@ const AddUsersPanel = ({
isLoading={isLoading}
searchLoader={<SearchLoader />}
isSearchLoading={isInit}
infoText={infoText}
withInfo={withInfo}
rowLoader={
<RowLoader
isUser

View File

@ -26,20 +26,14 @@
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";
@ -210,6 +204,17 @@ const StyledRow = styled.div`
.combo-buttons_expander-icon path {
fill: ${(props) => props.theme.text.disableColor};
}
.remove-icon {
cursor: pointer;
margin-inline-start: auto;
svg {
path {
fill: ${(props) => props.theme.text.disableColor};
}
}
}
`;
const StyledInviteInput = styled.div`
@ -266,21 +271,6 @@ const StyledInviteInput = styled.div`
}
`;
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;
@ -350,12 +340,6 @@ const SearchItemText = styled(Text)`
SearchItemText.defaultProps = { theme: Base };
const StyledEditButton = styled(Button)`
width: 32px;
height: 32px;
padding: 0px;
`;
const iconStyles = css`
${commonIconsStyles}
path {
@ -366,37 +350,12 @@ const iconStyles = css`
}
`;
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;
@ -415,17 +374,6 @@ const StyledLink = styled(Link)`
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)`
@ -459,60 +407,7 @@ const StyledControlContainer = styled.div`
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;
@ -522,6 +417,32 @@ const StyledCrossIconMobile = styled(CrossIconMobile)`
}
`;
const StyledBody = styled.div`
display: contents;
${({ isDisabled, theme }) =>
isDisabled
? css`
.invite-input-text {
pointer-events: none;
cursor: default;
color: ${theme.text.disableColor};
}
.invite-input-avatar {
opacity: 0.5;
}
.invite-input {
box-shadow: unset !important;
}
`
: css`
.invite-input-text {
color: ${theme.text.color};
}
`};
`;
StyledCrossIcon.defaultProps = { theme: Base };
export {
StyledBlock,
@ -533,21 +454,14 @@ export {
StyledInviteInputContainer,
StyledDropDown,
SearchItemText,
StyledEditInput,
StyledEditButton,
StyledCheckIcon,
StyledCrossIcon,
StyledHelpButton,
StyledDeleteIcon,
StyledButtons,
StyledLink,
ResetLink,
ScrollList,
StyledAccessSelector,
StyledToggleButton,
StyledDescription,
StyledInviteLanguage,
StyledControlContainer,
StyledCrossIconMobile,
StyledInviteUserBody,
StyledBody,
};

View File

@ -45,6 +45,7 @@ import {
StyledSubHeader,
StyledToggleButton,
StyledDescription,
StyledBody,
} from "./StyledInvitePanel";
import ItemsList from "./sub-components/ItemsList";
@ -69,8 +70,6 @@ const TemplateAccessSettingsPanel = ({
const [addUsersPanelVisible, setAddUsersPanelVisible] = useState(false);
const [isMobileView, setIsMobileView] = useState(isMobile());
const invitePanelBodyRef = useRef();
const zIndex = 311;
useEffect(() => {
@ -139,29 +138,31 @@ const TemplateAccessSettingsPanel = ({
{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
<StyledBody isDisabled={isAvailable}>
<InviteInput
t={t}
onClose={onClose}
inviteItems={inviteItems}
setInviteItems={setInviteItems}
setHasErrors={setHasErrors}
roomType={roomType}
scrollAllPanelContent={scrollAllPanelContent}
invitePanelBodyRef={invitePanelBodyRef}
addUsersPanelVisible={addUsersPanelVisible}
setAddUsersPanelVisible={setAddUsersPanelVisible}
isMobileView={isMobileView}
isDisabled={isAvailable}
/>
)}
<StyledSubHeader className="invite-input-text">
{t("Files:AccessToTemplate")}
</StyledSubHeader>
{hasInvitedUsers && (
<ItemsList
t={t}
inviteItems={inviteItems}
setInviteItems={setInviteItems}
scrollAllPanelContent={scrollAllPanelContent}
isDisabled={isAvailable}
/>
)}
</StyledBody>
</>
);
}, [
@ -171,7 +172,6 @@ const TemplateAccessSettingsPanel = ({
setHasErrors,
scrollAllPanelContent,
hasInvitedUsers,
invitePanelBodyRef,
]);
const invitePanelNode = (
@ -195,7 +195,7 @@ const TemplateAccessSettingsPanel = ({
{
<>
{scrollAllPanelContent ? (
<div className="invite-panel-body" ref={invitePanelBodyRef}>
<div className="invite-panel-body">
<Scrollbar>{bodyInvitePanel}</Scrollbar>
</div>
) : (

View File

@ -48,7 +48,7 @@ import withCultureNames from "SRC_DIR/HOCs/withCultureNames";
import { checkIfAccessPaid } from "SRC_DIR/helpers";
import AddUsersPanel from "../../AddUsersPanel";
import { getTopFreeRole } from "../utils";
import { getAccessOptions, getTopFreeRole } from "../utils";
import {
StyledSubHeader,
@ -60,13 +60,14 @@ import {
StyledDescription,
StyledCrossIcon,
} from "../StyledInvitePanel";
import { getDefaultAccessUser } from "@docspace/shared/utils/getDefaultAccessUser";
const minSearchValue = 1;
const PEOPLE_TAB_ID = "0";
const InviteInput = ({
t,
roomId = 254, //TODO: Templates
roomId = 281, //TODO: Templates
onClose,
roomType,
inviteItems,
@ -74,9 +75,12 @@ const InviteInput = ({
addUsersPanelVisible,
setAddUsersPanelVisible,
isMobileView,
isOwner,
standalone,
isDisabled,
}) => {
const [inputValue, setInputValue] = useState("");
const [selectedTab, setSelectedTab] = useState("");
const [selectedTab, setSelectedTab] = useState(PEOPLE_TAB_ID);
const [usersList, setUsersList] = useState([]);
const [isAddEmailPanelBlocked, setIsAddEmailPanelBlocked] = useState(true);
const [dropDownWidth, setDropDownWidth] = useState(0);
@ -267,13 +271,22 @@ const InviteInput = ({
const filter = new Filter();
filter.role = [EmployeeType.Admin, EmployeeType.User]; // 1(EmployeeType.User) - RoomAdmin | 3(EmployeeType.Admin) - DocSpaceAdmin
const accessOptions = getAccessOptions(
t,
roomType,
false,
true,
isOwner,
standalone,
);
return (
<>
<StyledSubHeader>
<StyledSubHeader className="invite-input-text">
{t("Files:AddUsersOrGroups")}
<StyledLink
className="link-list"
className="link-list invite-input-text"
fontWeight="600"
type="action"
isHovered
@ -301,6 +314,7 @@ const InviteInput = ({
isAutoFocussed={true}
type="search"
withBorder={false}
isDisabled={isDisabled}
/>
<div className="append" onClick={onClearInput}>
@ -331,6 +345,7 @@ const InviteInput = ({
onClose={closeUsersPanel}
visible={addUsersPanelVisible}
tempDataItems={inviteItems}
accessOptions={accessOptions}
setDataItems={addItems}
isMultiSelect
withoutBackground={isMobileView}
@ -349,6 +364,7 @@ const InviteInput = ({
filter={filter}
setActiveTabId={getSelectedTab}
isUsersList
defaultAccess={getDefaultAccessUser(roomType)}
/>
)}
</StyledInviteInputContainer>

View File

@ -24,50 +24,16 @@
// 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 RemoveReactSvgUrl from "PUBLIC_DIR/images/remove.react.svg?url";
import { ReactSVG } from "react-svg";
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 { StyledInviteUserBody } from "../StyledInvitePanel";
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 Item = ({ t, item, setInviteItems, inviteItems, isDisabled }) => {
const { avatar, displayName, email, id, isGroup, name: groupName } = item;
const name = isGroup
? groupName
@ -78,30 +44,6 @@ const Item = ({
: 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";
@ -111,86 +53,29 @@ const Item = ({
};
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 typeLabel = getUserTypeLabel(type, t);
const removeItem = () => {
const newItems = inviteItems.filter((item) => item.id !== id);
setInviteItems(newItems);
};
const selectItemAccess = (selected) => {
if (selected.key === "remove") return removeItem();
// const canDelete = !isOwner; //TODO: Templates
const canDelete = isDisabled ? false : true; //TODO: Templates
changeInviteItem({ id, access: selected.access });
};
const textProps = !!avatar || isGroup ? {} : { onClick: onEdit };
const displayBody = (
return (
<>
<Avatar
size="min"
role={type}
source={source}
isGroup={isGroup}
userName={groupName}
className="invite-input-avatar"
/>
<StyledInviteUserBody>
<Text {...textProps} truncate noSelect>
{inputValue}
<Text truncate noSelect className="invite-input-text">
{name}
</Text>
{!isGroup && (
@ -206,62 +91,15 @@ const Item = ({
</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>
)}
</>
{canDelete && (
<ReactSVG
className="remove-icon"
src={RemoveReactSvgUrl}
onClick={removeItem}
/>
)}
</>
);
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

@ -24,8 +24,7 @@
// 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 { useState, useEffect, useRef, memo, useCallback } from "react";
import { FixedSizeList as List } from "react-window";
import { CustomScrollbarsVirtualList } from "@docspace/shared/components/scrollbar";
import useResizeObserver from "use-resize-observer";
@ -38,18 +37,7 @@ 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;
const { inviteItems, setInviteItems, t, isDisabled } = data;
if (inviteItems === undefined) return;
@ -66,14 +54,8 @@ const Row = memo(({ data, index, style }) => {
t={t}
item={item}
setInviteItems={setInviteItems}
changeInviteItem={changeInviteItem}
inviteItems={inviteItems}
setHasErrors={setHasErrors}
roomType={roomType}
isOwner={isOwner}
setIsOpenItemAccess={setIsOpenItemAccess}
isMobileView={isMobileView}
standalone={standalone}
isDisabled={isDisabled}
/>
</StyledRow>
);
@ -83,20 +65,13 @@ const ItemsList = ({
t,
setInviteItems,
inviteItems,
changeInviteItem,
setHasErrors,
roomType,
isOwner,
externalLinksVisible,
scrollAllPanelContent,
invitePanelBodyRef,
isMobileView,
standalone,
isDisabled,
}) => {
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();
@ -106,22 +81,13 @@ const ItemsList = ({
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,
)
? Math.max(totalHeightItems, listAreaHeight, 0)
: heightList - FOOTER_HEIGHT;
const finalHeight = scrollAllPanelContent
? isOpenItemAccess
? calculatedHeight
: totalHeightItems
? totalHeightItems
: calculatedHeight;
setBodyHeight(finalHeight);
@ -136,7 +102,6 @@ const ItemsList = ({
bodyRef?.current?.offsetHeight,
inviteItems.length,
scrollAllPanelContent,
isOpenItemAccess,
]);
useEffect(() => {
@ -147,14 +112,10 @@ const ItemsList = ({
height,
inviteItems.length,
scrollAllPanelContent,
isOpenItemAccess,
]);
const overflowStyle = scrollAllPanelContent ? "hidden" : "scroll";
const willChangeStyle =
isMobileView && isOpenItemAccess ? "auto" : "transform";
return (
<ScrollList
offsetTop={offsetTop}
@ -163,23 +124,17 @@ const ItemsList = ({
isTotalListHeight={isTotalListHeight}
>
<List
style={{ overflow: overflowStyle, willChange: willChangeStyle }}
style={{ overflow: overflowStyle, willChange: "transform" }}
direction={interfaceDirection}
height={bodyHeight}
width="auto"
itemCount={inviteItems.length}
itemSize={USER_ITEM_HEIGHT}
itemData={{
t,
inviteItems,
setInviteItems,
changeInviteItem,
setHasErrors,
roomType,
isOwner,
setIsOpenItemAccess,
isMobileView,
t,
standalone,
isDisabled,
}}
outerElementType={!scrollAllPanelContent && CustomScrollbarsVirtualList}
>
@ -189,14 +144,4 @@ const ItemsList = ({
);
};
export default inject(({ userStore, dialogsStore, settingsStore }) => {
const { changeInviteItem } = dialogsStore;
const { isOwner } = userStore.user;
const { standalone } = settingsStore;
return {
changeInviteItem,
isOwner,
standalone,
};
})(observer(ItemsList));
export default ItemsList;