Merge branch 'develop' into refactoring/global-colors

This commit is contained in:
Viktor Fomin 2024-06-20 18:00:59 +03:00
commit cb4a36feee
45 changed files with 1577 additions and 1076 deletions

View File

@ -1,33 +1,29 @@
{ {
"AccountsEmptyScreenText": "See users details here", "AccountsEmptyScreenText": "See users details here",
"AddedRoomTags": "Tags added.",
"Administration": "Administration", "Administration": "Administration",
"AndMoreLabel": "and <strong>{{count}} more</strong>", "AndMoreLabel": "and <1>{{count}} more</1>",
"CreationDate": "Creation date", "CreationDate": "Creation date",
"Data": "Data", "Data": "Data",
"DateModified": "Date modified", "DateModified": "Date modified",
"DeletedRoomTags": "Tags removed.",
"ExpectUsers": "Expect users", "ExpectUsers": "Expect users",
"FeedCreateFileSeveral": "Files added", "FeedLinkWasDeleted": "Link was deleted",
"FeedCreateFileSingle": "File created",
"FeedCreateFolderSeveral": "Folders added",
"FeedCreateFolderSingle": "Folder created",
"FeedCreateRoom": "<strong>«{{roomTitle}}»</strong> room created",
"FeedCreateRoomTag": "Tags added",
"FeedCreateUser": "Users added",
"FeedDeleteFile": "Files removed",
"FeedDeleteFolder": "Folders removed",
"FeedDeleteRoomTag": "Tags removed",
"FeedDeleteUser": "User removed",
"FeedLocationLabel": "Folder «{{folderTitle}}»", "FeedLocationLabel": "Folder «{{folderTitle}}»",
"FeedMoveFile": "Files moved", "FileConverted": "File converted.",
"FeedMoveFolder": "Folders moved", "FileCopied": "Files copied.",
"FeedRenameFile": "File renamed", "FileCreated": "File created.",
"FeedRenameFolder": "Folder renamed", "FileDeleted": "Files removed.",
"FeedRenameRoom": "Room <strong>«{{oldRoomTitle}}»</strong> renamed to <strong>«{{roomTitle}}»</strong>.",
"FeedUpdateFile": "File updated",
"FeedUpdateRoom": "Icon changed",
"FeedUpdateUser": "has been assigned role {{role}}",
"FileExtension": "File extension", "FileExtension": "File extension",
"FileMoved": "Files moved.",
"FileRenamed": "File renamed.",
"FilesEmptyScreenText": "See file and folder details here", "FilesEmptyScreenText": "See file and folder details here",
"FileUploaded": "Files added.",
"FolderCopied": "Folders copied.",
"FolderCreated": "Folder created.",
"FolderDeleted": "Folders removed.",
"FolderMoved": "Folders moved.",
"FolderRenamed": "Folder renamed.",
"GalleryEmptyScreenText": "See form template details here", "GalleryEmptyScreenText": "See form template details here",
"GroupsEmptyScreenText": "See group details here", "GroupsEmptyScreenText": "See group details here",
"HistoryEmptyScreenText": "Activity history will be shown here", "HistoryEmptyScreenText": "Activity history will be shown here",
@ -35,11 +31,25 @@
"ItemsSelected": "Items selected", "ItemsSelected": "Items selected",
"LastModifiedBy": "Last modified by", "LastModifiedBy": "Last modified by",
"Properties": "Properties", "Properties": "Properties",
"RoomCreated": "<1>«{{roomTitle}}»</1> room created",
"RoomCreateUser": "Users added.",
"RoomExternalLinkCreated": "Link created.",
"RoomExternalLinkDeleted": "Link <1>«{{linkTitle}}»</1> deleted.",
"RoomExternalLinkRenamed": "Link <1>«{{oldLinkTitle}}»</1> renamed to <1>«{{linkTitle}}»</1>",
"RoomGroupAdded": "Groups added.",
"RoomGroupRemove": "Group removed",
"RoomLogoCreated": "Icon changed",
"RoomLogoDeleted": "Icon changed",
"RoomRemoveUser": "User removed.",
"RoomRenamed": "Room <1>«{{oldRoomTitle}}»</1> renamed to <1>«{{roomTitle}}»</1>",
"RoomsEmptyScreenTent": "See rooms details here", "RoomsEmptyScreenTent": "See rooms details here",
"RoomUpdateAccessForGroup": "has been assigned role",
"RoomUpdateAccessForUser": "has been assigned role",
"SelectedUsers": "Selected accounts", "SelectedUsers": "Selected accounts",
"StorageType": "Storage type", "StorageType": "Storage type",
"SubmenuDetails": "Details", "SubmenuDetails": "Details",
"SubmenuHistory": "History", "SubmenuHistory": "History",
"UserFileUpdated": "File updated.",
"Users": "Users", "Users": "Users",
"Versions": "Versions" "Versions": "Versions"
} }

View File

