diff --git a/packages/client/src/components/panels/EmbeddingPanel/index.tsx b/packages/client/src/components/panels/EmbeddingPanel/index.tsx index 07d6332655..d5a0b22b3d 100644 --- a/packages/client/src/components/panels/EmbeddingPanel/index.tsx +++ b/packages/client/src/components/panels/EmbeddingPanel/index.tsx @@ -44,9 +44,6 @@ import { ComboBox, TOption } from "@docspace/shared/components/combobox"; import { TData } from "@docspace/shared/components/toast/Toast.type"; import { TTranslation } from "@docspace/shared/types"; import { TColorScheme, TTheme } from "@docspace/shared/themes"; - -import { SettingsStore } from "@docspace/shared/store/SettingsStore"; -import { UserStore } from "@docspace/shared/store/UserStore"; import { ModalDialog, ModalDialogType, @@ -65,8 +62,6 @@ import { StyledModalDialog, StyledBody } from "./StyledEmbeddingPanel"; import { DisplayBlock } from "./sub-components/DisplayBlock"; import { CheckboxElement } from "./sub-components/CheckboxElement"; -import PublicRoomStore from "../../../store/PublicRoomStore"; -import DialogsStore from "../../../store/DialogsStore"; type LinkParamsLinkShareToType = { denyDownload: boolean; diff --git a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/index.js b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/index.js index ab938c824e..1f444c33a3 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/index.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/index.js @@ -29,7 +29,7 @@ import { inject, observer } from "mobx-react"; import { withTranslation } from "react-i18next"; import { toastr } from "@docspace/shared/components/toast"; -import { RoomsType, ShareAccessRights } from "@docspace/shared/enums"; +import { RoomsType } from "@docspace/shared/enums"; import { LINKS_LIMIT_COUNT } from "@docspace/shared/constants"; import InfoPanelViewLoader from "@docspace/shared/skeletons/info-panel/body"; import MembersHelper from "../../helpers/MembersHelper"; @@ -182,6 +182,8 @@ const Members = ({ key="general-link" link={primaryLink} setIsScrollLocked={setIsScrollLocked} + isShareLink + isPrimaryLink />, ); } @@ -193,6 +195,7 @@ const Members = ({ link={link} key={link?.sharedTo?.id} setIsScrollLocked={setIsScrollLocked} + isShareLink />, ); }); @@ -202,6 +205,7 @@ const Members = ({ key="create-additional-link" className="additional-link" onClick={onAddNewLink} + isShareLink >
diff --git a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/LinkRow.js b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/LinkRow.tsx similarity index 50% rename from packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/LinkRow.js rename to packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/LinkRow.tsx index 6501d852f7..e166b12cbe 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/LinkRow.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/LinkRow.tsx @@ -24,70 +24,77 @@ // 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 } from "react"; import { observer, inject } from "mobx-react"; import { withTranslation } from "react-i18next"; import copy from "copy-to-clipboard"; -import { Avatar } from "@docspace/shared/components/avatar"; -import { Link } from "@docspace/shared/components/link"; -import { Text } from "@docspace/shared/components/text"; -import { IconButton } from "@docspace/shared/components/icon-button"; -import { ContextMenuButton } from "@docspace/shared/components/context-menu-button"; +import moment from "moment"; +import LinkRowComponent from "@docspace/shared/components/share/sub-components/LinkRow"; import { toastr } from "@docspace/shared/components/toast"; -import CopyReactSvgUrl from "PUBLIC_DIR/images/copy.react.svg?url"; -import LinkReactSvgUrl from "PUBLIC_DIR/images/tablet-link.react.svg?url"; import SettingsReactSvgUrl from "PUBLIC_DIR/images/catalog.settings.react.svg?url"; -import ShareReactSvgUrl from "PUBLIC_DIR/images/share.react.svg?url"; import CodeReactSvgUrl from "PUBLIC_DIR/images/code.react.svg?url"; import CopyToReactSvgUrl from "PUBLIC_DIR/images/copyTo.react.svg?url"; import OutlineReactSvgUrl from "PUBLIC_DIR/images/outline-true.react.svg?url"; import LockedReactSvgUrl from "PUBLIC_DIR/images/locked.react.svg?url"; -import LoadedReactSvgUrl from "PUBLIC_DIR/images/loaded.react.svg?url"; import TrashReactSvgUrl from "PUBLIC_DIR/images/trash.react.svg?url"; -import ClockReactSvg from "PUBLIC_DIR/images/clock.react.svg"; -import moment from "moment-timezone"; import { RoomsType } from "@docspace/shared/enums"; +import { TTranslation } from "@docspace/shared/types"; +import { TFileLink } from "@docspace/shared/api/files/types"; +import { useState } from "react"; +import { TOption } from "@docspace/shared/components/combobox"; -import { StyledLinkRow } from "./Styled"; +type LinkRowProps = { + t: TTranslation; + link: TFileLink; + roomId: string | number; + setLinkParams: (linkParams: { + roomId: number | string; + isEdit?: boolean; + link: TFileLink; + isPublic?: boolean; + isFormRoom?: boolean; + }) => void; + setEditLinkPanelIsVisible: (value: boolean) => void; + setDeleteLinkDialogVisible: (value: boolean) => void; + setEmbeddingPanelData: (value: { + visible: boolean; + itemId?: string | number; + }) => void; -const LinkRow = (props) => { + isArchiveFolder: boolean; + setIsScrollLocked: (isScrollLocked: boolean) => void; + isPublicRoomType: boolean; + isFormRoom: boolean; + isPrimaryLink: boolean; +}; + +const LinkRow = (props: LinkRowProps) => { const { t, link, roomId, setLinkParams, - setExternalLink, - editExternalLink, setEditLinkPanelIsVisible, setDeleteLinkDialogVisible, setEmbeddingPanelData, isArchiveFolder, - theme, setIsScrollLocked, isPublicRoomType, isFormRoom, - ...rest + isPrimaryLink, + editExternalLink, + setExternalLink, } = props; - const [isLoading, setIsLoading] = useState(false); - - const { - title, - shareLink, - password, - disabled, - expirationDate, - isExpired, - primary, - } = link.sharedTo; + const { shareLink, password, isExpired, primary } = link.sharedTo; const isLocked = !!password; - const expiryDate = !!expirationDate; - const date = moment(expirationDate).tz(window.timezone).format("LLL"); + const isDisabled = isExpired; - const tooltipContent = isExpired - ? t("Translations:LinkHasExpiredAndHasBeenDisabled") - : t("Translations:PublicRoomLinkValidTime", { date }); + const [loadingLinks, setLoadingLinks] = useState<(string | number)[]>([]); + + const onCloseContextMenu = () => { + setIsScrollLocked(false); + }; const onEditLink = () => { setEditLinkPanelIsVisible(true); @@ -101,33 +108,11 @@ const LinkRow = (props) => { onCloseContextMenu(); }; - // const onDisableLink = () => { - // if (isExpired) { - // setEditLinkPanelIsVisible(true); - // setLinkParams({ isEdit: true, link, roomId, isPublic: isPublicRoomType, isFormRoom }); - // return; - // } - - // setIsLoading(true); - - // const newLink = JSON.parse(JSON.stringify(link)); - // newLink.sharedTo.disabled = !newLink.sharedTo.disabled; - - // editExternalLink(roomId, newLink) - // .then((link) => { - // setExternalLink(link); - - // disabled - // ? toastr.success(t("Files:LinkEnabledSuccessfully")) - // : toastr.success(t("Files:LinkDisabledSuccessfully")); - // }) - // .catch((err) => toastr.error(err?.message)) - // .finally(() => setIsLoading(false)); - // }; - const onCopyPassword = () => { - copy(password); - toastr.success(t("Files:PasswordSuccessfullyCopied")); + if (password) { + copy(password); + toastr.success(t("Files:PasswordSuccessfullyCopied")); + } }; const onEmbeddingClick = () => { @@ -152,12 +137,6 @@ const LinkRow = (props) => { setIsScrollLocked(true); }; - const onCloseContextMenu = () => { - setIsScrollLocked(false); - }; - - const isDisabled = disabled || isExpired; - const getData = () => { return [ { @@ -166,17 +145,6 @@ const LinkRow = (props) => { icon: SettingsReactSvgUrl, onClick: onEditLink, }, - // { - // key: "edit-link-separator", - // isSeparator: true, - // }, - // { - // key: "share-key", - // label: t("Files:Share"), - // icon: ShareReactSvgUrl, - // // onClick: () => args.onClickLabel("label2"), - // }, - !isDisabled && { key: "copy-link-settings-key", label: t("Files:CopySharedLink"), @@ -184,27 +152,21 @@ const LinkRow = (props) => { onClick: onCopyExternalLink, }, + !isDisabled && + isLocked && { + key: "copy-link-password-key", + label: t("Files:CopyLinkPassword"), + icon: LockedReactSvgUrl, + onClick: onCopyPassword, + }, + !isDisabled && { key: "embedding-settings-key", - label: t("Files:EmbeddingSettings"), + label: t("Files:Embed"), icon: CodeReactSvgUrl, onClick: onEmbeddingClick, }, - // disabled - // ? { - // key: "enable-link-key", - // label: t("Files:EnableLink"), - // icon: LoadedReactSvgUrl, - // onClick: onDisableLink, - // } - // : { - // key: "disable-link-key", - // label: t("Files:DisableLink"), - // icon: OutlineReactSvgUrl, - // onClick: onDisableLink, - // }, - { key: "delete-link-separator", isSeparator: true, @@ -222,91 +184,66 @@ const LinkRow = (props) => { ]; }; - const textColor = disabled ? theme.text.disableColor : theme.text.color; + const editExternalLinkAction = (newLink: TFileLink) => { + setLoadingLinks([newLink.sharedTo.id]); + + editExternalLink(roomId, newLink) + .then((linkData: TFileLink) => { + setExternalLink(linkData); + setLinkParams({ + link: linkData, + roomId, + isPublic: isPublicRoomType, + isFormRoom, + }); + + copy(link?.sharedTo?.shareLink); + toastr.success(t("Files:LinkEditedSuccessfully")); + }) + .catch((err: Error) => toastr.error(err?.message)) + .finally(() => setLoadingLinks([])); + }; + + const onAccessRightsSelect = (opt: TOption) => { + const newLink = { ...link }; + if (opt.access) newLink.access = opt.access; + editExternalLinkAction(newLink); + }; + + const changeExpirationOption = async ( + linkData: TFileLink, + expirationDate: moment.Moment | null, + ) => { + const newLink = { ...link }; + newLink.sharedTo.expirationDate = expirationDate + ? moment(expirationDate) + : null; + editExternalLinkAction(newLink); + }; return ( - - - -
- ) : null - } - withTooltip={expiryDate} - tooltipContent={tooltipContent} - /> - {isArchiveFolder ? ( - - {title} - - ) : ( - - {title} - - )} - - {disabled && {t("Settings:Disabled")}} - -
- {!disabled && !isExpired && !isArchiveFolder && ( - <> - {isLocked && ( - - )} - - - )} - - {!isArchiveFolder && ( - - )} -
- + /> ); }; -export default inject( +export default inject( ({ settingsStore, dialogsStore, - publicRoomStore, treeFoldersStore, infoPanelStore, + publicRoomStore, }) => { const { infoPanelSelection } = infoPanelStore; const { theme } = settingsStore; @@ -317,23 +254,25 @@ export default inject( setEmbeddingPanelData, setLinkParams, } = dialogsStore; - const { editExternalLink, setExternalLink } = publicRoomStore; const { isArchiveFolderRoot } = treeFoldersStore; + const { id, roomType } = infoPanelSelection!; + + const { editExternalLink, setExternalLink } = publicRoomStore; + return { setLinkParams, - editExternalLink, - roomId: infoPanelSelection.id, - setExternalLink, + roomId: id, setEditLinkPanelIsVisible, setDeleteLinkDialogVisible, setEmbeddingPanelData, isArchiveFolder: isArchiveFolderRoot, theme, isPublicRoomType: - infoPanelSelection.roomType === RoomsType.PublicRoom || - infoPanelSelection.roomType === RoomsType.FormRoom, - isFormRoom: infoPanelSelection?.roomType === RoomsType.FormRoom, + roomType === RoomsType.PublicRoom || roomType === RoomsType.FormRoom, + isFormRoom: roomType === RoomsType.FormRoom, + editExternalLink, + setExternalLink, }; }, )( diff --git a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/MembersList.js b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/MembersList.js index c2b5dc12fc..378dd5b1d6 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/MembersList.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/views/Members/sub-components/MembersList.js @@ -78,6 +78,7 @@ const StyledList = styled(List)` `; const itemSize = 48; +const shareLinkItemSize = 68; const MembersList = (props) => { const { @@ -151,6 +152,16 @@ const MembersList = (props) => { [isNextPageLoading, loadNextPage], ); + const getItemSize = ({ index }) => { + const elem = list[index]; + + if (elem?.props?.isShareLink) { + return shareLinkItemSize; + } + + return itemSize; + }; + const onScroll = (e) => { const header = document.getElementById("members-list-header"); @@ -221,7 +232,7 @@ const MembersList = (props) => { onRowsRendered={onRowsRendered} ref={registerChild} rowCount={itemsCount} - rowHeight={itemSize} + rowHeight={getItemSize} rowRenderer={renderRow} width={width} isScrolling={isScrolling} diff --git a/packages/client/src/store/InfoPanelStore.js b/packages/client/src/store/InfoPanelStore.js index 252e8225db..3025bf2aa3 100644 --- a/packages/client/src/store/InfoPanelStore.js +++ b/packages/client/src/store/InfoPanelStore.js @@ -78,7 +78,6 @@ class InfoPanelStore { infoPanelSelection = null; selectionHistory = null; - selectionHistory = null; roomsView = infoMembers; fileView = infoHistory; @@ -94,7 +93,6 @@ class InfoPanelStore { publicRoomStore = null; infoPanelMembers = null; - infoPanelSelection = null; infoPanelRoom = null; membersIsLoading = false; isMembersPanelUpdating = false; diff --git a/packages/shared/api/files/types.ts b/packages/shared/api/files/types.ts index e1b979b28e..5ecb36eb7a 100644 --- a/packages/shared/api/files/types.ts +++ b/packages/shared/api/files/types.ts @@ -24,6 +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 moment from "moment"; import { TCreatedBy, TPathParts } from "../../types"; import { TUser } from "../people/types"; import { @@ -395,9 +396,11 @@ export type TFileLink = { requestToken: string; shareLink: string; title: string; - expirationDate?: string; + expirationDate?: moment.Moment | null; internal?: boolean; + password?: string; }; + subjectType: number; }; export type TFilesUsedSpace = { diff --git a/packages/shared/api/rooms/index.ts b/packages/shared/api/rooms/index.ts index f6579e5278..6a72c1d270 100644 --- a/packages/shared/api/rooms/index.ts +++ b/packages/shared/api/rooms/index.ts @@ -27,7 +27,8 @@ /* eslint-disable @typescript-eslint/default-param-last */ import { AxiosRequestConfig } from "axios"; -import { FolderType, MembersSubjectType } from "../../enums"; +import moment from "moment"; +import { FolderType, MembersSubjectType, ShareAccessRights } from "../../enums"; import { request } from "../client"; import { checkFilterInstance, @@ -397,15 +398,15 @@ export const acceptInvitationByLink = async () => { }; export function editExternalLink( - roomId, - linkId, - title, - access, - expirationDate, - linkType, - password, - disabled, - denyDownload, + roomId: number | string, + linkId: number | string, + title: string, + access: ShareAccessRights, + expirationDate: moment.Moment, + linkType: number, + password: string, + disabled: boolean, + denyDownload: boolean, ) { const skipRedirect = true; diff --git a/packages/shared/components/access-right-select/AccessRightSelect.styled.ts b/packages/shared/components/access-right-select/AccessRightSelect.styled.ts index 72268514d1..86d09237c3 100644 --- a/packages/shared/components/access-right-select/AccessRightSelect.styled.ts +++ b/packages/shared/components/access-right-select/AccessRightSelect.styled.ts @@ -24,7 +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 styled from "styled-components"; +import styled, { css } from "styled-components"; import { Base } from "../../themes"; import { mobile } from "../../utils"; @@ -36,6 +36,25 @@ const StyledWrapper = styled(ComboBox)` padding-inline: 8px; } + ${({ type, theme }) => + type === "onlyIcon" && + css` + .combo-button { + padding-right: 4px; + } + + .combo-button_selected-icon-container { + margin-right: 0px; + } + + .combo-buttons_arrow-icon, + .combo-button_selected-icon-container { + svg path { + fill: ${theme.iconButton.color}; + } + } + `} + @media ${mobile} { .backdrop-active { top: -64px; @@ -77,8 +96,16 @@ const StyledItemDescription = styled.div` StyledItemDescription.defaultProps = { theme: Base }; -const StyledItemIcon = styled.img` +const StyledItemIcon = styled.img<{ isShortenIcon?: boolean }>` margin-inline-end: 8px; + + ${({ isShortenIcon }) => + isShortenIcon && + css` + padding-top: 2px; + width: 12px; + height: 12px; + `} `; const StyledItemContent = styled.div` diff --git a/packages/shared/components/access-right-select/AccessRightSelect.tsx b/packages/shared/components/access-right-select/AccessRightSelect.tsx index bd6acbb891..3034fe0195 100644 --- a/packages/shared/components/access-right-select/AccessRightSelect.tsx +++ b/packages/shared/components/access-right-select/AccessRightSelect.tsx @@ -46,6 +46,7 @@ export const AccessRightSelectPure = ({ advancedOptions, selectedOption, className, + type, ...props }: AccessRightSelectProps) => { const [currentItem, setCurrentItem] = useState(selectedOption); @@ -76,7 +77,12 @@ export const AccessRightSelectPure = ({ onClick={() => onSelectCurrentItem(item)} > - {item.icon && } + {item.icon && ( + + )} {item.label} @@ -108,6 +114,7 @@ export const AccessRightSelectPure = ({ return ( ; export type AccessRightSelectProps = PropsFromCombobox & { diff --git a/packages/shared/components/context-menu-button/ContextMenuButton.types.tsx b/packages/shared/components/context-menu-button/ContextMenuButton.types.tsx index 79cb2268fc..c2a280495a 100644 --- a/packages/shared/components/context-menu-button/ContextMenuButton.types.tsx +++ b/packages/shared/components/context-menu-button/ContextMenuButton.types.tsx @@ -77,7 +77,7 @@ export interface ContextMenuButtonProps { /** Sets the number of columns */ columnCount?: number; /** Sets the display type */ - displayType: ContextMenuButtonDisplayType; + displayType?: ContextMenuButtonDisplayType; /** Closing event */ onClose?: () => void; /** Sets the drop down open with the portal */ diff --git a/packages/shared/components/share/Share.helpers.ts b/packages/shared/components/share/Share.helpers.ts index 78b2589ed9..d71175bcad 100644 --- a/packages/shared/components/share/Share.helpers.ts +++ b/packages/shared/components/share/Share.helpers.ts @@ -116,6 +116,39 @@ export const getAccessOptions = ( return items; }; +export const getRoomAccessOptions = (t: TTranslation) => { + return [ + { + access: ShareAccessRights.Editing, + description: t("Translations:RoleEditorDescription"), + key: "editing", + label: t("Common:Editor"), + icon: AccessEditReactSvgUrl, + }, + { + access: ShareAccessRights.Review, + description: t("Translations:RoleReviewerDescription"), + key: "review", + label: t("Translations:RoleReviewer"), + icon: AccessReviewReactSvgUrl, + }, + { + access: ShareAccessRights.Comment, + description: t("Translations:RoleCommentatorDescription"), + key: "commenting", + label: t("Commentator"), + icon: AccessCommentReactSvgUrl, + }, + { + access: ShareAccessRights.ReadOnly, + description: t("Translations:RoleViewerDescription"), + key: "viewing", + label: t("JavascriptSdk:Viewer"), + icon: EyeReactSvgUrl, + }, + ]; +}; + export const getExpiredOptions = ( t: TTranslation, setTwelveHours: VoidFunction, diff --git a/packages/shared/components/share/Share.styled.ts b/packages/shared/components/share/Share.styled.ts index 42a96cbbc9..9f80696536 100644 --- a/packages/shared/components/share/Share.styled.ts +++ b/packages/shared/components/share/Share.styled.ts @@ -24,7 +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 -import styled from "styled-components"; +import styled, { css } from "styled-components"; +import { DropDown } from "../drop-down"; const StyledLinks = styled.div` margin-top: 20px; @@ -49,11 +50,30 @@ const StyledLinks = styled.div` } `; -const StyledLinkRow = styled.div` - padding: 8px 0; +const StyledLinkRow = styled.div<{ isExpired?: boolean; isDisabled?: boolean }>` display: flex; gap: 8px; align-items: center; + height: 68px; + + opacity: ${({ isDisabled }) => (isDisabled ? 0.4 : 1)}; + + .avatar-wrapper, + .avatar-wrapper:hover { + svg { + path { + fill: ${({ theme }) => theme.infoPanel.avatarColor}; + } + } + } + + .avatar_role-wrapper { + svg { + path:nth-child(3) { + fill: ${({ theme }) => theme.backgroundColor}; + } + } + } .combo-box { padding: 0; @@ -67,23 +87,41 @@ const StyledLinkRow = styled.div` display: flex; flex-direction: column; gap: 0; + overflow: hidden; } .internal-combobox { padding: 0px; + + .combo-button-label { + font-size: 14px; + } } - .internal-combobox_expiered { + .link-options_title { font-size: 14px; font-weight: 600; line-height: 16px; margin: 6px 8px; - color: ${({ theme }) => theme.infoPanel.members.linkAccessComboboxExpired}; + + ${({ theme, isExpired }) => + isExpired && + css` + color: ${theme.infoPanel.members.linkAccessComboboxExpired}; + `}; } .expired-options { padding: 0px; + .text { + color: ${({ theme }) => theme.infoPanel.links.color}; + :hover { + color: ${({ theme }) => theme.infoPanel.links.color}; + background: unset; + } + } + & > span > a { padding: 0px !important; } @@ -91,6 +129,7 @@ const StyledLinkRow = styled.div` .expire-text { margin-inline-start: 8px; + color: ${({ theme }) => theme.infoPanel.links.primaryColor}; } .link-actions { @@ -98,6 +137,11 @@ const StyledLinkRow = styled.div` gap: 16px; align-items: center; margin-inline-start: auto; + + .link-row_copy-icon { + min-width: 16px; + min-height: 16px; + } } .loader { @@ -131,4 +175,10 @@ const StyledSquare = styled.div` } `; -export { StyledLinks, StyledLinkRow, StyledSquare }; +const StyledDropDown = styled(DropDown)` + .share-link_calendar { + position: fixed; + } +`; + +export { StyledLinks, StyledLinkRow, StyledSquare, StyledDropDown }; diff --git a/packages/shared/components/share/Share.types.ts b/packages/shared/components/share/Share.types.ts index e9afd8e18d..498e73bd98 100644 --- a/packages/shared/components/share/Share.types.ts +++ b/packages/shared/components/share/Share.types.ts @@ -39,21 +39,50 @@ export type ShareCalendarProps = { closeCalendar: (formattedDate: moment.Moment) => void; calendarRef: React.RefObject; locale: string; + bodyRef?: React.MutableRefObject; + useDropDown?: boolean; }; export type TLink = TFileLink | { isLoaded: boolean }; -export type LinkRowProps = { - onAddClick: () => Promise; - links: TLink[] | null; - changeShareOption: (item: TOption, link: TFileLink) => Promise; - changeAccessOption: (item: TOption, link: TFileLink) => Promise; - changeExpirationOption: ( - link: TFileLink, - expirationDate: moment.Moment | null, - ) => Promise; - availableExternalRights: TAvailableExternalRights; - loadingLinks: (string | number)[]; -}; +export type LinkRowProps = + | { + onAddClick: () => Promise; + links: TLink[] | null; + changeShareOption: (item: TOption, link: TFileLink) => Promise; + changeAccessOption: (item: TOption, link: TFileLink) => Promise; + changeExpirationOption: ( + link: TFileLink, + expirationDate: moment.Moment | null, + ) => Promise; + availableExternalRights: TAvailableExternalRights; + loadingLinks: (string | number)[]; + isRoomsLink?: undefined; + isPrimaryLink?: undefined; + isArchiveFolder?: undefined; + getData: () => undefined; + onOpenContextMenu?: undefined; + onCloseContextMenu?: undefined; + onAccessRightsSelect?: undefined; + } + | { + onAddClick: () => Promise; + links: TLink[] | null; + changeShareOption: (item: TOption, link: TFileLink) => Promise; + changeAccessOption: (item: TOption, link: TFileLink) => Promise; + changeExpirationOption: ( + link: TFileLink, + expirationDate: moment.Moment | null, + ) => Promise; + availableExternalRights: TAvailableExternalRights; + loadingLinks: (string | number)[]; + isRoomsLink?: boolean; + isPrimaryLink: boolean; + isArchiveFolder: boolean; + getData: () => ContextMenuModel[]; + onOpenContextMenu: (e: React.MouseEvent) => void; + onCloseContextMenu: () => void; + onAccessRightsSelect: (option: TOption) => void; + }; export type ExpiredComboBoxProps = { link: TFileLink; @@ -62,6 +91,9 @@ export type ExpiredComboBoxProps = { expirationDate: moment.Moment | null, ) => Promise; isDisabled?: boolean; + isRoomsLink?: boolean; + changeAccessOption: (item: TOption, link: TFileLink) => Promise; + accessOptions: TOption[]; }; export type ShareProps = { diff --git a/packages/shared/components/share/index.tsx b/packages/shared/components/share/index.tsx index b60fa12f12..8178589a9f 100644 --- a/packages/shared/components/share/index.tsx +++ b/packages/shared/components/share/index.tsx @@ -244,11 +244,7 @@ const Share = (props: ShareProps) => { if (item.access === ShareAccessRights.None) { deleteLink(link.sharedTo.id); - if (link.sharedTo.primary) { - toastr.success(t("Common:GeneralAccessLinkRemove")); - } else { - toastr.success(t("Common:AdditionalLinkRemove")); - } + toastr.success(t("Common:LinkRemoved")); } else { updateLink(link, res); if (item.access === ShareAccessRights.DenyAccess) { diff --git a/packages/shared/components/share/sub-components/ExpiredComboBox.tsx b/packages/shared/components/share/sub-components/ExpiredComboBox.tsx index 12c7a5c77a..31a514ac88 100644 --- a/packages/shared/components/share/sub-components/ExpiredComboBox.tsx +++ b/packages/shared/components/share/sub-components/ExpiredComboBox.tsx @@ -37,11 +37,15 @@ import { getExpiredOptions } from "../Share.helpers"; import { ExpiredComboBoxProps } from "../Share.types"; import ShareCalendar from "./ShareCalendar"; +import { ShareAccessRights } from "../../../enums"; const ExpiredComboBox = ({ link, changeExpirationOption, isDisabled, + isRoomsLink, + changeAccessOption, + accessOptions, }: ExpiredComboBoxProps) => { const { t, i18n } = useTranslation(["Common"]); const calendarRef = useRef(null); @@ -107,8 +111,9 @@ const ExpiredComboBox = ({ return { date: calculatedDate + 1, label: t("Common:Days") }; }; - const onRegenerateClick = () => { - setSevenDays(); + const onRemoveLink = () => { + const opt = accessOptions.find((o) => o.access === ShareAccessRights.None); + if (opt) changeAccessOption(opt, link); }; useEffect(() => { @@ -150,7 +155,7 @@ const ExpiredComboBox = ({ ); } - const date = t("Common:Unlimited"); + const date = t("Common:Unlimited").toLowerCase(); return ( @@ -181,9 +186,9 @@ const ExpiredComboBox = ({ fontWeight={400} fontSize="12px" color="#4781D1" - onClick={onRegenerateClick} + onClick={onRemoveLink} > - {t("Common:Regenerate")} + {t("Common:RemoveLink")} ) : ( @@ -193,10 +198,12 @@ const ExpiredComboBox = ({ )} {showCalendar && ( )} diff --git a/packages/shared/components/share/sub-components/LinkRow.tsx b/packages/shared/components/share/sub-components/LinkRow.tsx index 662800eb0e..71d6a40c5b 100644 --- a/packages/shared/components/share/sub-components/LinkRow.tsx +++ b/packages/shared/components/share/sub-components/LinkRow.tsx @@ -30,6 +30,7 @@ import PlusIcon from "PUBLIC_DIR/images/plus.react.svg?url"; import UniverseIcon from "PUBLIC_DIR/images/universe.react.svg?url"; import PeopleIcon from "PUBLIC_DIR/images/people.react.svg?url"; import CopyIcon from "PUBLIC_DIR/images/copy.react.svg?url"; +import LockedReactSvg from "PUBLIC_DIR/images/icons/12/locked.react.svg"; import { RowSkeleton } from "../../../skeletons/share"; import { TFileLink } from "../../../api/files/types"; @@ -43,11 +44,18 @@ import { Loader, LoaderTypes } from "../../loader"; import { Text } from "../../text"; import { StyledLinkRow, StyledSquare } from "../Share.styled"; -import { getShareOptions, getAccessOptions } from "../Share.helpers"; +import { + getShareOptions, + getAccessOptions, + getRoomAccessOptions, +} from "../Share.helpers"; import { LinkRowProps } from "../Share.types"; import ExpiredComboBox from "./ExpiredComboBox"; +import { AccessRightSelect } from "../../access-right-select"; +import { ContextMenuButton } from "../../context-menu-button"; + const LinkRow = ({ onAddClick, links, @@ -56,11 +64,22 @@ const LinkRow = ({ changeExpirationOption, availableExternalRights, loadingLinks, + isRoomsLink, + isPrimaryLink, + isArchiveFolder, + getData, + onOpenContextMenu, + onCloseContextMenu, + onAccessRightsSelect, }: LinkRowProps) => { - const { t } = useTranslation(["Common"]); + const { t } = useTranslation(["Common", "Translations"]); const shareOptions = getShareOptions(t) as TOption[]; - const accessOptions = getAccessOptions(t, availableExternalRights); + const accessOptions = availableExternalRights + ? getAccessOptions(t, availableExternalRights) + : []; + + const roomAccessOptions = isRoomsLink ? getRoomAccessOptions(t) : []; const onCopyLink = (link: TFileLink) => { copyShareLink(link.sharedTo.shareLink); @@ -88,14 +107,26 @@ const LinkRow = ({ (option) => option && "access" in option && option.access === link.access, ); + + const roomSelectedOptions = roomAccessOptions.find( + (option) => + option && "access" in option && option.access === link.access, + ); + const avatar = shareOption?.key === "anyone" ? UniverseIcon : PeopleIcon; const isExpiredLink = link.sharedTo.isExpired; + const isLocked = !!link.sharedTo.password; + const linkTitle = link.sharedTo.title; const isLoaded = loadingLinks.includes(link.sharedTo.id); return ( - + {isLoaded ? ( ) : ( @@ -103,10 +134,15 @@ const LinkRow = ({ size={AvatarSize.min} role={AvatarRole.user} source={avatar} + roleIcon={isLocked ? : undefined} /> )}
- {!isExpiredLink ? ( + {isRoomsLink ? ( + + {linkTitle} + + ) : !isExpiredLink ? ( ) : ( - - {shareOption?.label} - + {shareOption?.label} + )} + {!isPrimaryLink && ( + )} -
- onCopyLink(link)} - title={t("Common:CreateAndCopy")} - isDisabled={isExpiredLink || isLoaded} - /> - changeAccessOption(item, link)} - scaled={false} - scaledOptions={false} - showDisabledItems - size={ComboBoxSize.content} - fillIcon - modernView - type="onlyIcon" - isDisabled={isExpiredLink || isLoaded} - manualWidth="fit-content" - /> + {!isArchiveFolder && ( + onCopyLink(link)} + title={t("Common:CreateAndCopy")} + isDisabled={isExpiredLink || isLoaded} + /> + )} + {isRoomsLink ? ( + <> + + {!isArchiveFolder && ( + + )} + + ) : ( + changeAccessOption(item, link)} + scaled={false} + scaledOptions={false} + showDisabledItems + size={ComboBoxSize.content} + fillIcon + modernView + type="onlyIcon" + isDisabled={isExpiredLink || isLoaded} + manualWidth="fit-content" + /> + )}
); diff --git a/packages/shared/components/share/sub-components/ShareCalendar.tsx b/packages/shared/components/share/sub-components/ShareCalendar.tsx index f7eb9e870c..3afd473280 100644 --- a/packages/shared/components/share/sub-components/ShareCalendar.tsx +++ b/packages/shared/components/share/sub-components/ShareCalendar.tsx @@ -31,6 +31,7 @@ import { isMobile } from "../../../utils/device"; import { Calendar } from "../../calendar"; import { ShareCalendarProps } from "../Share.types"; +import { StyledDropDown } from "../Share.styled"; const StyledCalendar = styled(Calendar)` position: absolute; @@ -50,12 +51,15 @@ const ShareCalendar = ({ closeCalendar, calendarRef, locale, + bodyRef, + useDropDown, }: ShareCalendarProps) => { const selectedDate = moment(); const maxDate = moment().add(10, "years"); - return ( + const calendarComponent = ( ); + + return useDropDown ? ( + + {calendarComponent} + + ) : ( + calendarComponent + ); }; export default ShareCalendar; diff --git a/packages/shared/themes/base.ts b/packages/shared/themes/base.ts index 1f8f3105b3..153c4ab75d 100644 --- a/packages/shared/themes/base.ts +++ b/packages/shared/themes/base.ts @@ -2085,8 +2085,10 @@ export const getBaseTheme = () => { closeButtonBg: "transparent", nameColor: "#858585", + avatarColor: "#555F65", links: { + color: "#4781D1", iconColor: "#3B72A7", iconErrorColor: "#F24724", primaryColor: "#555F65", diff --git a/packages/shared/themes/dark.ts b/packages/shared/themes/dark.ts index b60d6f0123..3123d2ed05 100644 --- a/packages/shared/themes/dark.ts +++ b/packages/shared/themes/dark.ts @@ -2057,8 +2057,10 @@ const Dark: TTheme = { closeButtonBg: "#a2a2a2", nameColor: "#A3A9AE", + avatarColor: "#A3A9AE", links: { + color: "#4781D1", iconColor: "#858585", iconErrorColor: "#E06451", primaryColor: "#ADADAD", diff --git a/public/images/icons/12/locked.react.svg b/public/images/icons/12/locked.react.svg new file mode 100644 index 0000000000..e37e3b9708 --- /dev/null +++ b/public/images/icons/12/locked.react.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/locales/en/Common.json b/public/locales/en/Common.json index 0a27ee1c01..bdb94025df 100644 --- a/public/locales/en/Common.json +++ b/public/locales/en/Common.json @@ -147,6 +147,7 @@ "EditButton": "Edit", "Editing": "Editing", "Editor": "Editor", + "Commentator": "Commentator", "Email": "Email", "EmptyDescription": "The list of users previously invited to {{productName}} or separate rooms will appear here. You will be able to invite these users for collaboration at any time.", "EmptyEmail": "No email address parsed", @@ -386,6 +387,8 @@ "RecoverDescribeYourProblemPlaceholder": "Describe your problem", "RecoverTitle": "Access recovery", "Regenerate": "Regenerate", + "RemoveLink": "Remove link", + "LinkRemoved": "Link removed", "RegistrationEmail": "Your registration email address", "ReloadPage": "Reload page", "Remember": "Remember me",