@ -109,28 +109,20 @@ const ArticleBodyContent = (props) => {
path = getCategoryUrl(CategoryType.Personal); path = getCategoryUrl(CategoryType.Personal);
if (activeItemId === myFolderId && folderId === selectedFolderId)
return;
break; break;
case archiveFolderId: case archiveFolderId:
const archiveFilter = RoomsFilter.getDefault(userId); const archiveFilter = RoomsFilter.getDefault(userId);
archiveFilter.searchArea = RoomSearchArea.Archive; archiveFilter.searchArea = RoomSearchArea.Archive;
params = archiveFilter.toUrlParams(userId, true); params = archiveFilter.toUrlParams(userId, true);
path = getCategoryUrl(CategoryType.Archive); path = getCategoryUrl(CategoryType.Archive);
if (activeItemId === archiveFolderId && folderId === selectedFolderId)
return;
break; break;
case recycleBinFolderId: case recycleBinFolderId:
const recycleBinFilter = FilesFilter.getDefault(); const recycleBinFilter = FilesFilter.getDefault();
recycleBinFilter.folder = folderId; recycleBinFilter.folder = folderId;
params = recycleBinFilter.toUrlParams(); params = recycleBinFilter.toUrlParams();
path = getCategoryUrl(CategoryType.Trash); path = getCategoryUrl(CategoryType.Trash);
if (
activeItemId === recycleBinFolderId &&
folderId === selectedFolderId
)
return;
break; break;
case "accounts": case "accounts":
const accountsFilter = AccountsFilter.getDefault(); const accountsFilter = AccountsFilter.getDefault();
@ -138,7 +130,6 @@ const ArticleBodyContent = (props) => {
path = getCategoryUrl(CategoryType.Accounts); path = getCategoryUrl(CategoryType.Accounts);
withTimer = false; withTimer = false;
if (activeItemId === "accounts" && isAccounts) return;
break; break;
case "settings": case "settings":
@ -155,12 +146,11 @@ const ArticleBodyContent = (props) => {
roomsFilter.searchArea = RoomSearchArea.Active; roomsFilter.searchArea = RoomSearchArea.Active;
params = roomsFilter.toUrlParams(userId, true); params = roomsFilter.toUrlParams(userId, true);
path = getCategoryUrl(CategoryType.Shared); path = getCategoryUrl(CategoryType.Shared);
if (activeItemId === roomsFolderId && folderId === selectedFolderId)
return;
break; break;
} }
path += `?${params}`; path += `?${params}&date=${new Date().getTime()}`;
if (openingNewTab(path, e)) return; if (openingNewTab(path, e)) return;

View File

@ -156,7 +156,6 @@ const EditRoomEvent = ({
isTitleChanged || isQuotaChanged isTitleChanged || isQuotaChanged
? await editRoom(item.id, editRoomParams) ? await editRoom(item.id, editRoomParams)
: item; : item;
room.isLogoLoading = true; room.isLogoLoading = true;
const createTagActions = []; const createTagActions = [];
@ -177,7 +176,7 @@ const EditRoomEvent = ({
}; };
} }
if (tags.length) { if (tags.length) {
actions.push(addTagsToRoom(room.id, tags)); actions.push(addTagsToRoom(room.id, newTags));
room.tags = tags; room.tags = tags;
} }
if (removedTags.length) if (removedTags.length)
@ -189,7 +188,7 @@ const EditRoomEvent = ({
room = await removeLogoFromRoom(room.id); room = await removeLogoFromRoom(room.id);
} }
if (roomParams.icon.uploadedFile) { if (roomParams.iconWasUpdated && roomParams.icon.uploadedFile) {
updateRoom(item, { updateRoom(item, {
...room, ...room,
logo: { big: item.logo.original }, logo: { big: item.logo.original },

View File

@ -129,7 +129,7 @@ const SetRoomParams = ({
if (!icon.uploadedFile !== disableImageRescaling) if (!icon.uploadedFile !== disableImageRescaling)
setDisableImageRescaling(!icon.uploadedFile); setDisableImageRescaling(!icon.uploadedFile);
setRoomParams({ ...roomParams, icon: icon }); setRoomParams({ ...roomParams, icon: icon, iconWasUpdated: true });
}; };
const onOwnerChange = () => { const onOwnerChange = () => {

View File

@ -37,16 +37,10 @@ const StyledTagList = styled.div`
width: 100%; width: 100%;
.set_room_params-tag_input-tag { .set_room_params-tag_input-tag {
background: ${(props) =>
props.theme.createEditRoomDialog.tagInput.tagBackground};
padding: 6px 8px; padding: 6px 8px;
border-radius: 3px; border-radius: 3px;
margin: 0; margin: 0;
:hover {
background: ${(props) =>
props.theme.createEditRoomDialog.tagInput.tagHoverBackground};
}
.tag-icon { .tag-icon {
${({ theme }) => ${({ theme }) =>
theme.interfaceDirection === "rtl" theme.interfaceDirection === "rtl"

View File

@ -24,41 +24,34 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // 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 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { useState, useEffect, useMemo } from "react"; import { useState, useMemo } from "react";
import { Backdrop } from "@docspace/shared/components/backdrop"; import { inject, observer } from "mobx-react";
import { withTranslation } from "react-i18next";
import { ReactSVG } from "react-svg";
import { Loader } from "@docspace/shared/components/loader"; import { Loader } from "@docspace/shared/components/loader";
import { Text } from "@docspace/shared/components/text"; import { Text } from "@docspace/shared/components/text";
import { Heading } from "@docspace/shared/components/heading";
import { Aside } from "@docspace/shared/components/aside";
import { Row } from "@docspace/shared/components/row"; import { Row } from "@docspace/shared/components/row";
import { Button } from "@docspace/shared/components/button"; import { Button } from "@docspace/shared/components/button";
import { withTranslation } from "react-i18next";
import { toastr } from "@docspace/shared/components/toast"; import { toastr } from "@docspace/shared/components/toast";
import { Portal } from "@docspace/shared/components/portal";
import { ReactSVG } from "react-svg";
import { import {
StyledAsidePanel, ModalDialog,
StyledContent, ModalDialogType,
StyledHeaderContent, } from "@docspace/shared/components/modal-dialog";
StyledBody,
StyledFooter,
StyledSharingBody,
StyledLink,
} from "../StyledPanels";
import { inject, observer } from "mobx-react";
import { combineUrl } from "@docspace/shared/utils/combineUrl";
import config from "PACKAGE_FILE";
import { DialogAsideSkeleton } from "@docspace/shared/skeletons/dialog"; import { DialogAsideSkeleton } from "@docspace/shared/skeletons/dialog";
import withLoader from "../../../HOCs/withLoader";
import FilesFilter from "@docspace/shared/api/files/filter";
import { combineUrl } from "@docspace/shared/utils/combineUrl";
import { import {
getCategoryTypeByFolderType, getCategoryTypeByFolderType,
getCategoryUrl, getCategoryUrl,
} from "SRC_DIR/helpers/utils"; } from "SRC_DIR/helpers/utils";
import FilesFilter from "@docspace/shared/api/files/filter";
import { DeviceType } from "@docspace/shared/enums";
const SharingBodyStyle = { height: `calc(100vh - 156px)` }; import { StyledNewFilesBody, StyledLink } from "../StyledPanels";
import withLoader from "../../../HOCs/withLoader";
import config from "PACKAGE_FILE";
const NewFilesPanel = (props) => { const NewFilesPanel = (props) => {
const { const {
@ -295,61 +288,44 @@ const NewFilesPanel = (props) => {
}); });
}, [onNewFileClick, getItemIcon, currentOpenFileId]); }, [onNewFileClick, getItemIcon, currentOpenFileId]);
const element = ( return (
<StyledAsidePanel visible={visible}> <ModalDialog
<Backdrop visible={visible}
onClick={onClose} onClose={onClose}
visible={visible} displayType={ModalDialogType.aside}
zIndex={310} withBodyScroll
isAside={true} >
/> <ModalDialog.Header>{t("NewFiles")}</ModalDialog.Header>
<Aside className="header_aside-panel" visible={visible} onClose={onClose}> <ModalDialog.Body>
<StyledContent> {!isLoading ? (
<StyledHeaderContent> <StyledNewFilesBody>{filesListNode}</StyledNewFilesBody>
<Heading className="files-operations-header" size="medium" truncate> ) : (
{t("NewFiles")} <div key="loader" className="panel-loader-wrapper">
</Heading> <Loader type="oval" size="16px" className="panel-loader" />
</StyledHeaderContent> <Text as="span">{`${t("Common:LoadingProcessing")} ${t(
{!isLoading ? ( "Common:LoadingDescription",
<StyledBody className="files-operations-body"> )}`}</Text>
<StyledSharingBody style={SharingBodyStyle}> </div>
{filesListNode} )}
</StyledSharingBody> </ModalDialog.Body>
</StyledBody> <ModalDialog.Footer>
) : ( <Button
<div key="loader" className="panel-loader-wrapper"> scale
<Loader type="oval" size="16px" className="panel-loader" /> label={t("Viewed")}
<Text as="span">{`${t("Common:LoadingProcessing")} ${t( size="normal"
"Common:LoadingDescription", primary
)}`}</Text> onClick={onMarkAsRead}
</div> isLoading={inProgress}
)} />
<StyledFooter> <Button
<Button scale
className="new_files_panel-button new_file_panel-first-button" label={t("Common:CloseButton")}
label={t("Viewed")} size="normal"
size="normal" isDisabled={inProgress}
primary onClick={onClose}
onClick={onMarkAsRead} />
isLoading={inProgress} </ModalDialog.Footer>
/> </ModalDialog>
<Button
className="new_files_panel-button"
label={t("Common:CloseButton")}
size="normal"
isDisabled={inProgress}
onClick={onClose}
/>
</StyledFooter>
</StyledContent>
</Aside>
</StyledAsidePanel>
);
return currentDeviceType === DeviceType.mobile ? (
<Portal element={element} />
) : (
element
); );
}; };

View File

@ -89,40 +89,6 @@ const StyledAsidePanel = styled.div`
padding-right: 10px; padding-right: 10px;
`} `}
} }
.upload_panel-header {
font-weight: 700;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 19px 17px 19px auto;
`
: css`
padding: 19px auto 19px 17px;
`}
}
.upload-panel_header-content {
z-index: 320;
position: fixed;
left: 0;
right: 0;
background-color: ${(props) =>
props.theme.filesPanels.aside.backgroundColor};
height: ${isMobile ? "55px" : "48px"};
}
.upload-panel_header-content::after {
position: absolute;
width: 100%;
max-width: 468px;
height: 1px;
background: ${(props) => props.theme.filesPanels.sharing.borderBottom};
content: "";
top: 48px;
width: calc(100% - 32px);
}
.upload-panel_body {
padding-top: ${isMobile ? "67px" : "60px"};
height: ${isMobile ? "calc(100vh - 67px)" : "calc(100vh - 60px)"};
}
.modal-dialog-aside { .modal-dialog-aside {
padding: 0; padding: 0;
@ -219,17 +185,6 @@ const StyledContent = styled.div`
background-color: ${(props) => background-color: ${(props) =>
props.theme.filesPanels.content.backgroundColor}; props.theme.filesPanels.content.backgroundColor};
.upload-panel_header-content {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 0 !important;
`
: css`
margin-right: 0 !important;
`}
}
.header_aside-panel-plus-icon { .header_aside-panel-plus-icon {
${(props) => ${(props) =>
props.theme.interfaceDirection === "rtl" props.theme.interfaceDirection === "rtl"
@ -316,19 +271,6 @@ const StyledHeaderContent = styled.div`
`} `}
border-bottom: ${(props) => props.theme.filesPanels.sharing.borderBottom}; border-bottom: ${(props) => props.theme.filesPanels.sharing.borderBottom};
.upload_panel-icons-container {
display: flex;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: auto;
`
: css`
margin-left: auto;
`}
.upload_panel-vertical-dots-icon {
}
} }
.files-operations-header, .files-operations-header,
@ -427,181 +369,15 @@ const StyledBody = styled.div`
StyledBody.defaultProps = { theme: Base }; StyledBody.defaultProps = { theme: Base };
const StyledSharingBody = styled(Scrollbar)` const StyledNewFilesBody = styled.div`
position: relative; height: 100%;
padding: 16px 0; width: 100%;
box-sizing: border-box;
width: calc(100% + 16px) !important;
.link-row__container {
height: 47px;
}
.link-row__container,
.sharing-row {
.styled-element {
margin-right: 0;
margin-left: 0;
}
}
.row_content { .row_content {
overflow: visible; overflow: visible;
height: auto; height: auto;
} }
.sharing-row {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-right: 16px;
`
: css`
padding-left: 16px;
`}
//width: calc(100% - 16px);
box-sizing: border-box;
border-bottom: none;
}
.nav-thumb-vertical {
opacity: 0;
transition: opacity 200ms ease;
}
:hover {
.nav-thumb-vertical {
opacity: 1;
}
}
.sharing_panel-text {
line-height: 24px;
font-weight: 600;
font-size: 14px;
}
.sharing_panel-link {
a {
text-decoration: none !important;
span {
font-weight: 600;
}
}
}
.sharing_panel-link-combo-box {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: auto;
`
: css`
margin-left: auto;
`}
.combo-button {
height: 24px;
width: 94px;
svg {
bottom: 6px;
position: absolute;
height: 8px;
width: 8px;
}
}
}
.sharing_panel-owner-icon {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-left: 19px;
`
: css`
padding-right: 19px;
`}
}
.sharing_panel-remove-icon {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: auto;
`
: css`
margin-left: auto;
`}
line-height: 24px;
display: flex;
align-items: center;
flex-direction: row-reverse;
svg {
width: 16px;
height: 16px;
}
}
.panel_combo-box {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 0px;
`
: css`
margin-left: 0px;
`}
.combo-button {
height: 30px;
margin: 0;
padding: 0;
border: none;
}
.combo-button-label {
margin: 0;
}
}
.sharing_panel-text-area {
position: fixed;
bottom: 70px;
width: 94%;
left: 0;
right: 0;
margin: auto;
}
@media ${desktop} {
.link-row__container {
height: 41px;
.link-row {
min-height: 41px;
}
}
.sharing-row {
min-height: 41px;
//padding-right: 15px;
.sharing_panel-remove-icon {
font-size: 12px;
}
}
.sharing_panel-text,
.sharing_panel-link span {
font-size: 13px;
}
}
.row-loader {
margin-left: 4px;
}
`; `;
const StyledFooter = styled.div` const StyledFooter = styled.div`
@ -936,6 +712,22 @@ const StyledLink = styled(Link)`
StyledModalRowContainer.defaultProps = { theme: Base }; StyledModalRowContainer.defaultProps = { theme: Base };
const StyledUploadHeader = styled.div`
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
`;
const StyledUploadBody = styled.div`
width: calc(100% + 16px);
height: 100%;
.scroll-body {
padding-inline-end: 0px !important;
}
`;
export { export {
StyledAsidePanel, StyledAsidePanel,
StyledEmbeddingPanel, StyledEmbeddingPanel,
@ -943,9 +735,11 @@ export {
StyledContent, StyledContent,
StyledHeaderContent, StyledHeaderContent,
StyledBody, StyledBody,
StyledSharingBody,
StyledFooter, StyledFooter,
StyledLinkRow, StyledLinkRow,
StyledModalRowContainer, StyledModalRowContainer,
StyledLink, StyledLink,
StyledNewFilesBody,
StyledUploadHeader,
StyledUploadBody,
}; };

View File

@ -41,26 +41,13 @@ import { Button } from "@docspace/shared/components/button";
import { tablet } from "@docspace/shared/utils"; import { tablet } from "@docspace/shared/utils";
const StyledFileRow = styled(Row)` const StyledFileRow = styled(Row)`
width: calc(100% - 16px); width: 100%;
box-sizing: border-box; box-sizing: border-box;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-right: 16px;
`
: css`
padding-left: 16px;
`}
max-width: 484px;
.row_context-menu-wrapper { .row_context-menu-wrapper {
width: auto; width: auto;
display: none; display: none;
} }
::after {
max-width: 468px;
width: calc(100% - 16px);
}
${!isMobile && "min-height: 48px;"} ${!isMobile && "min-height: 48px;"}

View File

@ -24,26 +24,31 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // 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 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import VerticalDotsReactSvgUrl from "PUBLIC_DIR/images/vertical-dots.react.svg?url";
import ClearActiveReactSvgUrl from "PUBLIC_DIR/images/clear.active.react.svg?url"; import ClearActiveReactSvgUrl from "PUBLIC_DIR/images/clear.active.react.svg?url";
import ButtonCancelReactSvgUrl from "PUBLIC_DIR/images/button.cancel.react.svg?url"; import ButtonCancelReactSvgUrl from "PUBLIC_DIR/images/button.cancel.react.svg?url";
import React from "react"; import React from "react";
import { IconButton } from "@docspace/shared/components/icon-button"; import styled from "styled-components";
import { Backdrop } from "@docspace/shared/components/backdrop";
import { Heading } from "@docspace/shared/components/heading";
import { Aside } from "@docspace/shared/components/aside";
import { withTranslation } from "react-i18next"; import { withTranslation } from "react-i18next";
import {
StyledAsidePanel,
StyledContent,
StyledHeaderContent,
StyledBody,
} from "../StyledPanels";
import FileList from "./FileList";
import { inject, observer } from "mobx-react"; import { inject, observer } from "mobx-react";
import { IconButton } from "@docspace/shared/components/icon-button";
import {
ModalDialog,
ModalDialogType,
} from "@docspace/shared/components/modal-dialog";
import { DialogAsideSkeleton } from "@docspace/shared/skeletons/dialog"; import { DialogAsideSkeleton } from "@docspace/shared/skeletons/dialog";
import { StyledUploadHeader, StyledUploadBody } from "../StyledPanels";
import FileList from "./FileList";
import withLoader from "../../../HOCs/withLoader"; import withLoader from "../../../HOCs/withLoader";
const StyledModal = styled(ModalDialog)`
.heading {
width: 100%;
}
`;
class UploadPanelComponent extends React.Component { class UploadPanelComponent extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -120,54 +125,39 @@ class UploadPanelComponent extends React.Component {
: t("Files:Convert"); : t("Files:Convert");
return ( return (
<StyledAsidePanel visible={visible}> <StyledModal
<Backdrop visible={visible}
onClick={this.onClose} onClose={this.onClose}
visible={visible} displayType={ModalDialogType.aside}
zIndex={zIndex} >
isAside={true} <ModalDialog.Header>
/> <StyledUploadHeader>
<Aside <div>{title}</div>
className="header_aside-panel" <div>
visible={visible} {uploaded && converted ? (
withoutBodyScroll <IconButton
onClose={this.onClose} size="20"
> iconName={ClearActiveReactSvgUrl}
<StyledContent> isClickable
<StyledHeaderContent className="upload-panel_header-content"> onClick={this.clearUploadPanel}
<Heading className="upload_panel-header" size="medium" truncate> />
{title} ) : (
</Heading> <IconButton
<div className="upload_panel-icons-container"> size="20"
<div className="upload_panel-remove-icon"> iconName={ButtonCancelReactSvgUrl}
{uploaded && converted ? ( isClickable
<IconButton onClick={uploaded ? cancelConversion : this.onCancelUpload}
size="20" />
iconName={ClearActiveReactSvgUrl} )}
// color={theme.filesPanels.upload.color} </div>
isClickable </StyledUploadHeader>
onClick={this.clearUploadPanel} </ModalDialog.Header>
/> <ModalDialog.Body>
) : ( <StyledUploadBody>
<IconButton <FileList />
size="20" </StyledUploadBody>
iconName={ButtonCancelReactSvgUrl} </ModalDialog.Body>
// color={theme.filesPanels.upload.color} </StyledModal>
isClickable
onClick={
uploaded ? cancelConversion : this.onCancelUpload
}
/>
)}
</div>
</div>
</StyledHeaderContent>
<StyledBody className="upload-panel_body">
<FileList />
</StyledBody>
</StyledContent>
</Aside>
</StyledAsidePanel>
); );
} }
} }

View File

@ -28,6 +28,7 @@ import moment from "moment-timezone";
import { LANGUAGE } from "@docspace/shared/constants"; import { LANGUAGE } from "@docspace/shared/constants";
import { getCookie } from "@docspace/shared/utils"; import { getCookie } from "@docspace/shared/utils";
import { getFeedInfo } from "../views/History/FeedInfo";
export const getRelativeDateDay = (t, date) => { export const getRelativeDateDay = (t, date) => {
moment.locale(getCookie(LANGUAGE)); moment.locale(getCookie(LANGUAGE));
@ -62,44 +63,39 @@ export const getDateTime = (date) => {
return given.format("LT"); return given.format("LT");
}; };
// from { response: { feeds: groupedFeeds: [{ json: "" }], json: "" }} export const addLinksToHistory = (fetchedHistory, links) => {
// to [{ day: "", feeds: [ groupedFeeds: [{ json: {} }], json: {} ]}] if (!fetchedHistory) return null;
if (!links) return fetchedHistory;
export const parseHistory = (t, fetchedHistory) => { const historyWithLinks = fetchedHistory?.items.map((feed) => {
let feeds = fetchedHistory.feeds; const { actionType, targetType } = getFeedInfo(feed);
if (targetType !== "roomExternalLink") return feed;
if (actionType === "rename" || actionType === "delete") return feed;
const link = links.find((link) => link.sharedTo.id === feed.data.id);
if (!link) return feed;
return { ...feed, data: link };
});
return { ...fetchedHistory, items: historyWithLinks };
};
export const parseHistory = (fetchedHistory) => {
if (!fetchedHistory) return null;
let feeds = fetchedHistory?.items;
let parsedFeeds = []; let parsedFeeds = [];
for (let i = 0; i < feeds.length; i++) { for (let i = 0; i < feeds.length; i++) {
const feedsJSON = JSON.parse(feeds[i].json); const feedDay = moment(feeds[i].date).format("YYYY-MM-DD");
const feedDay = getRelativeDateDay(t, feeds[i].modifiedDate);
let newGroupedFeeds = [];
if (feeds[i].groupedFeeds) {
let groupFeeds = feeds[i].groupedFeeds;
for (let j = 0; j < groupFeeds.length; j++)
newGroupedFeeds.push(
!!groupFeeds[j].target
? groupFeeds[j].target
: JSON.parse(groupFeeds[j].json),
);
}
if (parsedFeeds.length && parsedFeeds.at(-1).day === feedDay) if (parsedFeeds.length && parsedFeeds.at(-1).day === feedDay)
parsedFeeds.at(-1).feeds.push({ parsedFeeds.at(-1).feeds.push({ ...feeds[i] });
...feeds[i],
json: feedsJSON,
groupedFeeds: newGroupedFeeds,
});
else else
parsedFeeds.push({ parsedFeeds.push({
day: feedDay, day: feedDay,
feeds: [ feeds: [{ ...feeds[i] }],
{
...feeds[i],
json: feedsJSON,
groupedFeeds: newGroupedFeeds,
},
],
}); });
} }

View File

@ -62,10 +62,18 @@ const InfoPanelBodyContent = ({
const isInsideGroup = getIsGroups() && groupId; const isInsideGroup = getIsGroups() && groupId;
const isGroups = const isGroups =
getIsGroups() || getIsGroups() ||
(isInsideGroup && (!selectedItems.length || !!selectedItems[0].manager)); (isInsideGroup &&
(!selectedItems.length ||
(selectedItems[0]?.membersCount !== null &&
selectedItems[0]?.membersCount !== undefined)));
const isPeople = const isPeople =
getIsPeople() || getIsPeople() ||
(getIsGroups() && !isInsideGroup && !selectedItems[0]?.manager) || (getIsGroups() &&
!isInsideGroup &&
!(
selectedItems[0]?.membersCount !== null &&
selectedItems[0]?.membersCount !== undefined
)) ||
(isInsideGroup && selectedItems.length && !selectedItems[0].manager); (isInsideGroup && selectedItems.length && !selectedItems[0].manager);
const isSeveralItems = props.selectedItems?.length > 1; const isSeveralItems = props.selectedItems?.length > 1;

View File

@ -46,31 +46,6 @@ const StyledHistorySubtitle = styled.div`
color: ${(props) => props.theme.infoPanel.history.subtitleColor}; color: ${(props) => props.theme.infoPanel.history.subtitleColor};
`; `;
const StyledUserNameLink = styled.span`
display: inline-block;
white-space: normal;
margin: 1px 0;
.username {
font-size: 13px;
font-weight: 600;
display: inline-block;
}
.link {
text-decoration: underline;
text-decoration-style: dashed;
text-underline-offset: 2px;
}
.space {
display: inline-block;
width: 4px;
height: 15px;
}
`;
const StyledHistoryBlock = styled.div` const StyledHistoryBlock = styled.div`
width: 100%; width: 100%;
display: flex; display: flex;
@ -121,26 +96,6 @@ const StyledHistoryBlock = styled.div`
} }
} }
} }
${(props) =>
props.isUserAction &&
css`
.info {
flex-direction: row;
flex-wrap: wrap;
.message {
display: inline-block;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 4px;
`
: css`
margin-right: 4px;
`}
}
}
`}
`; `;
const StyledHistoryBlockMessage = styled.div` const StyledHistoryBlockMessage = styled.div`
@ -148,13 +103,14 @@ const StyledHistoryBlockMessage = styled.div`
font-size: 13px; font-size: 13px;
line-height: 20px; line-height: 20px;
display: flex; display: inline-flex;
gap: 4px; gap: 4px;
.main-message { .main-message {
width: max-content; width: max-content;
max-width: 100%; max-width: 100%;
min-width: max-content; min-width: max-content;
padding-inline-end: 4px;
} }
strong { strong {
@ -174,6 +130,44 @@ const StyledHistoryBlockMessage = styled.div`
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
} }
.old-role {
color: ${(props) => props.theme.infoPanel.history.oldRoleColor};
font-weight: 600;
text-decoration: line-through;
}
`;
const StyledHistoryLink = styled.span`
display: inline-block;
white-space: normal;
margin: 1px 0;
.text {
font-size: 13px;
font-weight: 600;
display: inline-block;
}
.link {
text-decoration: underline;
text-decoration-style: dashed;
text-underline-offset: 2px;
}
.space {
display: inline-block;
width: 4px;
height: 15px;
}
`;
const StyledHistoryBlockTagList = styled.div`
margin-top: 8px;
display: flex;
flex-wrap: wrap;
gap: 4px;
`; `;
const StyledHistoryBlockFilesList = styled.div` const StyledHistoryBlockFilesList = styled.div`
@ -183,28 +177,6 @@ const StyledHistoryBlockFilesList = styled.div`
padding: 8px 0; padding: 8px 0;
background: ${(props) => props.theme.infoPanel.history.fileBlockBg}; background: ${(props) => props.theme.infoPanel.history.fileBlockBg};
border-radius: 3px; border-radius: 3px;
.show_more-link {
cursor: pointer;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin: 10px 20px 3px 0;
`
: css`
margin: 10px 0 3px 20px;
`}
font-weight: 400;
font-size: 13px;
line-height: 15px;
strong {
font-weight: 600;
text-decoration: underline;
text-decoration-style: dashed;
text-underline-offset: 2px;
}
}
`; `;
const StyledHistoryBlockFile = styled.div` const StyledHistoryBlockFile = styled.div`
@ -241,6 +213,17 @@ const StyledHistoryBlockFile = styled.div`
flex-shrink: 0; flex-shrink: 0;
color: ${(props) => props.theme.infoPanel.history.fileExstColor}; color: ${(props) => props.theme.infoPanel.history.fileExstColor};
} }
&.old-item-title {
.name {
color: ${(props) => props.theme.infoPanel.history.renamedItemColor};
text-decoration: line-through;
}
.exst {
color: ${(props) => props.theme.infoPanel.history.renamedItemColor};
text-decoration: line-through;
}
}
} }
.location-btn { .location-btn {
@ -256,6 +239,29 @@ const StyledHistoryBlockFile = styled.div`
} }
`; `;
const StyledHistoryBlockExpandLink = styled.div`
cursor: pointer;
font-weight: 400;
font-size: 13px;
line-height: 20px;
&.files-list-expand-link {
margin-top: 8px;
margin-inline-start: 20px;
}
&.user-list-expand-link {
display: inline-block;
}
strong {
font-weight: 600;
text-decoration: underline;
text-decoration-style: dashed;
text-underline-offset: 2px;
}
`;
StyledHistorySubtitle.defaultProps = { theme: Base }; StyledHistorySubtitle.defaultProps = { theme: Base };
StyledHistoryBlock.defaultProps = { theme: Base }; StyledHistoryBlock.defaultProps = { theme: Base };
StyledHistoryBlockMessage.defaultProps = { theme: Base }; StyledHistoryBlockMessage.defaultProps = { theme: Base };
@ -265,9 +271,11 @@ StyledHistoryBlockFile.defaultProps = { theme: Base };
export { export {
StyledHistoryList, StyledHistoryList,
StyledHistorySubtitle, StyledHistorySubtitle,
StyledUserNameLink, StyledHistoryLink,
StyledHistoryBlock, StyledHistoryBlock,
StyledHistoryBlockMessage, StyledHistoryBlockMessage,
StyledHistoryBlockFilesList, StyledHistoryBlockFilesList,
StyledHistoryBlockFile, StyledHistoryBlockFile,
StyledHistoryBlockTagList,
StyledHistoryBlockExpandLink,
}; };

View File

@ -0,0 +1,188 @@
enum FeedAction {
Create = "create",
Upload = "upload",
Update = "update",
Convert = "convert",
Delete = "delete",
Rename = "rename",
Move = "move",
Copy = "copy",
}
enum FeedTarget {
File = "file",
Folder = "folder",
Room = "room",
RoomTag = "roomTag",
RoomLogo = "roomLogo",
RoomExternalLink = "roomExternalLink",
User = "user",
Group = "group",
}
export type AnyFeedInfo = (typeof feedInfo)[number];
export type ActionByTarget<T extends `${FeedTarget}`> = Extract<
AnyFeedInfo,
{ targetType: T }
>["actionType"];
export const feedInfo = [
//
// FILE
{
key: "FileCreated",
targetType: `${FeedTarget.File}`,
actionType: `${FeedAction.Create}`,
},
{
key: "FileUploaded",
targetType: `${FeedTarget.File}`,
actionType: `${FeedAction.Upload}`,
},
{
key: "UserFileUpdated",
targetType: `${FeedTarget.File}`,
actionType: `${FeedAction.Update}`,
},
{
key: "FileConverted",
targetType: `${FeedTarget.File}`,
actionType: `${FeedAction.Convert}`,
},
{
key: "FileRenamed",
targetType: `${FeedTarget.File}`,
actionType: `${FeedAction.Rename}`,
},
{
key: "FileMoved",
targetType: `${FeedTarget.File}`,
actionType: `${FeedAction.Move}`,
},
{
key: "FileCopied",
targetType: `${FeedTarget.File}`,
actionType: `${FeedAction.Copy}`,
},
{
key: "FileDeleted",
targetType: `${FeedTarget.File}`,
actionType: `${FeedAction.Delete}`,
},
//
// FOLDER
{
key: "FolderCreated",
targetType: `${FeedTarget.Folder}`,
actionType: `${FeedAction.Create}`,
},
{
key: "FolderRenamed",
targetType: `${FeedTarget.Folder}`,
actionType: `${FeedAction.Rename}`,
},
{
key: "FolderMoved",
targetType: `${FeedTarget.Folder}`,
actionType: `${FeedAction.Move}`,
},
{
key: "FolderCopied",
targetType: `${FeedTarget.Folder}`,
actionType: `${FeedAction.Copy}`,
},
{
key: "FolderDeleted",
targetType: `${FeedTarget.Folder}`,
actionType: `${FeedAction.Delete}`,
},
//
// ROOM
{
key: "RoomCreated",
targetType: `${FeedTarget.Room}`,
actionType: `${FeedAction.Create}`,
},
{
key: "RoomRenamed",
targetType: `${FeedTarget.Room}`,
actionType: `${FeedAction.Rename}`,
},
// ROOM TAGS
{
key: "AddedRoomTags",
targetType: `${FeedTarget.RoomTag}`,
actionType: `${FeedAction.Create}`,
},
{
key: "DeletedRoomTags",
targetType: `${FeedTarget.RoomTag}`,
actionType: `${FeedAction.Delete}`,
},
// ROOM LOGO
{
key: "RoomLogoCreated",
targetType: `${FeedTarget.RoomLogo}`,
actionType: `${FeedAction.Create}`,
},
{
key: "RoomLogoDeleted",
targetType: `${FeedTarget.RoomLogo}`,
actionType: `${FeedAction.Delete}`,
},
// ROOM EXTERNAL LINK
{
key: "RoomExternalLinkCreated",
targetType: `${FeedTarget.RoomExternalLink}`,
actionType: `${FeedAction.Create}`,
},
{
key: "RoomExternalLinkRenamed",
targetType: `${FeedTarget.RoomExternalLink}`,
actionType: `${FeedAction.Rename}`,
},
{
key: "RoomExternalLinkDeleted",
targetType: `${FeedTarget.RoomExternalLink}`,
actionType: `${FeedAction.Delete}`,
},
//
// USER
{
key: "RoomCreateUser",
targetType: `${FeedTarget.User}`,
actionType: `${FeedAction.Create}`,
},
{
key: "RoomUpdateAccessForUser",
targetType: `${FeedTarget.User}`,
actionType: `${FeedAction.Update}`,
},
{
key: "RoomRemoveUser",
targetType: `${FeedTarget.User}`,
actionType: `${FeedAction.Delete}`,
},
//
// GROUP
{
key: "RoomGroupAdded",
targetType: `${FeedTarget.Group}`,
actionType: `${FeedAction.Create}`,
},
{
key: "RoomUpdateAccessForGroup",
targetType: `${FeedTarget.Group}`,
actionType: `${FeedAction.Update}`,
},
{
key: "RoomGroupRemove",
targetType: `${FeedTarget.Group}`,
actionType: `${FeedAction.Delete}`,
},
] as const;
export const getFeedInfo = (feed: { action: { key: AnyFeedInfo["key"] } }) => {
return feedInfo.find((info) => info.key === feed.action.key)! || {};
};

View File

@ -24,47 +24,23 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // 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 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React from "react";
import AtReactSvgUrl from "PUBLIC_DIR/images/@.react.svg?url"; import AtReactSvgUrl from "PUBLIC_DIR/images/@.react.svg?url";
import { Avatar } from "@docspace/shared/components/avatar"; import { Avatar } from "@docspace/shared/components/avatar";
import { Text } from "@docspace/shared/components/text"; import { Text } from "@docspace/shared/components/text";
import HistoryBlockMessage from "./HistoryBlockMessage";
import HistoryBlockItemList from "./HistoryBlockItemList";
import HistoryBlockUser from "./HistoryBlockUser";
import { FeedItemTypes } from "@docspace/shared/enums";
import DefaultUserAvatarSmall from "PUBLIC_DIR/images/default_user_photo_size_32-32.png"; import DefaultUserAvatarSmall from "PUBLIC_DIR/images/default_user_photo_size_32-32.png";
import { StyledHistoryBlock } from "../../styles/history"; import { StyledHistoryBlock } from "../../styles/history";
import { getDateTime } from "../../helpers/HistoryHelper"; import { getDateTime } from "../../helpers/HistoryHelper";
import { decode } from "he"; import { decode } from "he";
const HistoryBlock = ({ import HistoryBlockContent from "./HistoryBlockContent";
t,
selectionIsFile,
feed,
selectedFolder,
infoPanelSelection,
getInfoPanelItemIcon,
checkAndOpenLocationAction,
openUser,
isVisitor,
isCollaborator,
withFileList,
isLastEntity,
}) => {
const { target, initiator, json, groupedFeeds } = feed;
const users = [target, ...groupedFeeds].filter( const HistoryBlock = ({ t, feed, isLastEntity }) => {
(user, index, self) => const { action, initiator, date } = feed;
self.findIndex((user2) => user2?.id === user?.id) === index,
);
const isUserAction = json.Item === FeedItemTypes.User && target; const isUserAction =
const isItemAction = action.key === "RoomCreateUser" ||
json.Item === FeedItemTypes.File || json.Item === FeedItemTypes.Folder; action.key === "RoomUpdateAccessForUser" ||
action.key === "RoomRemoveUser";
const userAvatar = initiator.hasAvatar
? initiator.avatarSmall
: DefaultUserAvatarSmall;
return ( return (
<StyledHistoryBlock <StyledHistoryBlock
@ -75,11 +51,13 @@ const HistoryBlock = ({
role="user" role="user"
className="avatar" className="avatar"
size="min" size="min"
source={
userAvatar ||
(initiator.displayName ? "" : initiator.email && AtReactSvgUrl)
}
userName={initiator.displayName} userName={initiator.displayName}
source={
initiator.hasAvatar
? initiator.avatarSmall
: DefaultUserAvatarSmall ||
(initiator.displayName ? "" : initiator.email && AtReactSvgUrl)
}
/> />
<div className="info"> <div className="info">
<div className="title"> <div className="title">
@ -93,38 +71,10 @@ const HistoryBlock = ({
{t("Common:Owner").toLowerCase()} {t("Common:Owner").toLowerCase()}
</Text> </Text>
)} )}
<Text className="date">{getDateTime(json.ModifiedDate)}</Text> <Text className="date">{getDateTime(date)}</Text>
</div> </div>
<HistoryBlockMessage <HistoryBlockContent feed={feed} />
t={t}
className="message"
action={json}
groupedActions={groupedFeeds}
selectedFolder={selectedFolder}
infoPanelSelection={infoPanelSelection}
/>
{isItemAction && withFileList && (
<HistoryBlockItemList
t={t}
items={[json, ...groupedFeeds]}
getInfoPanelItemIcon={getInfoPanelItemIcon}
checkAndOpenLocationAction={checkAndOpenLocationAction}
/>
)}
{isUserAction &&
users.map((user, i) => (
<HistoryBlockUser
isVisitor={isVisitor}
isCollaborator={isCollaborator}
key={user.id}
user={user}
withComma={i < users.length - 1}
openUser={openUser}
/>
))}
</div> </div>
</StyledHistoryBlock> </StyledHistoryBlock>
); );

View File

@ -0,0 +1,136 @@
// (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 { useState } from "react";
import { useNavigate, NavigateFunction } from "react-router-dom";
import { decode } from "he";
import { inject, observer } from "mobx-react";
import { Link } from "@docspace/shared/components/link";
import { Text } from "@docspace/shared/components/text";
import { Trans, withTranslation } from "react-i18next";
import { TTranslation } from "@docspace/shared/types";
import {
StyledHistoryBlockExpandLink,
StyledHistoryLink,
} from "../../../styles/history";
const EXPANSION_THRESHOLD = 8;
interface HistoryGroupListProps {
t: TTranslation;
feed: any;
isVisitor?: boolean;
isCollaborator?: boolean;
setPeopleSelection?: (newSelection: any[]) => void;
setPeopleBufferSelection?: (newBufferSelection: any) => void;
setFilesSelection?: (newSelection: any[]) => void;
setFilesBufferSelection?: (newBufferSelection: any) => void;
}
const HistoryGroupList = ({
t,
feed,
isVisitor,
isCollaborator,
setPeopleSelection,
setPeopleBufferSelection,
setFilesSelection,
setFilesBufferSelection,
}: HistoryGroupListProps) => {
const navigate = useNavigate();
const [isExpanded, setIsExpanded] = useState(
1 + feed.related.length <= EXPANSION_THRESHOLD,
);
const onExpand = () => setIsExpanded(true);
const groupsData = [
feed.data,
...feed.related.map((relatedFeed: any) => relatedFeed.data),
];
const onGroupClick = (groupId: string) => {
setPeopleSelection?.([]);
setPeopleBufferSelection?.(null);
setFilesSelection?.([]);
setFilesBufferSelection?.(null);
navigate(`/accounts/groups/${groupId}/filter`);
};
return (
<>
{groupsData.map(({ group }, i) => {
if (!isExpanded && i > EXPANSION_THRESHOLD - 1) return null;
const withComma = !isExpanded
? i < EXPANSION_THRESHOLD - 1
: i < groupsData.length - 1;
return (
<StyledHistoryLink key={group.id}>
{isVisitor || isCollaborator ? (
<Text as="span" className="text" fontWeight={600}>
{decode(group.name)}
</Text>
) : (
<Link
className="text link"
onClick={() => onGroupClick(group.id)}
>
{decode(group.name)}
</Link>
)}
{withComma && ","}
<div className="space" />
</StyledHistoryLink>
);
})}
{!isExpanded && (
<StyledHistoryBlockExpandLink
className="user-list-expand-link"
onClick={onExpand}
>
<Trans
t={t}
ns="InfoPanel"
i18nKey="AndMoreLabel"
values={{ count: groupsData.length - EXPANSION_THRESHOLD }}
components={{ 1: <strong /> }}
/>
</StyledHistoryBlockExpandLink>
)}
</>
);
};
export default inject(({ userStore, peopleStore, filesStore }) => ({
isVisitor: userStore.user.isVisitor,
isCollaborator: userStore.user.isCollaborator,
setPeopleSelection: peopleStore.selectionStore.setSelection,
setPeopleBufferSelection: peopleStore.selectionStore.setBufferSelection,
setFilesSelection: filesStore.setSelection,
setFilesBufferSelection: filesStore.setBufferSelection,
}))(withTranslation(["InfoPanel"])(observer(HistoryGroupList)));

View File

@ -0,0 +1,186 @@
// (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 FolderLocationReactSvgUrl from "PUBLIC_DIR/images/folder-location.react.svg?url";
import { useState } from "react";
import { Trans, withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import { IconButton } from "@docspace/shared/components/icon-button";
import { ReactSVG } from "react-svg";
import { TTranslation } from "@docspace/shared/types";
import { getFileExtension } from "@docspace/shared/utils/common";
import {
StyledHistoryBlockExpandLink,
StyledHistoryBlockFile,
StyledHistoryBlockFilesList,
} from "../../../styles/history";
import { ActionByTarget } from "../FeedInfo";
const EXPANSION_THRESHOLD = 3;
type HistoryItemListProps = {
t: TTranslation;
feed: any;
nameWithoutExtension?: (title: string) => string;
getInfoPanelItemIcon?: (item: any, size: number) => string;
checkAndOpenLocationAction?: (item: any) => void;
} & (
| {
actionType: ActionByTarget<"file">;
targetType: "file";
}
| {
actionType: ActionByTarget<"folder">;
targetType: "folder";
}
);
export const HistoryItemList = ({
t,
feed,
actionType,
targetType,
nameWithoutExtension,
getInfoPanelItemIcon,
checkAndOpenLocationAction,
}: HistoryItemListProps) => {
const [isExpanded, setIsExpanded] = useState(
1 + feed.related.length <= EXPANSION_THRESHOLD,
);
const onExpand = () => setIsExpanded(true);
const items = [
feed.data,
...feed.related.map((relatedFeeds) => relatedFeeds.data),
].map((item) => {
return {
...item,
title: nameWithoutExtension!(item.title || item.newTitle),
fileExst: getFileExtension(item.title || item.newTitle),
isFolder: targetType === "folder",
};
});
const oldItem = actionType === "rename" && {
title: nameWithoutExtension!(feed.data.oldTitle),
fileExst: getFileExtension(feed.data.oldTitle),
};
return (
<StyledHistoryBlockFilesList>
{items.map((item, i) => {
if (!isExpanded && i > EXPANSION_THRESHOLD - 1) return null;
return (
<StyledHistoryBlockFile
isRoom={false}
key={`${feed.action.id}_${item.id}`}
>
<ReactSVG className="icon" src={getInfoPanelItemIcon!(item, 24)} />
<div className="item-title">
{item.title ? (
<>
<span className="name" key="hbil-item-name">
{item.title}
</span>
{item.fileExst && (
<span className="exst" key="hbil-item-exst">
{item.fileExst}
</span>
)}
</>
) : (
<span className="name">{item.fileExst}</span>
)}
</div>
<IconButton
className="location-btn"
iconName={FolderLocationReactSvgUrl}
size="16"
isFill
onClick={() => checkAndOpenLocationAction!(item)}
title={t("Files:OpenLocation")}
/>
</StyledHistoryBlockFile>
);
})}
{actionType === "rename" && oldItem && (
<StyledHistoryBlockFile>
<div style={{ width: "24px", height: "24px" }} />
<div className="item-title old-item-title">
{oldItem.title ? (
<>
<span className="name" key="hbil-item-name">
{oldItem.title}
</span>
{oldItem.fileExst && (
<span className="exst" key="hbil-item-exst">
{oldItem.fileExst}
</span>
)}
</>
) : (
<span className="name">{oldItem.fileExst}</span>
)}
</div>
</StyledHistoryBlockFile>
)}
{!isExpanded && (
<StyledHistoryBlockExpandLink
className="files-list-expand-link"
onClick={onExpand}
>
<Trans
t={t}
ns="InfoPanel"
i18nKey="AndMoreLabel"
values={{ count: items.length - EXPANSION_THRESHOLD }}
components={{ 1: <strong /> }}
/>
</StyledHistoryBlockExpandLink>
)}
</StyledHistoryBlockFilesList>
);
};
export default inject(({ infoPanelStore, filesActionsStore }) => {
const { getInfoPanelItemIcon } = infoPanelStore;
const { nameWithoutExtension, checkAndOpenLocationAction } =
filesActionsStore;
return {
getInfoPanelItemIcon,
nameWithoutExtension,
checkAndOpenLocationAction,
};
})(
withTranslation(["InfoPanel", "Common", "Translations"])(
observer(HistoryItemList),
),
);

View File

@ -24,42 +24,28 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // 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 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React from "react"; import { withTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom"; import { inject, observer } from "mobx-react";
import { decode } from "he"; import { TTranslation } from "@docspace/shared/types";
import { StyledHistoryBlockMessage } from "../../../styles/history";
import { useFeedTranslation } from "../useFeedTranslation";
import { Link } from "@docspace/shared/components/link"; interface HistoryMainTextProps {
import { StyledUserNameLink } from "../../styles/history"; t: TTranslation;
import { Text } from "@docspace/shared/components/text"; feed: any;
const HistoryBlockUser = ({ }
user,
withComma,
openUser,
isVisitor,
isCollaborator,
}) => {
const username = user.displayName;
const navigate = useNavigate();
const onUserClick = () => {
openUser(user, navigate);
};
const HistoryMainText = ({ t, feed }: HistoryMainTextProps) => {
return ( return (
<StyledUserNameLink key={user.id} className="user"> <StyledHistoryBlockMessage className="message">
{isVisitor || isCollaborator ? ( <span className="main-message">{useFeedTranslation(t, feed)}</span>{" "}
<Text as="span" fontWeight={600}> </StyledHistoryBlockMessage>
{decode(username)}
</Text>
) : (
<Link className="username link" onClick={onUserClick}>
{decode(username)}
</Link>
)}
{withComma ? "," : ""}
{withComma && <div className="space"></div>}
</StyledUserNameLink>
); );
}; };
export default HistoryBlockUser; export default inject(({ infoPanelStore }) => {
const { infoPanelSelection } = infoPanelStore;
return {
infoPanelSelection,
};
})(withTranslation(["InfoPanel"])(observer(HistoryMainText)));

View File

@ -24,57 +24,41 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // 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 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React from "react"; import { withTranslation } from "react-i18next";
import { Trans } from "react-i18next"; import { inject, observer } from "mobx-react";
import { TTranslation } from "@docspace/shared/types";
import { StyledHistoryBlockMessage } from "../../../styles/history";
import { FeedActionTypes, FeedItemTypes } from "@docspace/shared/enums"; type HistoryMainTextFolderInfoProps = {
t: TTranslation;
feed: any;
selectedFolderId?: number;
};
import { StyledHistoryBlockMessage } from "../../styles/history"; const HistoryMainTextFolderInfo = ({
import getBlockMessageTranslation from "./HistroryBlockMessageTranslations";
const HistoryBlockMessage = ({
t, t,
action, feed,
groupedActions, selectedFolderId,
selectedFolder, }: HistoryMainTextFolderInfoProps) => {
infoPanelSelection, if (
}) => { feed.data.parentId === selectedFolderId ||
const message = getBlockMessageTranslation( feed.data.toFolderId === selectedFolderId
t, )
action.hasOwnProperty("Action") ? action.Action : action.Actions, return null;
action.Item,
action.Item === FeedItemTypes.File || action.Item === FeedItemTypes.Folder
? !!groupedActions.length
: false,
action.Item === FeedItemTypes.Room
? { roomTitle: action.Title, oldRoomTitle: "" }
: {},
);
const getFolderLabel = () => {
if (action.Item !== "file" && action.Item !== "folder") return "";
const itemLocationId = +action.ExtraLocation;
if (selectedFolder?.id === itemLocationId) return "";
if (infoPanelSelection?.isRoom && infoPanelSelection?.id === itemLocationId)
return "";
const folderTitle = action.ExtraLocationTitle;
if (!folderTitle) return "";
return (
<span className="folder-label">
{` ${t("FeedLocationLabel", { folderTitle })}`}
</span>
);
};
return ( return (
<StyledHistoryBlockMessage className="message"> <StyledHistoryBlockMessage className="message">
<span className="main-message">{message}</span> <span className="folder-label">
{getFolderLabel()} {` ${t("FeedLocationLabel", { folderTitle: feed.data.parentTitle || feed.data.toFolderTitle })}`}
</span>
</StyledHistoryBlockMessage> </StyledHistoryBlockMessage>
); );
}; };
export default HistoryBlockMessage; export default inject(({ selectedFolderStore }) => ({
selectedFolderId: selectedFolderStore.id,
}))(
withTranslation(["InfoPanel", "Common", "Translations"])(
observer(HistoryMainTextFolderInfo),
),
);

View File

@ -0,0 +1,79 @@
// (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 { inject, observer } from "mobx-react";
import { StyledHistoryLink } from "../../../styles/history";
import { ActionByTarget } from "../FeedInfo";
import { decode } from "he";
import { Link } from "@docspace/shared/components/link";
import { toastr } from "@docspace/shared/components/toast";
import { withTranslation } from "react-i18next";
import { Text } from "@docspace/shared/components/text";
interface HistoryRoomExternalLinkProps {
feed: any;
actionType: ActionByTarget<"roomTag">;
}
const HistoryRoomExternalLink = ({
t,
feed,
actionType,
canEditLink,
setEditLinkPanelIsVisible,
setLinkParams,
}: HistoryRoomExternalLinkProps) => {
const onEditLink = () => {
if (!feed.data.sharedTo) {
toastr.error(t("FeedLinkWasDeleted"));
return;
}
setLinkParams({ isEdit: true, link: feed.data });
setEditLinkPanelIsVisible(true);
};
if (actionType === "create")
return (
<StyledHistoryLink>
{canEditLink ? (
<Link className="text link" onClick={onEditLink}>
{decode(feed.data.title || feed.data.sharedTo?.title)}
</Link>
) : (
<Text as="span" className="text">
{decode(feed.data.title || feed.data.sharedTo?.title)}
</Text>
)}
</StyledHistoryLink>
);
};
export default inject(({ userStore, dialogsStore }) => ({
canEditLink: !(userStore.user.isVisitor || userStore.user.isCollaborator),
setEditLinkPanelIsVisible: dialogsStore.setEditLinkPanelIsVisible,
setLinkParams: dialogsStore.setLinkParams,
}))(withTranslation(["InfoPanel"])(observer(HistoryRoomExternalLink)));

View File

@ -0,0 +1,69 @@
// (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 { StyledHistoryBlockTagList } from "../../../styles/history";
import { ActionByTarget } from "../FeedInfo";
import { Tag } from "@docspace/shared/components/tag";
interface HistoryRoomTagListProps {
feed: any;
actionType: ActionByTarget<"roomTag">;
}
const HistoryRoomTagList = ({ feed, actionType }: HistoryRoomTagListProps) => {
if (actionType === "create")
return (
<StyledHistoryBlockTagList>
{feed.data.tags.map((tag: string) => (
<Tag
className="history-tag"
key={tag}
label={tag}
tag={tag}
isNewTag
/>
))}
</StyledHistoryBlockTagList>
);
if (actionType === "delete") {
return (
<StyledHistoryBlockTagList>
{feed.data.tags.map((tag: string) => (
<Tag
className="history-tag deleted-tag"
key={tag}
label={tag}
tag={tag}
isDeleted
/>
))}
</StyledHistoryBlockTagList>
);
}
};
export default HistoryRoomTagList;

View File

@ -0,0 +1,44 @@
// (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 { StyledHistoryBlockMessage } from "../../../styles/history";
interface HistoryUserRoleChangeProps {
feed: any;
}
const HistoryUserGroupRoleChange = ({ feed }: HistoryUserRoleChangeProps) => {
return (
<StyledHistoryBlockMessage className="message">
<span className="main-message">
<strong>«{feed.data.access}»</strong>
</span>{" "}
<span className="old-role">«{feed.data.oldAccess}»</span>
</StyledHistoryBlockMessage>
);
};
export default HistoryUserGroupRoleChange;

View File

@ -0,0 +1,118 @@
// (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 { useState } from "react";
import { useNavigate, NavigateFunction } from "react-router-dom";
import { decode } from "he";
import { inject, observer } from "mobx-react";
import { Link } from "@docspace/shared/components/link";
import { Text } from "@docspace/shared/components/text";
import {
StyledHistoryBlockExpandLink,
StyledHistoryLink,
} from "../../../styles/history";
import { Trans, withTranslation } from "react-i18next";
const EXPANSION_THRESHOLD = 8;
interface HistoryUserListProps {
t;
feed: any;
openUser?: (user: any, navigate: NavigateFunction) => void;
isVisitor?: boolean;
isCollaborator?: boolean;
}
const HistoryUserList = ({
t,
feed,
openUser,
isVisitor,
isCollaborator,
}: HistoryUserListProps) => {
const navigate = useNavigate();
const [isExpanded, setIsExpanded] = useState(
feed.related.length + 1 <= EXPANSION_THRESHOLD,
);
const onExpand = () => setIsExpanded(true);
const usersData = [
feed.data,
...feed.related.map((relatedFeed: any) => relatedFeed.data),
];
return (
<>
{usersData.map(({ user }, i) => {
if (!isExpanded && i > EXPANSION_THRESHOLD - 1) return null;
const withComma = !isExpanded
? i < EXPANSION_THRESHOLD - 1
: i < usersData.length - 1;
return (
<StyledHistoryLink key={user.id}>
{isVisitor || isCollaborator ? (
<Text as="span" className="text">
{decode(user.displayName)}
</Text>
) : (
<Link
className="text link"
onClick={() => openUser!(user, navigate)}
>
{decode(user.displayName)}
</Link>
)}
{withComma && ","}
<div className="space" />
</StyledHistoryLink>
);
})}
{!isExpanded && (
<StyledHistoryBlockExpandLink
className="user-list-expand-link"
onClick={onExpand}
>
<Trans
t={t}
ns="InfoPanel"
i18nKey="AndMoreLabel"
values={{ count: usersData.length - EXPANSION_THRESHOLD }}
components={{ 1: <strong /> }}
/>
</StyledHistoryBlockExpandLink>
)}
</>
);
};
export default inject(({ infoPanelStore, userStore }) => ({
openUser: infoPanelStore.openUser,
isVisitor: userStore.user.isVisitor,
isCollaborator: userStore.user.isCollaborator,
}))(withTranslation(["InfoPanel"])(observer(HistoryUserList)));

View File

@ -0,0 +1,98 @@
// (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 { inject, observer } from "mobx-react";
import HistoryUserList from "./UserList";
import HistoryMainText from "./MainText";
import HistoryItemList from "./ItemList";
import HistoryMainTextFolderInfo from "./MainTextFolderInfo";
import HistoryRoomExternalLink from "./RoomExternalLink";
import HistoryGroupList from "./GroupList";
import HistoryUserGroupRoleChange from "./UserGroupRoleChange";
import HistoryRoomTagList from "./RoomTagList";
import { getFeedInfo } from "../FeedInfo";
interface HistoryBlockContentProps {
feed: any;
}
const HistoryBlockContent = ({
feed,
historyWithFileList,
}: HistoryBlockContentProps) => {
const { actionType, targetType } = getFeedInfo(feed);
return (
<>
{targetType === "user" && actionType === "update" && (
<HistoryUserList feed={feed} />
)}
{targetType === "group" && actionType === "update" && (
<HistoryGroupList feed={feed} />
)}
<HistoryMainText feed={feed} />
{(targetType === "file" || targetType === "folder") &&
actionType !== "delete" && <HistoryMainTextFolderInfo feed={feed} />}
{(targetType === "file" || targetType === "folder") &&
(actionType === "rename" || historyWithFileList) && (
<HistoryItemList
feed={feed}
actionType={actionType}
targetType={targetType}
/>
)}
{targetType === "roomTag" && (
<HistoryRoomTagList feed={feed} actionType={actionType} />
)}
{targetType === "roomExternalLink" && actionType === "create" && (
<HistoryRoomExternalLink feed={feed} actionType={actionType} />
)}
{targetType === "user" && actionType !== "update" && (
<HistoryUserList feed={feed} />
)}
{targetType === "group" && actionType !== "update" && (
<HistoryGroupList feed={feed} />
)}
{(targetType === "user" || targetType === "group") &&
actionType === "update" && <HistoryUserGroupRoleChange feed={feed} />}
</>
);
};
export default inject(({ infoPanelStore }) => {
const { historyWithFileList } = infoPanelStore;
return { historyWithFileList };
})(observer(HistoryBlockContent));

View File

@ -1,130 +0,0 @@
// (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 FolderLocationReactSvgUrl from "PUBLIC_DIR/images/folder-location.react.svg?url";
import React, { useState } from "react";
import { Trans } from "react-i18next";
import { Text } from "@docspace/shared/components/text";
import { IconButton } from "@docspace/shared/components/icon-button";
import { ReactSVG } from "react-svg";
import {
StyledHistoryBlockFile,
StyledHistoryBlockFilesList,
} from "../../styles/history";
import { RoomsType } from "@docspace/shared/enums";
export const HistoryBlockItemList = ({
t,
items,
getInfoPanelItemIcon,
checkAndOpenLocationAction,
}) => {
const [isShowMore, setIsShowMore] = useState(items.length <= 3);
const onShowMore = () => setIsShowMore(true);
const parsedItems = items.map((item) => {
const indexPoint = item.Title.lastIndexOf(".");
const splitTitle = item.Title.split(".");
const splitTitleLength = splitTitle.length;
const fileExst =
splitTitleLength !== 1 ? `.${splitTitle[splitTitleLength - 1]}` : null;
const title =
splitTitleLength <= 2 ? splitTitle[0] : item.Title.slice(0, indexPoint);
return {
...item,
isRoom: item.Item === "room",
isFolder: item.Item === "folder",
roomType: RoomsType[item.AdditionalInfo],
title,
fileExst,
id: item.ItemId.split("_")[0],
viewUrl: item.itemId,
};
});
// If server returns two instances of the same item by mistake filters it out
const includedIds = [];
const filteredParsedItems = parsedItems.filter((item) => {
if (includedIds.indexOf(item.id) > -1) return false;
includedIds.push(item.id);
return true;
});
return (
<StyledHistoryBlockFilesList>
{filteredParsedItems.map((item, i) => {
includedIds.push(item);
if (!isShowMore && i > 2) return null;
return (
<StyledHistoryBlockFile isRoom={item.isRoom} key={item.id + "__" + i}>
<ReactSVG className="icon" src={getInfoPanelItemIcon(item, 24)} />
<div className="item-title">
{item.title ? (
[
<span className="name" key="hbil-item-name">
{item.title}
</span>,
item.fileExst && (
<span className="exst" key="hbil-item-exst">
{item.fileExst}
</span>
),
]
) : (
<span className="name">{item.fileExst}</span>
)}
</div>
<IconButton
className="location-btn"
iconName={FolderLocationReactSvgUrl}
size="16"
isFill={true}
onClick={() => checkAndOpenLocationAction(item)}
title={t("Files:OpenLocation")}
/>
</StyledHistoryBlockFile>
);
})}
{!isShowMore && (
<Text className="show_more-link" onClick={onShowMore}>
<Trans
t={t}
ns="InfoPanel"
i18nKey="AndMoreLabel"
values={{ count: items.length - 3 }}
components={{ bold: <strong /> }}
/>
</Text>
)}
</StyledHistoryBlockFilesList>
);
};
export default HistoryBlockItemList;

View File

@ -1,164 +0,0 @@
// (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 from "react";
import { Trans } from "react-i18next";
const { FeedActionTypes, FeedItemTypes } = require("@docspace/shared/enums");
const getBlockMessageTranslation = (
t,
actionType,
itemType,
isSeveral,
data,
) => {
const keys = [
// FILE //
{
key: t("FeedCreateFileSingle", data),
actionType: FeedActionTypes.Create,
itemType: FeedItemTypes.File,
isSeveral: false,
},
{
key: t("FeedCreateFileSeveral", data),
actionType: FeedActionTypes.Create,
itemType: FeedItemTypes.File,
isSeveral: true,
},
{
key: t("FeedUpdateFile", data),
actionType: FeedActionTypes.Update,
itemType: FeedItemTypes.File,
},
{
key: t("FeedRenameFile", data),
actionType: FeedActionTypes.Rename,
itemType: FeedItemTypes.File,
},
{
key: t("FeedMoveFile", data),
actionType: FeedActionTypes.Move,
itemType: FeedItemTypes.File,
},
{
key: t("FeedDeleteFile", data),
actionType: FeedActionTypes.Delete,
itemType: FeedItemTypes.File,
},
// FOLDER //
{
key: t("FeedCreateFolderSingle", data),
actionType: FeedActionTypes.Create,
itemType: FeedItemTypes.Folder,
isSeveral: false,
},
{
key: t("FeedCreateFolderSeveral", data),
actionType: FeedActionTypes.Create,
itemType: FeedItemTypes.Folder,
isSeveral: true,
},
{
key: t("FeedRenameFolder", data),
actionType: FeedActionTypes.Rename,
itemType: FeedItemTypes.Folder,
},
{
key: t("FeedMoveFolder", data),
actionType: FeedActionTypes.Move,
itemType: FeedItemTypes.Folder,
},
{
key: t("FeedDeleteFolder", data),
actionType: FeedActionTypes.Delete,
itemType: FeedItemTypes.Folder,
},
// ROOM //
{
key: t("FeedCreateRoom", data),
actionType: FeedActionTypes.Create,
itemType: FeedItemTypes.Room,
},
{
key: t("FeedRenameRoom", data),
actionType: FeedActionTypes.Rename,
itemType: FeedItemTypes.Room,
},
{
key: t("FeedUpdateRoom", data),
actionType: FeedActionTypes.Update,
itemType: FeedItemTypes.Room,
},
// TAG //
{
key: t("FeedCreateRoomTag", data),
actionType: FeedActionTypes.Create,
itemType: FeedItemTypes.Tag,
},
{
key: t("FeedDeleteRoomTag", data),
actionType: FeedActionTypes.Delete,
itemType: FeedItemTypes.Tag,
},
// USER //
{
key: t("FeedCreateUser", data),
actionType: FeedActionTypes.Create,
itemType: FeedItemTypes.User,
},
{
key: t("FeedUpdateUser", data),
actionType: FeedActionTypes.Update,
itemType: FeedItemTypes.User,
},
{
key: t("FeedDeleteUser", data),
actionType: FeedActionTypes.Delete,
itemType: FeedItemTypes.User,
},
];
const [result] = keys.filter(
(key) =>
key.actionType === actionType &&
key.itemType === itemType &&
!!key.isSeveral === isSeveral,
);
if (!result) return `${actionType} ${itemType}`;
return (
<Trans
t={t}
ns="InfoPanel"
i18nKey={result.key}
components={{ bold: <strong /> }}
/>
);
};
export default getBlockMessageTranslation;

View File

@ -31,7 +31,7 @@ import { withTranslation } from "react-i18next";
import { StyledHistoryList, StyledHistorySubtitle } from "../../styles/history"; import { StyledHistoryList, StyledHistorySubtitle } from "../../styles/history";
import InfoPanelViewLoader from "@docspace/shared/skeletons/info-panel/body"; import InfoPanelViewLoader from "@docspace/shared/skeletons/info-panel/body";
import { parseHistory } from "./../../helpers/HistoryHelper"; import { getRelativeDateDay } from "./../../helpers/HistoryHelper";
import HistoryBlock from "./HistoryBlock"; import HistoryBlock from "./HistoryBlock";
import NoHistory from "../NoItem/NoHistory"; import NoHistory from "../NoItem/NoHistory";
@ -40,10 +40,9 @@ const History = ({
historyWithFileList, historyWithFileList,
selectedFolder, selectedFolder,
selectionHistory, selectionHistory,
setSelectionHistory,
infoPanelSelection, infoPanelSelection,
getInfoPanelItemIcon, getInfoPanelItemIcon,
getHistory, fetchHistory,
checkAndOpenLocationAction, checkAndOpenLocationAction,
openUser, openUser,
isVisitor, isVisitor,
@ -52,46 +51,30 @@ const History = ({
const isMount = useRef(true); const isMount = useRef(true);
const abortControllerRef = useRef(new AbortController()); const abortControllerRef = useRef(new AbortController());
const [isPending, startTransition] = useTransition();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isShowLoader, setIsShowLoader] = useState(false); const [isShowLoader, setIsShowLoader] = useState(false);
const fetchHistory = async (item) => { const getHistory = async (item) => {
if (!item?.id) return; if (!item?.id) return;
if (isLoading) { if (isLoading) {
abortControllerRef.current?.abort(); abortControllerRef.current?.abort();
abortControllerRef.current = new AbortController(); abortControllerRef.current = new AbortController();
} else setIsLoading(true); } else setIsLoading(true);
let module = "files"; if (isMount.current) {
if (infoPanelSelection.isRoom) module = "rooms"; fetchHistory(abortControllerRef.current?.signal).finally(() => {
else if (infoPanelSelection.isFolder) module = "folders";
getHistory(
module,
item.id,
abortControllerRef.current?.signal,
item?.requestToken,
)
.then((data) => {
if (isMount.current)
startTransition(() => {
const parsedHistory = parseHistory(t, data);
setSelectionHistory(parsedHistory);
});
})
.catch((err) => {
if (err.message !== "canceled") console.error(err);
})
.finally(() => {
if (isMount.current) setIsLoading(false); if (isMount.current) setIsLoading(false);
}); });
}
}; };
useEffect(() => { useEffect(() => {
if (!isMount.current) return; if (!isMount.current) return;
fetchHistory(infoPanelSelection); getHistory(infoPanelSelection);
}, [infoPanelSelection.id]); }, [
infoPanelSelection.id,
infoPanelSelection.isFolder || infoPanelSelection.isRoom,
]);
useEffect(() => { useEffect(() => {
const showLoaderTimer = setTimeout(() => setIsShowLoader(true), 500); const showLoaderTimer = setTimeout(() => setIsShowLoader(true), 500);
@ -111,10 +94,12 @@ const History = ({
return ( return (
<StyledHistoryList> <StyledHistoryList>
{selectionHistory.map(({ day, feeds }) => [ {selectionHistory.map(({ day, feeds }) => [
<StyledHistorySubtitle key={day}>{day}</StyledHistorySubtitle>, <StyledHistorySubtitle key={day}>
{getRelativeDateDay(t, feeds[0].date)}
</StyledHistorySubtitle>,
...feeds.map((feed, i) => ( ...feeds.map((feed, i) => (
<HistoryBlock <HistoryBlock
key={feed.json.Id} key={`${feed.action.id}_${feed.date}`}
t={t} t={t}
feed={feed} feed={feed}
selectedFolder={selectedFolder} selectedFolder={selectedFolder}
@ -134,24 +119,17 @@ const History = ({
}; };
export default inject( export default inject(
({ ({ settingsStore, filesActionsStore, infoPanelStore, userStore }) => {
settingsStore,
filesStore,
filesActionsStore,
infoPanelStore,
userStore,
}) => {
const { const {
infoPanelSelection, infoPanelSelection,
fetchHistory,
selectionHistory, selectionHistory,
setSelectionHistory,
historyWithFileList, historyWithFileList,
getInfoPanelItemIcon, getInfoPanelItemIcon,
openUser, openUser,
} = infoPanelStore; } = infoPanelStore;
const { culture } = settingsStore; const { culture } = settingsStore;
const { getHistory } = filesStore;
const { checkAndOpenLocationAction } = filesActionsStore; const { checkAndOpenLocationAction } = filesActionsStore;
const { user } = userStore; const { user } = userStore;
@ -161,11 +139,10 @@ export default inject(
return { return {
culture, culture,
selectionHistory, selectionHistory,
setSelectionHistory,
historyWithFileList, historyWithFileList,
infoPanelSelection, infoPanelSelection,
getInfoPanelItemIcon, getInfoPanelItemIcon,
getHistory, fetchHistory,
checkAndOpenLocationAction, checkAndOpenLocationAction,
openUser, openUser,
isVisitor, isVisitor,

View File

@ -0,0 +1,109 @@
import { TTranslation } from "@docspace/shared/types";
import { Trans } from "react-i18next";
import { AnyFeedInfo } from "./FeedInfo";
export const useFeedTranslation = (
t: TTranslation,
feed: { action: { key: AnyFeedInfo["key"] }; data: any },
) => {
switch (feed.action.key) {
case "FileCreated":
return t("InfoPanel:FileCreated");
case "FileUploaded":
return t("InfoPanel:FileUploaded");
case "UserFileUpdated":
return t("InfoPanel:UserFileUpdated");
case "FileConverted":
return t("InfoPanel:FileConverted");
case "FileRenamed":
return t("InfoPanel:FileRenamed");
case "FileMoved":
return t("InfoPanel:FileMoved");
case "FileCopied":
return t("InfoPanel:FileCopied");
case "FileDeleted":
return t("InfoPanel:FileDeleted");
case "FolderCreated":
return t("InfoPanel:FolderCreated");
case "FolderRenamed":
return t("InfoPanel:FolderRenamed");
case "FolderMoved":
return t("InfoPanel:FolderMoved");
case "FolderCopied":
return t("InfoPanel:FolderCopied");
case "FolderDeleted":
return t("InfoPanel:FolderDeleted");
case "RoomCreated":
return (
<Trans
t={t}
ns="InfoPanel"
i18nKey="RoomCreated"
values={{ roomTitle: feed.data.title }}
components={{ 1: <strong /> }}
/>
);
case "RoomRenamed":
return (
<Trans
t={t}
ns="InfoPanel"
i18nKey="RoomRenamed"
values={{
roomTitle: feed.data.newTitle,
oldRoomTitle: feed.data.oldTitle,
}}
components={{ 1: <strong /> }}
/>
);
case "AddedRoomTags":
return t("InfoPanel:AddedRoomTags");
case "DeletedRoomTags":
return t("InfoPanel:DeletedRoomTags");
case "RoomLogoCreated":
return t("InfoPanel:RoomLogoCreated");
case "RoomLogoDeleted":
return t("InfoPanel:RoomLogoDeleted");
case "RoomExternalLinkCreated":
return t("InfoPanel:RoomExternalLinkCreated");
case "RoomExternalLinkRenamed":
return (
<Trans
t={t}
ns="InfoPanel"
i18nKey="RoomExternalLinkRenamed"
values={{
linkTitle: feed.data.title,
oldLinkTitle: feed.data.oldTitle,
}}
components={{ 1: <strong /> }}
/>
);
case "RoomExternalLinkDeleted":
return (
<Trans
t={t}
ns="InfoPanel"
i18nKey="RoomExternalLinkDeleted"
values={{
linkTitle: feed.data.title,
}}
components={{ 1: <strong /> }}
/>
);
case "RoomCreateUser":
return t("InfoPanel:RoomCreateUser");
case "RoomUpdateAccessForUser":
return t("InfoPanel:RoomUpdateAccessForUser");
case "RoomRemoveUser":
return t("InfoPanel:RoomRemoveUser");
case "RoomGroupAdded":
return t("InfoPanel:RoomGroupAdded");
case "RoomUpdateAccessForGroup":
return t("InfoPanel:RoomUpdateAccessForGroup");
case "RoomGroupRemove":
return t("InfoPanel:RoomGroupRemove");
default:
return null;
}
};

View File

@ -509,6 +509,7 @@ const InsideGroupTableRow = (props) => {
className="table-cell_username" className="table-cell_username"
noHover noHover
dir="auto" dir="auto"
truncate={true}
> >
{statusType === "pending" {statusType === "pending"
? email ? email

View File

@ -512,6 +512,7 @@ const PeopleTableRow = (props) => {
className="table-cell_username" className="table-cell_username"
noHover noHover
dir="auto" dir="auto"
truncate={true}
> >
{statusType === "pending" {statusType === "pending"
? email ? email

View File

@ -880,7 +880,7 @@ const SectionHeaderContent = (props) => {
: isRootFolder || isAccountsPage || isSettingsPage; : isRootFolder || isAccountsPage || isSettingsPage;
const getInsideGroupTitle = () => { const getInsideGroupTitle = () => {
return isLoading || !currentGroup?.name return isLoading && insideGroupTempTitle
? insideGroupTempTitle ? insideGroupTempTitle
: currentGroup?.name; : currentGroup?.name;
}; };

View File

@ -27,6 +27,7 @@ import { useState, useEffect } from "react";
import { withTranslation } from "react-i18next"; import { withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react"; import { inject, observer } from "mobx-react";
import { useNavigate, useLocation } from "react-router-dom"; import { useNavigate, useLocation } from "react-router-dom";
import { isMobile } from "react-device-detect";
import { Text } from "@docspace/shared/components/text"; import { Text } from "@docspace/shared/components/text";
import { HelpButton } from "@docspace/shared/components/help-button"; import { HelpButton } from "@docspace/shared/components/help-button";
@ -309,7 +310,7 @@ const WhiteLabel = (props) => {
isDisabled={!isSettingPaid} isDisabled={!isSettingPaid}
isReadOnly={!isSettingPaid} isReadOnly={!isSettingPaid}
scale={true} scale={true}
isAutoFocussed={true} isAutoFocussed={!isMobile}
tabIndex={1} tabIndex={1}
maxLength={30} maxLength={30}
/> />

View File

@ -172,12 +172,7 @@ class CommonStore {
}; };
applyNewLogos = (logos) => { applyNewLogos = (logos) => {
const theme = const theme = this.settingsStore.theme.isBase ? "light" : "dark";
typeof window !== "undefined" &&
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
const favicon = document.getElementById("favicon"); const favicon = document.getElementById("favicon");
const logo = document.getElementsByClassName("logo-icon_svg")?.[0]; const logo = document.getElementsByClassName("logo-icon_svg")?.[0];

View File

@ -1443,17 +1443,19 @@ class FilesActionStore {
setIsSectionFilterLoading(param); setIsSectionFilterLoading(param);
}; };
const { ExtraLocationTitle, ExtraLocation, fileExst, folderId } = item; const { title, fileExst } = item;
const parentId = item.parentId || item.toFolderId || recycleBinFolderId;
const parentTitle = item.parentTitle || item.toFolderTitle;
const isRoot = const isRoot = [
ExtraLocation === myRoomsId || myRoomsId,
ExtraLocation === myFolderId || myFolderId,
ExtraLocation === archiveRoomsId || archiveRoomsId,
ExtraLocation === recycleBinFolderId; recycleBinFolderId,
].includes(parentId);
const state = { const state = {
title: ExtraLocationTitle, title: parentTitle,
isRoot, isRoot,
fileExst, fileExst,
highlightFileId: item.id, highlightFileId: item.id,
@ -1461,14 +1463,12 @@ class FilesActionStore {
rootFolderType, rootFolderType,
}; };
const url = getCategoryUrl(categoryType, ExtraLocation); const url = getCategoryUrl(categoryType, parentId);
const newFilter = FilesFilter.getDefault(); const newFilter = FilesFilter.getDefault();
const title = this.nameWithoutExtension(item.title);
newFilter.search = title; newFilter.search = title;
newFilter.folder = ExtraLocation || folderId; newFilter.folder = parentId;
setIsLoading( setIsLoading(
window.DocSpace.location.search !== `?${newFilter.toUrlParams()}` || window.DocSpace.location.search !== `?${newFilter.toUrlParams()}` ||

View File

@ -67,6 +67,7 @@ import { CategoryType } from "SRC_DIR/helpers/constants";
import debounce from "lodash.debounce"; import debounce from "lodash.debounce";
import clone from "lodash/clone"; import clone from "lodash/clone";
import Queue from "queue-promise"; import Queue from "queue-promise";
import { parseHistory } from "SRC_DIR/pages/Home/InfoPanel/Body/helpers/HistoryHelper";
const { FilesFilter, RoomsFilter } = api; const { FilesFilter, RoomsFilter } = api;
const storageViewAs = localStorage.getItem("viewAs"); const storageViewAs = localStorage.getItem("viewAs");
@ -274,6 +275,19 @@ class FilesStore {
this.treeFoldersStore.updateTreeFoldersItem(opt); this.treeFoldersStore.updateTreeFoldersItem(opt);
}); });
socketHelper.on("s:update-history", ({ id, type }) => {
const { infoPanelSelection, fetchHistory } = this.infoPanelStore;
let infoPanelSelectionType = "file";
if (infoPanelSelection?.isRoom || infoPanelSelection?.isFolder)
infoPanelSelectionType = "folder";
if (id === infoPanelSelection?.id && type === infoPanelSelectionType) {
console.log("[WS] s:update-history", id);
fetchHistory();
}
});
socketHelper.on("refresh-folder", (id) => { socketHelper.on("refresh-folder", (id) => {
const { socketSubscribers } = socketHelper; const { socketSubscribers } = socketHelper;
const pathParts = `DIR-${id}`; const pathParts = `DIR-${id}`;
@ -1397,6 +1411,13 @@ class FilesStore {
return res; return res;
}; };
abortAllFetch = () => {
this.filesController.abort();
this.roomsController.abort();
this.filesController = new AbortController();
this.roomsController = new AbortController();
};
fetchFiles = ( fetchFiles = (
folderId, folderId,
filter, filter,
@ -1405,9 +1426,9 @@ class FilesStore {
clearSelection = true, clearSelection = true,
) => { ) => {
const { setSelectedNode } = this.treeFoldersStore; const { setSelectedNode } = this.treeFoldersStore;
if (this.clientLoadingStore.isLoading) { if (this.clientLoadingStore.isLoading) {
this.roomsController.abort(); this.abortAllFetch();
this.roomsController = new AbortController();
} }
const filterData = filter ? filter.clone() : FilesFilter.getDefault(); const filterData = filter ? filter.clone() : FilesFilter.getDefault();
@ -1728,8 +1749,7 @@ class FilesStore {
const { setSelectedNode, roomsFolderId } = this.treeFoldersStore; const { setSelectedNode, roomsFolderId } = this.treeFoldersStore;
if (this.clientLoadingStore.isLoading) { if (this.clientLoadingStore.isLoading) {
this.filesController.abort(); this.abortAllFetch();
this.filesController = new AbortController();
} }
const filterData = !!filter const filterData = !!filter
@ -2723,8 +2743,8 @@ class FilesStore {
return api.rooms.updateRoomMemberRole(id, data); return api.rooms.updateRoomMemberRole(id, data);
} }
getHistory(module, id, signal = null, requestToken) { getHistory(selectionType, id, signal = null, requestToken) {
return api.rooms.getHistory(module, id, signal, requestToken); return api.rooms.getHistory(selectionType, id, signal, requestToken);
} }
getRoomHistory(id) { getRoomHistory(id) {

View File

@ -546,11 +546,17 @@ class GroupsStore {
withBackURL: boolean, withBackURL: boolean,
tempTitle: string, tempTitle: string,
) => { ) => {
const { setIsSectionBodyLoading, setIsSectionFilterLoading } =
this.clientLoadingStore;
this.setSelection([]); this.setSelection([]);
this.setBufferSelection(null); this.setBufferSelection(null);
this.setCurrentGroup(null); this.setCurrentGroup(null);
this.setInsideGroupTempTitle(tempTitle); this.setInsideGroupTempTitle(tempTitle);
setIsSectionFilterLoading(true);
setIsSectionBodyLoading(true);
if (withBackURL) { if (withBackURL) {
const url = `${window.location.pathname}${window.location.search}`; const url = `${window.location.pathname}${window.location.search}`;
this.setInsideGroupBackUrl(url); this.setInsideGroupBackUrl(url);
@ -592,6 +598,7 @@ class GroupsStore {
this.insideGroupFilter.clone(), this.insideGroupFilter.clone(),
); );
this.peopleStore.usersStore.setUsers(members.items); this.peopleStore.usersStore.setUsers(members.items);
this.setInsideGroupTempTitle(res.name);
} }
if (infoPanelSelection?.id === res.id) { if (infoPanelSelection?.id === res.id) {

View File

@ -50,6 +50,10 @@ import {
} from "@docspace/shared/api/files"; } from "@docspace/shared/api/files";
import isEqual from "lodash/isEqual"; import isEqual from "lodash/isEqual";
import { getUserStatus } from "SRC_DIR/helpers/people-helpers"; import { getUserStatus } from "SRC_DIR/helpers/people-helpers";
import {
addLinksToHistory,
parseHistory,
} from "SRC_DIR/pages/Home/InfoPanel/Body/helpers/HistoryHelper";
const observedKeys = [ const observedKeys = [
"id", "id",
@ -715,6 +719,45 @@ class InfoPanelStore {
this.setIsMembersPanelUpdating(false); this.setIsMembersPanelUpdating(false);
}; };
fetchHistory = async (abortControllerSignal = null) => {
const { getHistory, getRoomLinks } = this.filesStore;
const { setExternalLinks } = this.publicRoomStore;
let selectionType = "file";
if (this.infoPanelSelection.isRoom || this.infoPanelSelection.isFolder)
selectionType = "folder";
const withLinks =
this.infoPanelSelection.isRoom &&
[RoomsType.FormRoom, RoomsType.CustomRoom, RoomsType.PublicRoom].includes(
this.infoPanelSelection.roomType,
);
return getHistory(
selectionType,
this.infoPanelSelection.id,
abortControllerSignal,
this.infoPanelSelection?.requestToken,
)
.then(async (data) => {
if (withLinks) {
const links = await getRoomLinks(this.infoPanelSelection.id);
const historyWithLinks = addLinksToHistory(data, links);
setExternalLinks(links);
return historyWithLinks;
}
return data;
})
.then((data) => {
const parsedSelectionHistory = parseHistory(data);
this.setSelectionHistory(parsedSelectionHistory);
return parsedSelectionHistory;
})
.catch((err) => {
if (err.message !== "canceled") console.error(err);
});
};
openShareTab = () => { openShareTab = () => {
this.setView("info_share"); this.setView("info_share");
this.isVisible = true; this.isVisible = true;

View File

@ -46,8 +46,15 @@ class SelectionStore {
} }
updateSelection = (peopleList) => { updateSelection = (peopleList) => {
const hasSelection = !!this.selection.length;
const hasBufferSelection = !!this.bufferSelection;
peopleList.some((el) => { peopleList.some((el) => {
if (el.id === this.selection[0].id) this.setSelection([el]); if (hasSelection && this.selection[0].id === el.id)
this.setSelection([el]);
if (hasBufferSelection && this.bufferSelection.id === el.id)
this.setBufferSelection(el);
}); });
}; };
@ -303,6 +310,10 @@ class SelectionStore {
return this.selection.length > 0 && this.selection.length === 1; return this.selection.length > 0 && this.selection.length === 1;
} }
get isOnlyBufferSelection() {
return !this.selection.length && !!this.bufferSelection;
}
get hasFreeUsers() { get hasFreeUsers() {
const users = this.selection.filter( const users = this.selection.filter(
(x) => x.status !== EmployeeStatus.Disabled && x.isVisitor, (x) => x.status !== EmployeeStatus.Disabled && x.isVisitor,

View File

@ -47,6 +47,8 @@ class UsersStore {
providers = []; providers = [];
accountsIsIsLoading = false; accountsIsIsLoading = false;
operationRunning = false; operationRunning = false;
abortController = new AbortController();
requestRunning = false;
constructor(peopleStore, settingsStore, infoPanelStore, userStore) { constructor(peopleStore, settingsStore, infoPanelStore, userStore) {
this.peopleStore = peopleStore; this.peopleStore = peopleStore;
@ -63,6 +65,12 @@ class UsersStore {
) => { ) => {
const filterData = filter ? filter.clone() : Filter.getDefault(); const filterData = filter ? filter.clone() : Filter.getDefault();
if (this.requestRunning) {
this.abortController.abort();
this.abortController = new AbortController();
}
const filterStorageItem = localStorage.getItem( const filterStorageItem = localStorage.getItem(
`PeopleFilter=${this.userStore.user?.id}`, `PeopleFilter=${this.userStore.user?.id}`,
); );
@ -88,9 +96,16 @@ class UsersStore {
if (filterData.group && filterData.group === "root") if (filterData.group && filterData.group === "root")
filterData.group = undefined; filterData.group = undefined;
const res = await api.people.getUserList(filterData); this.requestRunning = true;
const res = await api.people.getUserList(
filterData,
this.abortController.signal,
);
filterData.total = res.total; filterData.total = res.total;
this.requestRunning = false;
if (updateFilter) { if (updateFilter) {
this.peopleStore.filterStore.setFilterParams(filterData); this.peopleStore.filterStore.setFilterParams(filterData);
} }
@ -141,9 +156,10 @@ class UsersStore {
get needResetUserSelection() { get needResetUserSelection() {
const { isVisible: infoPanelVisible } = this.infoPanelStore; const { isVisible: infoPanelVisible } = this.infoPanelStore;
const { isOneUserSelection } = this.peopleStore.selectionStore; const { isOneUserSelection, isOnlyBufferSelection } =
this.peopleStore.selectionStore;
return !infoPanelVisible || !isOneUserSelection; return !infoPanelVisible || (!isOneUserSelection && !isOnlyBufferSelection);
} }
updateUserStatus = async (status, userIds) => { updateUserStatus = async (status, userIds) => {
return api.people.updateUserStatus(status, userIds).then((users) => { return api.people.updateUserStatus(status, userIds).then((users) => {

View File

@ -40,7 +40,10 @@ import { TReqOption } from "../../utils/axiosClient";
import { EmployeeActivationStatus, ThemeKeys } from "../../enums"; import { EmployeeActivationStatus, ThemeKeys } from "../../enums";
import { TGroup } from "../groups/types"; import { TGroup } from "../groups/types";
export async function getUserList(filter = Filter.getDefault()) { export async function getUserList(
filter = Filter.getDefault(),
signal?: AbortSignal,
) {
let params = ""; let params = "";
// if (fake) { // if (fake) {
// return fakePeople.getUserList(filter); // return fakePeople.getUserList(filter);
@ -57,6 +60,7 @@ export async function getUserList(filter = Filter.getDefault()) {
const res = (await request({ const res = (await request({
method: "get", method: "get",
url: `/people${params}`, url: `/people${params}`,
signal,
})) as TGetUserList; })) as TGetUserList;
res.items = res.items.map((user) => { res.items = res.items.map((user) => {

View File

@ -113,20 +113,21 @@ export function updateRoomMemberRole(id, data) {
}); });
} }
export function getHistory(module, id, signal = null, requestToken) { export function getHistory(
selectionType: "file" | "folder",
id,
signal = null,
requestToken,
) {
const options = { const options = {
method: "get", method: "get",
url: `/feed/filter?module=${module}&withRelated=true&id=${id}`, url: `/files/${selectionType}/${id}/log`,
signal, signal,
}; };
if (requestToken) { if (requestToken) options.headers = { "Request-Token": requestToken };
options.headers = { "Request-Token": requestToken };
}
return request(options).then((res) => { return request(options).then((res) => res);
return res;
});
} }
export function getRoomHistory(id) { export function getRoomHistory(id) {

View File

@ -34,6 +34,7 @@ const StyledTag = styled.div<{
tagMaxWidth?: string; tagMaxWidth?: string;
isLast?: boolean; isLast?: boolean;
isDisabled?: boolean; isDisabled?: boolean;
isDeleted?: boolean;
isNewTag?: boolean; isNewTag?: boolean;
isDefault?: boolean; isDefault?: boolean;
isClickable?: boolean; isClickable?: boolean;
@ -76,6 +77,16 @@ const StyledTag = styled.div<{
pointer-events: none; pointer-events: none;
} }
${({ isDeleted, theme }) =>
isDeleted &&
css`
background: ${theme.tag.deletedBackground};
.tag-text {
text-decoration: line-through;
color: ${theme.tag.deletedColor};
}
`}
.tag-icon { .tag-icon {
${(props) => ${(props) =>
props.theme.interfaceDirection === "rtl" props.theme.interfaceDirection === "rtl"
@ -98,10 +109,13 @@ const StyledTag = styled.div<{
${(props) => ${(props) =>
props.isClickable && props.isClickable &&
!props.isDisabled && !props.isDisabled &&
!props.isDeleted &&
css` css`
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background: ${props.theme.tag.hoverBackground}; background: ${!props.isNewTag
? props.theme.tag.hoverBackground
: props.theme.tag.newTagHoverBackground};
} }
`} `}
`; `;

View File

@ -42,6 +42,7 @@ export const TagPure = ({
label, label,
isNewTag, isNewTag,
isDisabled, isDisabled,
isDeleted,
isDefault, isDefault,
isLast, isLast,
onDelete, onDelete,
@ -99,12 +100,12 @@ export const TagPure = ({
const onClickAction = React.useCallback( const onClickAction = React.useCallback(
(e: React.MouseEvent | React.ChangeEvent) => { (e: React.MouseEvent | React.ChangeEvent) => {
if (onClick && !isDisabled) { if (onClick && !isDisabled && !isDeleted) {
const target = e.target as HTMLDivElement; const target = e.target as HTMLDivElement;
onClick({ roomType, label: target.dataset.tag, providerType }); onClick({ roomType, label: target.dataset.tag, providerType });
} }
}, },
[onClick, isDisabled, roomType, providerType], [onClick, isDisabled, isDeleted, roomType, providerType],
); );
const onDeleteAction = React.useCallback( const onDeleteAction = React.useCallback(
@ -125,6 +126,7 @@ export const TagPure = ({
ref={tagRef} ref={tagRef}
onClick={openDropdownAction} onClick={openDropdownAction}
isDisabled={isDisabled} isDisabled={isDisabled}
isDeleted={isDeleted}
isDefault={isDefault} isDefault={isDefault}
isLast={isLast} isLast={isLast}
tagMaxWidth={tagMaxWidth} tagMaxWidth={tagMaxWidth}
@ -167,6 +169,7 @@ export const TagPure = ({
onClick={onClickAction} onClick={onClickAction}
isNewTag={isNewTag} isNewTag={isNewTag}
isDisabled={isDisabled} isDisabled={isDisabled}
isDeleted={isDeleted}
isDefault={isDefault} isDefault={isDefault}
tagMaxWidth={tagMaxWidth} tagMaxWidth={tagMaxWidth}
data-tag={label} data-tag={label}
@ -190,7 +193,7 @@ export const TagPure = ({
> >
{label} {label}
</Text> </Text>
{isNewTag && ( {isNewTag && !!onDelete && (
<IconButton <IconButton
className="tag-icon" className="tag-icon"
iconName={CrossIconReactSvgUrl} iconName={CrossIconReactSvgUrl}

View File

@ -39,8 +39,10 @@ export interface TagProps {
isNewTag?: boolean; isNewTag?: boolean;
/** Accepts the tag styles as disabled and disables clicking */ /** Accepts the tag styles as disabled and disables clicking */
isDisabled?: boolean; isDisabled?: boolean;
/** Accepts the tag styles as deleted and disables clicking */
isDeleted?: boolean;
/** Accepts the function that is called when the tag is clicked */ /** Accepts the function that is called when the tag is clicked */
onClick: (tag?: object) => void; onClick?: (tag?: object) => void;
/** Accepts the function that ist called when the tag delete button is clicked */ /** Accepts the function that ist called when the tag delete button is clicked */
onDelete?: (tag?: string) => void; onDelete?: (tag?: string) => void;
/** Accepts the max width of the tag */ /** Accepts the max width of the tag */

View File

@ -1872,12 +1872,14 @@ export const getBaseTheme = () => {
}, },
history: { history: {
subtitleColor: gray, subtitleColor: "#a3a9ae",
fileBlockBg: grayLight, fileBlockBg: "#f8f9f9",
dateColor: gray, dateColor: "#A3A9AE",
fileExstColor: gray, fileExstColor: "#A3A9AE",
locationIconColor: gray, locationIconColor: "#A3A9AE",
folderLabelColor: gray, folderLabelColor: "#A3A9AE",
renamedItemColor: "#A3A9AE",
oldRoleColor: "#657077",
}, },
details: { details: {
@ -2248,11 +2250,6 @@ export const getBaseTheme = () => {
descriptionColor: grayText, descriptionColor: grayText,
}, },
tagInput: {
tagBackground: grayLightMid,
tagHoverBackground: lightGrayHover,
},
dropdown: { dropdown: {
background: white, background: white,
borderColor: grayStrong, borderColor: grayStrong,
@ -3009,11 +3006,14 @@ export const getBaseTheme = () => {
tag: { tag: {
color: black, color: black,
background: lightGrayHover, deletedColor: "#A3A9AE",
hoverBackground: grayLightMid, background: "#f3f4f4",
disabledBackground: grayLight, hoverBackground: "#eceef1",
disabledBackground: "#f8f9f9",
deletedBackground: "#F8F9F9",
defaultTagColor: black, defaultTagColor: black,
newTagBackground: grayLightMid, newTagBackground: "#eceef1",
newTagHoverBackground: "#F3F4F4",
}, },
profile: { profile: {

View File

@ -1856,12 +1856,14 @@ const Dark: TTheme = {
}, },
history: { history: {
subtitleColor: gray, subtitleColor: "#A3A9AE",
fileBlockBg: black, fileBlockBg: "#292929",
dateColor: gray, dateColor: "#A3A9AE",
fileExstColor: gray, fileExstColor: "#A3A9AE",
locationIconColor: gray, locationIconColor: "#A3A9AE",
folderLabelColor: gray, folderLabelColor: "#A3A9AE",
renamedItemColor: "#A3A9AE",
oldRoleColor: "#A3A9AE",
}, },
details: { details: {
@ -2231,11 +2233,6 @@ const Dark: TTheme = {
descriptionColor: gray, descriptionColor: gray,
}, },
tagInput: {
tagBackground: grayDarkMid,
tagHoverBackground: lightDarkGrayHover,
},
dropdown: { dropdown: {
background: black, background: black,
borderColor: grayDarkStrong, borderColor: grayDarkStrong,
@ -2990,11 +2987,14 @@ const Dark: TTheme = {
tag: { tag: {
color: white, color: white,
background: grayDarkStrong, deletedColor: "#A3A9AE",
hoverBackground: darkGrayLight, background: "#474747",
disabledBackground: grayDark, hoverBackground: "#282828",
disabledBackground: "#858585",
deletedBackground: "#282828",
defaultTagColor: white, defaultTagColor: white,
newTagBackground: black, newTagBackground: "#242424",
newTagHoverBackground: "#3D3D3D",
}, },
profile: { profile: {